Demo SCOM Script Template




I thought I’d take a moment to publish my SCOM script template.

Whenever I am writing a SCOM script for monitoring, discovery, or automation, there are some “standards” that I want in all my scripts.


1.  I personally feel that all script running in SCOM should at the MINIMUM log at script starting event, and a script completed event with runtime in seconds.  This helps anyone evaluating the server, or agent, just how many scripts are running and on what kind of frequency.

2.  I like to log “who” the script is executed by (what account, whether RunAs or default agent action account.

3.  I like to have an examples section for manually assigning script variables, which is very handy when testing/troubleshooting.

4.  I assign a ScriptName and EventID variables in the script, for consistency when logging events.

5.  I load examples for discovery scripts, propertybags for monitoring scripts, and just remove what isn't needed.  I find this easier and more consistent than going and grabbing an example from some other script I wrote previously.

6.  I have a section on connecting to the SCOM SDK, for scripts that will run automation on the SCOM management server.  I found this method to be the most reliable, as there are scenarios where commandlets just stop working under the MonitoringHost.exe process.


I don’t have a lot of “fluff” in here…. I never like it when I have to page down 3 or 4 pages to get to what a script is actually doing…. this is mostly just the meat and potatoes.


#================================================================================= # Describe Script Here # # Author: Kevin Holman # v1.2 #================================================================================= param($SourceId, $ManagedEntityId, $ComputerName, $Param1, $Param2) # Manual Testing section - put stuff here for manually testing script - typically parameters: #================================================================================= # $SourceId = '{00000000-0000-0000-0000-000000000000}' # $ManagedEntityId = '{00000000-0000-0000-0000-000000000000}' # $ComputerName = "" # $Param1 = "foo" # $Param2 = "bar" #================================================================================= # Constants section - modify stuff here: #================================================================================= # Assign script name variable for use in event logging. # ScriptName should be the same as the ID of the module that the script is contained in $ScriptName = "CompanyID.AppName.Workflow.RuleMonitorDiscoveryDSWA.ps1" $EventID = "1234" #================================================================================= # Starting Script section - All scripts get this #================================================================================= # Gather the start time of the script $StartTime = Get-Date #Set variable to be used in logging events $whoami = whoami # Load MOMScript API $momapi = New-Object -comObject MOM.ScriptAPI #Log script event that we are starting task $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Script is starting. `n Running as ($whoami).") #================================================================================= # Discovery Script section - Discovery scripts get this #================================================================================= # Load SCOM Discovery module $DiscoveryData = $momapi.CreateDiscoveryData(0, $SourceId, $ManagedEntityId) #================================================================================= # PropertyBag Script section - Monitoring scripts get this #================================================================================= # Load SCOM PropertyBag function $bag = $momapi.CreatePropertyBag() #================================================================================= # Connect to local SCOM Management Group Section - If required #================================================================================= # I have found this to be the most reliable method to load SCOM modules for scripts running on Management Servers # Clear any previous errors $Error.Clear() # Import the OperationsManager module and connect to the management group $SCOMPowerShellKey = "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Powershell\V2" $SCOMModulePath = Join-Path (Get-ItemProperty $SCOMPowerShellKey).InstallDirectory "OperationsManager" Import-module $SCOMModulePath New-DefaultManagementGroupConnection "localhost" IF ($Error) { $momapi.LogScriptEvent($ScriptName,$EventID,1,"`n FATAL ERROR: Unable to load OperationsManager module or unable to connect to Management Server. `n Terminating script. `n Error is: ($Error).") EXIT } #================================================================================= # Begin MAIN script section #================================================================================= #Put your stuff in here #================================================================================= # End MAIN script section # Discovery Script section - Discovery scripts get this #================================================================================= # Example discovery of a class with properties $instance = $DiscoveryData.CreateClassInstance("$MPElement[Name='Your.Custom.Class']$") $instance.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $ComputerName) $instance.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", $ComputerName) $instance.AddProperty("$MPElement[Name='Your.Custom.Class']/Property1$", $Param1) $instance.AddProperty("$MPElement[Name='Your.Custom.Class']/Property2$", $Param2) $DiscoveryData.AddInstance($instance) # Return Discovery Items Normally $DiscoveryData # Return Discovery Bag to the command line for testing (does not work from ISE) # $momapi.Return($DiscoveryData) #================================================================================= # PropertyBag Script section - Monitoring scripts get this #================================================================================= # Output a fixed Result = BAD for a monitor example $bag.AddValue("Result","BAD") # Output other data from script into bag $bag.AddValue("Param1",$Param1) $bag.AddValue("Param2",$Param2) # Return all bags $bag #================================================================================= # End of script section #================================================================================= #Log an event for script ending and total execution time. $EndTime = Get-Date $ScriptTime = ($EndTime - $StartTime).TotalSeconds $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Script Completed. `n Script Runtime: ($ScriptTime) seconds.") #================================================================================= # End of script


Do you have stuff you like to place in every script?  If so – let me know in the comments!

Comments (5)

  1. Reidar Johansen says:

    Thank you for sharing this.
    In the starting script section I usually also have:

    The $scriptComputer I would usually add as propertybag in unhosted monitors so it will be visible in the context of the state change events (usefull to see what management server executed the script).

    I also include some details in the event logging by adding some functions and use those functions to create the events, like this:

    function Get-ScriptParam
    foreach($k in $boundKeys){$v=[string]$BoundParameters.Item($k);$ret+=if($k -match ‘password’ -and $v){$k+’=”******”’;}else{$k+’=”’+$v+””;};};
    foreach($key in $keys)
    if($boundKeys -notcontains $key -and $builtin -notcontains $key)
    $var=(Get-Variable -Scope Script -ErrorAction SilentlyContinue)|Where-Object{$_.Name -eq $key};
    if($val){$ret+=if($key -match ‘password’){$key+’=”******”’;}else{$key+’=”’+$val+””;};};
    $ret -join [char]10;
    function Write-OMEventLog
    $snv='{0} : Version [{1}]’ -f $ScriptName,$ScriptVersion;
    # Fallback to prevent any error. SCOM only supports EventIDs up to 20000
    if($EventId -ge 20000){$EventId=19999;};
    # Support WhatIf parameter before making any changes
    if($DebugIndependent -and $MOMScripAPI){$MOMScripAPI.LogScriptEvent($snv,$EventId,$es,$Message);}
    $Message='{0}: {1}’ -f ‘DEBUG’,$Message;
    Write-Verbose $Message;
    function Get-ErrorMessageString
    $msg=if([bool]($e|gm -Name Exception) -and [bool]($e.Exception|gm -Name InnerException) -and $e.Exception.InnerException)
    }elseif([bool]($e|gm -Name Exception) -and $e.Exception)
    }elseif([bool]($e|gm -Name ErrorRecord) -and [bool]($e.ErrorRecord|gm -Name Exception) -and [bool]($e.ErrorRecord.Exception|gm -Name InnerException) -and $e.ErrorRecord.Exception.InnerException)
    }elseif([bool]($e|gm -Name ErrorRecord) -and [bool]($e.ErrorRecord|gm -Name Exception) -and $e.ErrorRecord.Exception)
    }elseif([bool]($e|gm -Name Message))
    if($msg -match ‘\n’)
    $a=@($msg -split ‘\r\n’ -split ‘\n’);
    $b=$a -notmatch ‘^\+’ -notmatch ‘^$’;
    $msg=$b -join ‘ : ‘;
    if($msg -match (‘^Exception calling ‘+[char]34+’.+’+[char]34+’ with ‘+[char]34+’\d+’+[char]34+’ argument\(s\): ‘+[char]34)){$msg=$msg -replace (‘^Exception calling ‘+[char]34+’.+’+[char]34+’ with ‘+[char]34+’\d+’+[char]34+’ argument\(s\): ‘+[char]34) -replace ([char]34+’$’),”;};
    if($msg -eq ”){($errtype+’ has occured without any error message.’).Trim();}else{$msg+’.’ -replace ‘\t|\r\n|\n|\r|\s+’,’ ‘ -replace ‘\.+’,’.’;};
    function Get-ErrorListAsString
    if($Error.Count -gt 0)
    foreach($e in $Error)
    $stacktrace=if([bool]($e|gm -Name InvocationInfo) -and $e.InvocationInfo)
    ‘At line:’+$e.InvocationInfo.ScriptLineNumber+’ char:’+$e.InvocationInfo.OffsetInLine;
    }elseif([bool]($e|gm -Name ErrorRecord) -and [bool]($e.ErrorRecord|gm -Name InvocationInfo) -and $e.ErrorRecord.InvocationInfo)
    ‘At line:’+$e.ErrorRecord.InvocationInfo.ScriptLineNumber+’ char:’+$e.ErrorRecord.InvocationInfo.OffsetInLine;
    }else{‘At line:0 char:0’;};
    $fqerror=if([bool]($e|gm -Name FullyQualifiedErrorId) -and $e.FullyQualifiedErrorId)
    }elseif([bool]($e|gm -Name ErrorRecord) -and [bool]($e.ErrorRecord|gm -Name FullyQualifiedErrorId) -and $e.ErrorRecord.FullyQualifiedErrorId)
    $msg=Get-ErrorMessageString $e;
    if($fqerror){$rt+=’ : ‘+$fqerror;};
    if($stacktrace){$rt+=’ : ‘+$stacktrace;};
    if($msg){$rt+=’ : ‘+$msg;};
    $rt -replace ‘\s+’,’ ‘;

    $messageparam=Get-ScriptParam -Invocation $MyInvocation -BoundParameters $PSBoundParameters -ErrorAction SilentlyContinue;
    if($messageparam){$messageparam=’Script parameters:’+[char]10+$messageparam;};
    $message=’Script executed by user ‘+$scriptUser+’ at ‘+$scriptStart.ToShortTimeString()+[char]10;

    #… do stuff
    $message+=’Add some more details to the event’;

    $scriptRuntimeinSeconds='{0:N2}’ -f $scriptRuntime.TotalSeconds;
    $message+=[char]10+$messageparam+[char]10+[char]10+’Script completed after ‘+$scriptRuntimeinSeconds+’ seconds.’+[char]10;

    # Get errors that have occured, if any
    # If we have errors during script execution
    $severity=if($ef){$message+=[char]10+’Errors found ‘+$ef.Count+’:’+[char]10+$($ef -join [char]10);’Error’;}else{$severity;};
    # Event log have a limit on text size, so truncate message if to large
    if($message.Length -gt 31389){$message=$message.Substring(0,31389);};

    Write-OMEventLog -ScriptName $scriptName -ScriptVersion $scriptVersion -MOMScripAPI $oAPI -EventId $scriptEventID -EventSeverity $severity -Message $message -DebugIndependent -DebugMode $DebugMode -ErrorAction Stop;

    1. rob1974 says:

      I also set the culture

      And my template also includes the testing return code to see if the bag does return what i want:
      $api.Return($Bag) (which you’ve done for the discovery data as well)

  2. Mark Woolaway says:

    When it comes to assigning event IDs, (in your script $EventID = “1234”) what is a good range to use please? I don’t want to pick something already used by another process. Thank you.

    1. Kevin Holman says:

      Unfortunately there isnt one. Different authors always use different event ID’s…. including Microsoft. The best feedback I can give is to look at what’s in your environment and see if there seems to be a range not in use. But there is no guarantee it wont. The point here – is that any alert rules that use the OpsMgr health service script source, should include “event description contains” and use your script name as a criteria, then it doesn’t matter if there is event ID overlap.

  3. PChip says:

    This is my template, which encapsulates the probing in a Try/Catch/Finally to ensure that the scripts runs in case of errors and at least always put logs. I also have a helper logging function to minimize repeating code. Good idea in naming the script as the Module that calls it, will use…

    param (
    ,[Parameter(Mandatory = $false)]
    [switch]$ShowBag = $false
    [Parameter(Mandatory = $false)]
    [switch]$ShowLogs = $true
    $ElapsedTime = [System.Diagnostics.Stopwatch]::StartNew()
    $Global:RetLog = “Version: 1.0.0 — Last Modified: 2016-00-00 @ 00:00”
    $Global:RetLog += “`nUser running script: $($env:USERNAME)`nPoSH Name: $($Host.Name) v$($host.Version)”
    $Global:RetLog += “`nParameters Provided:`n$($PSBoundParameters | Out-String)”
    $ScriptName = “Probe Script”
    $RetEventID = 4100

    if ($Host.Name -notmatch ‘OpsMgr PowerShell’) {
    $RetEventID += 1
    “Note that EventID will be different: $RetEventID”

    $oAPI = New-Object -comObject ‘MOM.ScriptAPI’

    Function Add-Log {
    [Parameter(Mandatory = $true,
    Position = 0)]
    [int]$NumSpace = 0,
    [switch]$ALShowLogs = $ShowLogs,
    [int]$EventType = 0

    if ($ALShowLogs) {
    $oAPI.LogScriptEvent($ScriptName + ‘ –DETAILS’, $($RetEventID – 1000), $EventType, $Message)
    Switch ($EventType) {
    (0) {(“`n” * $NumSpace) + $Message}
    (1) {Write-Host ((“`n” * $NumSpace) + $Message) -ForegroundColor Red}
    (2) {Write-Host ((“`n” * $NumSpace) + $Message) -ForegroundColor Yellow }

    $Global:RetLog += “$(“`n” * ($NumSpace + 1))$Message”

    try {
    #Do Probe here

    #Add to Bag
    $oBag = $oAPI.CreatePropertyBag()
    $oBag.AddValue(‘BagName’, $BagValue)
    Add-Log “Line_of_log”
    if ($ShowBag) {
    $oAPI.Return($oBag); “`n”
    } else {

    } catch {
    $LstError = $_
    Add-Log “Error occurred while capturing probe data at line $($LstError.InvocationInfo.ScriptLineNumber):`n$LstError`n$($LstError.InvocationInfo.PositionMessage)” -NumSpace 3 -EventType 1
    if ($ShowLogs) { “`n`n`n”; $Global:RetLog }
    trap { continue } $oAPI.LogScriptEvent($ScriptName, $RetEventID, 1, $Global:RetLog)
    $oAPI.LogScriptEvent($ScriptName, $($RetEventID + 100), 1, $LstError) #also write error on separate entry just in case previous fails
    } finally {
    Add-Log “Script Run Time: $($ElapsedTime.Elapsed.TotalSeconds) second(s)”
    $oAPI.LogScriptEvent($ScriptName, $RetEventID, 0, $Global:RetLog)
    “RetLog Length: $($Global:RetLog.Length) chars”
    Remove-Variable -Name RetLog -Scope Global -Confirm:$false

Skip to main content