M365SSVC.ServicesMon.Serial.PA

M365SSVC.ServicesMon.Serial.PA (ProbeActionModuleType)

Designed to leverage a Posh script PA for retrieving object properties/metrics.

Element properties:

TypeProbeActionModuleType
IsolationAny
AccessibilityInternal
RunAsM365SL.RunAs.Profile
InputTypeSystem.BaseData
OutputTypeMicrosoft.Windows.SerializedObjectData

Member Modules:

ID Module Type TypeId RunAs 
POSH ProbeAction Microsoft.Windows.PowerShellProbe Default

Overrideable Parameters:

IDParameterTypeSelector
CD_ServiceIDRegExstring$Config/CD_ServiceIDRegEx$
EventIDFilterstring$Config/EventIDFilter$
PoshLibraryPathstring$Config/PoshLibraryPath$
ProbeActionTimeoutSecondsint$Config/ProbeActionTimeoutSeconds$
WriteToEventLogbool$Config/WriteToEventLog$

Source Code:

<ProbeActionModuleType ID="M365SSVC.ServicesMon.Serial.PA" Accessibility="Internal" Batching="false" PassThrough="false" RunAs="M365SL!M365SL.RunAs.Profile">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="CD_ServiceIDRegEx" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="MgmtApiTokenScopeURL" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="MgmtApiTokenURL" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="MgmtApiURL" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="M365_AccountName" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="M365_AccountPassword" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="M365_ClientID" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="M365_ClientSecret" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="EventIDFilter" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="PoshLibraryPath" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="ProbeActionTimeoutSeconds" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="TenantName" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="TLSVersion" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="WorkflowName" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="WriteToEventLog" type="xsd:boolean"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="CD_ServiceIDRegEx" Selector="$Config/CD_ServiceIDRegEx$" ParameterType="string"/>
<OverrideableParameter ID="EventIDFilter" Selector="$Config/EventIDFilter$" ParameterType="string"/>
<OverrideableParameter ID="PoshLibraryPath" Selector="$Config/PoshLibraryPath$" ParameterType="string"/>
<OverrideableParameter ID="ProbeActionTimeoutSeconds" Selector="$Config/ProbeActionTimeoutSeconds$" ParameterType="int"/>
<OverrideableParameter ID="WriteToEventLog" Selector="$Config/WriteToEventLog$" ParameterType="bool"/>
</OverrideableParameters>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<ProbeAction ID="POSH" TypeID="Windows!Microsoft.Windows.PowerShellProbe">
<ScriptName>M365SSVC.ServicesMon.ps1</ScriptName>
<ScriptBody><Script>&lt;#
#===============================================================================================================================================================
Created by: Brian Zoucha, Tyson Paul
Filename: M365SSVC.ServicesMon.ps1
Description: This will retrieve a few properties from the Services object to be used for monitoring workflows.

Version History:
2021.09.03.1544 - Updated mgmt API URL to use Graph. Added Username/Password now that the Service Communications API accepts Delegated auth.
2020.11.18.2356 - Added API Url params
2020.10.27.1031 - Added $CD_ServiceIDRegEx to accomodate serialized output filtering for agent task.
2020.10.23.1350 - v1

Requirements: Must have an Azure application registered and have the following:
Delegated permissions
ClientID &amp; Application Secret
===============================================================================================================================================================
#&gt;

Param(
[string]$CD_ServiceIDRegEx, #Used for agent task only
[string]$M365_AccountName,
[string]$M365_AccountPassword,

[string]$MgmtApiTokenScopeURL,
[string]$MgmtApiTokenURL,
[string]$MgmtApiURL,

# Azure Application auth
[string]$M365_ClientID,
[string]$M365_ClientSecret,

# Comma-separated list of event IDs, script will only write to log for these EventIDs. This is a way to only write specific events. Only valid if $WriteToEventLog parameter is 'true'.
[string]$EventIDFilter,

# Comma-separated list of .ps1 files to load
[string]$PoshLibraryPath,


# This is a clever way to utilize the exact same script for both rule/mon and agent tasks.
[Parameter(Mandatory=$false,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false,
ValueFromRemainingArguments=$false)]
[ValidateSet('PropertyBag', 'Serialized')]
[string]$ScriptOutputType = 'PropertyBag',

# Azure tenant
[string]$TenantName,

[string]$TLSVersion,

# Typically this is the name of the workflow calling this script. Should be set to the name of the probe/WA if being used by both rules/mons so as not to break cookdown.
# Keep in mind that datasource params need to be identical for cookdown to work.
[string]$WorkflowName,

# type:string. SCOM bool params are different than Posh bool. Will get converted to Posh bool below.
[string]$WriteToEventLog = 'false'
)

