Demo SCOM Script Template


<!--[if lt IE 9]>

<![endif]-->


Comments (5)
  1. Reidar Johansen says:

    Thank you for sharing this.
    In the starting script section I usually also have:
    [Threading.Thread]::CurrentThread.CurrentCulture=’en-US’;
    [Threading.Thread]::CurrentThread.CurrentUICulture=’en-US’;
    [string]$scriptUser=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name;
    [string]$scriptComputer=[System.Environment]::MachineName;
    [string]$scriptVersion=’1.20170911.1′;

    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
    {
    [CmdletBinding()]
    param
    (
    [Parameter(Mandatory=$true)]
    $Invocation,
    [Parameter(Mandatory=$true)]
    $BoundParameters
    )
    $builtin=@(‘ErrorAction’,’WarningAction’,’Verbose’,’ErrorVariable’,’WarningVariable’,’OutVariable’,’OutBuffer’,’Debug’,’Confirm’,’PipelineVariable’,’WhatIf’);
    $pc=$Invocation.MyCommand.Parameters;
    if(-not($pc)){return;};
    $keys=$pc.Keys;
    $boundKeys=$BoundParameters.Keys;
    $ret=@();
    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($var)
    {
    $val=[string]$var.Value;
    if($val){$ret+=if($key -match ‘password’){$key+’=”******”’;}else{$key+’=”’+$val+””;};};
    };
    };
    };
    $ret -join [char]10;
    };
    function Write-OMEventLog
    {
    [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact=’Medium’)]
    param
    (
    [Parameter(Mandatory=$true)]
    [string]$ScriptName,
    [Parameter(Mandatory=$true)]
    [string]$ScriptVersion,
    [Parameter(Mandatory=$true)]
    $MOMScripAPI,
    [Parameter(Mandatory=$true)]
    [int]$EventId,
    [ValidateSet(‘Info’,’Error’,’Warning’)]
    [string]$EventSeverity=’Info’,
    [Parameter(Mandatory=$true)]
    [string]$Message,
    [switch]$DebugIndependent,
    $DebugMode
    )
    $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;};
    $es=switch($EventSeverity){‘Warning'{2;};’Error'{1;};default{0;};};
    # Support WhatIf parameter before making any changes
    if($PSCmdlet.ShouldProcess($Message))
    {
    if($DebugIndependent -and $MOMScripAPI){$MOMScripAPI.LogScriptEvent($snv,$EventId,$es,$Message);}
    else
    {
    if($DebugMode)
    {
    $Message='{0}: {1}’ -f ‘DEBUG’,$Message;
    if($MOMScripAPI){$MOMScripAPI.LogScriptEvent($snv,$EventId,$es,$Message);};
    };
    };
    };
    Write-Verbose $Message;
    };
    function Get-ErrorMessageString
    {
    [CmdletBinding()]
    param
    (
    [Parameter(Mandatory=$true)]
    $e
    )
    $errtype=”;
    $msg=if([bool]($e|gm -Name Exception) -and [bool]($e.Exception|gm -Name InnerException) -and $e.Exception.InnerException)
    {
    $errtype=$e.Exception.InnerException.GetType().FullName;
    $e.Exception.InnerException.Message;
    }elseif([bool]($e|gm -Name Exception) -and $e.Exception)
    {
    $errtype=$e.Exception.GetType().FullName;
    $e.Exception.Message;
    }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)
    {
    $errtype=$e.ErrorRecord.Exception.InnerException.GetType().FullName;
    $e.ErrorRecord.Exception.InnerException.Message;
    }elseif([bool]($e|gm -Name ErrorRecord) -and [bool]($e.ErrorRecord|gm -Name Exception) -and $e.ErrorRecord.Exception)
    {
    $errtype=$e.ErrorRecord.Exception.GetType().FullName;
    $e.ErrorRecord.Exception.Message;
    }elseif([bool]($e|gm -Name Message))
    {
    $errtype=$e.GetType().FullName;
    $e.Message;
    }else{”;};
    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)
    {
    $i=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)
    {
    $e.FullyQualifiedErrorId;
    }elseif([bool]($e|gm -Name ErrorRecord) -and [bool]($e.ErrorRecord|gm -Name FullyQualifiedErrorId) -and $e.ErrorRecord.FullyQualifiedErrorId)
    {
    $e.ErrorRecord.FullyQualifiedErrorId;
    }else{‘Unknown’;};
    $msg=Get-ErrorMessageString $e;
    $rt=[char]36+’Error[‘+$i+’]’;
    if($fqerror){$rt+=’ : ‘+$fqerror;};
    if($stacktrace){$rt+=’ : ‘+$stacktrace;};
    if($msg){$rt+=’ : ‘+$msg;};
    $rt -replace ‘\s+’,’ ‘;
    $i++;
    };
    };
    };

    $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’;

    $scriptRuntime=(Get-Date)-$scriptStart;
    $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
    $ef=@(Get-ErrorListAsString);
    # 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
    $message=$message.Trim([char]10);
    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
      [Threading.Thread]::CurrentThread.CurrentCulture=’en-US’;
      [Threading.Thread]::CurrentThread.CurrentUICulture=’en-US’;

      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 {
    param
    (
    [Parameter(Mandatory = $true,
    Position = 0)]
    [ValidateNotNullOrEmpty()]
    [string]$Message,
    [int]$NumSpace = 0,
    [Alias(‘ShowLogs’)]
    [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 {
    $oBag
    }

    } 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
    }

Comments are closed.

Skip to main content