# Set defaults for event filters. Apparently setting this in the Params declaration did not work.
If (-NOT $EventIDFilter) {
$EventIDFilter = "9990,9991,9992,9995,9996,9997,9998,9999"
}

$ScriptName = 'M365SSVC.ServicesMon.ps1'
[bool]$WriteToEventLog = [System.Convert]::ToBoolean($WriteToEventLog)
$NameSpace = 'Services'
$Testing = $false

######################### FUNCTIONS ############################
################################################################
Function Load-Library {
Param (
[string]$PoshLibraryPath
)
$ErrorActionPreference = 'STOP'
If (-NOT $NameSpace.Length) {
$NameSpace = $ScriptName
}
$EventSource = "M365 Supplemental"
$EventLogName = "Application"
$objEvent = New-Object System.Diagnostics.EventLog
$objEvent.Source = $EventSource
$objEvent.Log = $EventLogName
$EventID_Normal = 9992
$EventID_Anomaly = 9996
[Int]$info=4 #System.Diagnostics.EventInstance
[Int]$critical=1
[Int]$warn=2

If ($PoshLibraryPath ){
Start-Sleep -Seconds 5 #Allow time for Library to be deployed
ForEach ($Path in $PoshLibraryPath.Split(',') ){
Try {
If (($Path.Length) -AND ($Path -notmatch '^-1$')) {
. $Path
If ([bool]$WriteToEventLog -eq $true) {
$msg = "Line [$($MyInvocation.ScriptLineNumber)], $($MyInvocation.PSCommandPath): Success loading library file: [$($Path)]"
$logData = "$($Msg)^$($Msg)^$($ScriptName)^$($TenantName)^$($NameSpace)"
[array]$arrMessage = @($logData.Split('^'))
$objEventID = New-Object System.Diagnostics.EventInstance($EventID_Normal,1,$info)
$objEvent.WriteEvent($objEventID, @($arrMessage))
}
}
} Catch {
[bool]$LibExists =$false
Try {
# It's possible the libfile exists (as it should) but cannot be loaded for some reason.
[bool]$LibExists = [bool](Test-Path -Path $Path -PathType Leaf)
} Catch {
#File does not exist. LibExists = $false already by default
}
$msg = "Line [$($MyInvocation.ScriptLineNumber )]: Error loading PoshLibrary at path:[$($Path)]. Library file exists? [$($LibExists)]. This is likely to cause many other dependent functions to fail. `n`nError data: $($_)`n`n"
$logData = "$($Msg)^$($Msg)^$($ScriptName)^$($TenantName)^$($NameSpace)"
[array]$arrMessage = @($logData.Split('^'))
$objEventID = New-Object System.Diagnostics.EventInstance($EventID_Anomaly,1,$warn)
$objEvent.WriteEvent($objEventID, @($arrMessage))
}
}
}
$ErrorActionPreference = 'CONTINUE'
}
################################################################

############## TESTING ##############
&lt;# #Run this as needed when testing

Function Testing {
$Testing = $true
Get-Item Alias:\_LINE_ -ErrorAction Ignore | Remove-Item -ErrorAction Ignore
$testParamsFile = (Join-path $TestFolder ("PARAMS_$($ScriptName)"))
#Write-Host "$(Test-Path $testParamsFile):$($testParamsFile)" -F Yellow -B Green
. $testParamsFile
. Load-Library -PoshLibraryPath $PoshLibraryPath

# Encode user data/passwords in current test user context
$M365_ClientSecret = Encode-UserData $M365_ClientSecret_PLAINTEXT
$M365_AccountPassword = Encode-UserData $M365_AccountPassword_PLAINTEXT
$error.Clear()
}

$ThisScriptFullName = $MyInvocation.MyCommand.Path
$TestFolder = Join-Path (Split-Path $ThisScriptFullName -Parent) "TestSetup"
If (Test-Path -Path $TestFolder) {
. Testing
}

#&gt;
############## TESTING ##############


. Load-Library -PoshLibraryPath $PoshLibraryPath
LogIt -EventID 9990 -Type $info -Msg "Begin script..." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()

LogIt -EventID 9992 -Type $info -msg "Setting TLS for session, Version: [$($TLSVersion)]." -Proceed $WriteToEventLog -Line $(_LINE_); $Error.Clear();
#Set Default TLS
. Set-TLS -TLSVersion $TLSVersion

#&lt;#
# Decode Account Password
$Message = "Decode password for [$($NameSpace)]."
LogIt -EventID 9992 -Type $info -Msg $Message -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Try {
$M365_AccountPassword_DECRYPTED = Decode-UserData -Data $M365_AccountPassword
} Catch {
$Message += "Unable to $($Message). See error data."
LogIt -EventID 9995 -Type $warn -Msg $Message -Proceed $true -LINE $(_LINE_); $Error.Clear()
}

# Decode Client Secret
$Message = "Decode client secret for [$($NameSpace)]."
LogIt -EventID 9992 -Type $info -Msg $Message -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Try {
$M365_ClientSecret_DECRYPTED = Decode-UserData -Data $M365_ClientSecret
} Catch {
$Message += "Unable to $($Message). See error data."
LogIt -EventID 9995 -Type $warn -Msg $Message -Proceed $true -LINE $(_LINE_); $Error.Clear()
}

# Get Access Token
$Message = "Get Access Token for [$($NameSpace)]."
LogIt -EventID 9992 -Type $info -Msg $Message -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Try {
# Apparently TenantID and TenantName can be interchanged
$TokenResponse = Get-AccessToken -Delegated -User $M365_AccountName -Pass $M365_AccountPassword_DECRYPTED -ClientID $M365_ClientID -ClientSecret $M365_ClientSecret_DECRYPTED -TenantName $TenantName -ApiTokenUrl $MgmtApiTokenURL -ApiTokenScopeURL $MgmtApiTokenScopeURL
} Catch {
$Message = "Unable to $($Message). See error data. Exiting."
LogIt -EventID 9997 -Type $warn -Msg $Message -Proceed $true -LINE $(_LINE_); $Error.Clear()
If ($ScriptOutputType -eq 'Serialized') {
Write-Output $Message
}
Exit
}

# Retrieve objects
LogIt -EventID 9992 -Type $info -Msg "Retrieve $($NameSpace) object(s)" -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
$Headers = @{"Authorization"= "Bearer $($TokenResponse.access_token)"; "Content-Type"="application/json"};
$URL = "$($MgmtApiURL)/v1.0/admin/serviceAnnouncement/healthOverviews"
Try {
$Result = Invoke-RestMethod -Method GET -Headers $Headers -Uri $URL -ErrorAction Stop
$AllObjects = $Result.Value
#Filtering occurs when script is initiated by agent task. Otherwise all items are retrieved.
$FilteredObjects = $Result.Value | Where-Object {$_.ID -match $CD_ServiceIDRegEx}

LogIt -EventID 9992 -Type $info -Msg "Retrieved $($NameSpace) object(s): [$($FilteredObjects | Sort-Object Status | Select-Object Status,ID,WorkloadDisplayName | Out-String)]" -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
} Catch {
$Message = "Unable to get $($NameSpace) object(s) from URL: [$($URL)]. See error data. Verify that the correct permissions have been granted to the App Registration and that Admin Consent has been granted to all permissions, regardless of if consent is 'required'. Exiting. "
LogIt -EventID 9996 -Type $warn -Msg $Message -Proceed $true -LINE $(_LINE_); $Error.Clear()
Exit
}


#----------------------------------------
# Build bag for Servicess
$DateStamp = (Get-Date)
LogIt -EventID 9992 -Type $info -Msg "Proceed to build bags for bag category: [Services]..." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
ForEach ($Obj in $FilteredObjects) {
LogIt -EventID 9992 -Type $info -Msg "Build (Servicess) bag for [$($Obj.ID)]..." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()

# Set up BasicBag
$bag = New-Object -TypeName BasicBag
$bag.AddValue('Category',"ServiceStatus")
#$bag.AddValue('Message',"")
$bag.AddValue('ID', "$($Obj.ID)")

$bag.AddValue('WorkloadDisplayName',"$($Obj.Service)")
$bag.AddValue('Status',"$($Obj.Status)")
#$bag.AddValue('StatusDisplayName',"$($Obj.StatusDisplayName)")
#$bag.AddValue('IncidentIDs',"$($Obj.IncidentIds)")

$bag.AddValue('ThisScriptInstanceGUID',"$ThisScriptInstanceGUID")
$bag.AddValue('Whoami',"$whoami")
$bag.AddValue('M365_ClientID',"$M365_ClientID")
$bag.AddValue('M365_AccountName',"$M365_AccountName")
$bag.AddValue('TenantName',"$TenantName")

OutPut $bag -Tag "ServiceID:$($Obj.ID)"
}

Remove-Variable -Name bag -ErrorAction Ignore

#Performance metrics

$TotalServices = [double]$($AllObjects.Count)
$OtherwiseOperational = @("ServiceOperational","ServiceRestored","FalsePositive","PostIncidentReportPublished")
$TotalOperationalServices = @($AllObjects | Where-Object {$_.Status -in $OtherwiseOperational} ).Count
$TotalDegradedServices = @($AllObjects | Where-Object {$_.Status -match 'degrad' } ).Count
$TotalAbnormalServices = ($TotalServices - $TotalOperationalServices)
$PercentOperationalServices = ("{0:N0}" -F (($TotalOperationalServices / $TotalServices)*100) )

# Set up BasicBag for performance data
$bag = New-Object -TypeName BasicBag
$bag.AddValue('Category',"ServicePerformance")
$bag.AddValue('TotalServices', [double]$TotalServices )
$bag.AddValue('TotalOperationalServices', [double]$TotalOperationalServices )
$bag.AddValue('TotalDegradedServices', [double]$TotalDegradedServices )
$bag.AddValue('TotalAbnormalServices', [double]$TotalAbnormalServices )
$bag.AddValue('PercentOperationalServices', [double]$PercentOperationalServices )
$bag.AddValue('DegradedServices', [string]$( @(($AllObjects | Sort-Object -Property Id | Where-Object {$_.Status -notin $OtherwiseOperational}).ID -join ', ') ) )

$bag.AddValue('ThisScriptInstanceGUID',"$ThisScriptInstanceGUID")
$bag.AddValue('Whoami',"$whoami")
$bag.AddValue('M365_ClientID',"$M365_ClientID")
$bag.AddValue('M365_AccountName',"$M365_AccountName")
$bag.AddValue('TenantName',"$TenantName")
OutPut $bag -Tag "ServicePerformance"

#----------------------------------------

LogIt -EventID 9991 -Type $info -Msg "Script End. Finished in [$($ScriptTimer.Elapsed.TotalSeconds)] seconds. `n" -LINE $(_LINE_)
</Script></ScriptBody>
<Parameters>
<Parameter>
<Name>CD_ServiceIDRegEx</Name>
<Value>$Config/CD_ServiceIDRegEx$</Value>
</Parameter>
<Parameter>
<Name>MgmtApiTokenScopeURL</Name>
<Value>$Config/MgmtApiTokenScopeURL$</Value>
</Parameter>
<Parameter>
<Name>MgmtApiTokenURL</Name>
<Value>$Config/MgmtApiTokenURL$</Value>
</Parameter>
<Parameter>
<Name>MgmtApiURL</Name>
<Value>$Config/MgmtApiURL$</Value>
</Parameter>
<Parameter>
<Name>M365_AccountName</Name>
<Value>$Config/M365_AccountName$</Value>
</Parameter>
<Parameter>
<Name>M365_AccountPassword</Name>
<Value>$Config/M365_AccountPassword$</Value>
</Parameter>
<Parameter>
<Name>M365_ClientID</Name>
<Value>$Config/M365_ClientID$</Value>
</Parameter>
<Parameter>
<Name>M365_ClientSecret</Name>
<Value>$Config/M365_ClientSecret$</Value>
</Parameter>
<Parameter>
<Name>EventIDFilter</Name>
<Value>$Config/EventIDFilter$</Value>
</Parameter>
<Parameter>
<Name>PoshLibraryPath</Name>
<Value>$FileResource[Name='Res.M365SSVC.M365Library.ps1.Resource']/Path$,$Config/PoshLibraryPath$</Value>
</Parameter>
<Parameter>
<Name>ScriptOutputType</Name>
<Value>Serialized</Value>
</Parameter>
<Parameter>
<Name>TenantName</Name>
<Value>$Config/TenantName$</Value>
</Parameter>
<Parameter>
<Name>TLSVersion</Name>
<Value>$Config/TLSVersion$</Value>
</Parameter>
<Parameter>
<Name>WorkflowName</Name>
<Value>M365SSVC.ServicesMon.PB.PA_($Config/WorkflowName$)</Value>
</Parameter>
<Parameter>
<Name>WriteToEventLog</Name>
<Value>$Config/WriteToEventLog$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>$Config/ProbeActionTimeoutSeconds$</TimeoutSeconds>
</ProbeAction>
</MemberModules>
<Composition>
<Node ID="POSH"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>Windows!Microsoft.Windows.SerializedObjectData</OutputType>
<InputType>System!System.BaseData</InputType>
</ProbeActionModuleType>