SCOMAgentHelper.TriggerMMToggle.WriteStatusEvent.WA

SCOMAgentHelper.TriggerMMToggle.WriteStatusEvent.WA (WriteActionModuleType)

Will change MM status, then trigger event to write current MM status to agent event log.

Element properties:

TypeWriteActionModuleType
IsolationAny
AccessibilityPublic
RunAsDefault
InputTypeSystem.BaseData
OutputTypeMicrosoft.Windows.SerializedObjectData

Member Modules:

ID Module Type TypeId RunAs 
POSH_TriggerMMToggleforWinComp WriteAction Microsoft.Windows.PowerShellWriteAction Default

Overrideable Parameters:

IDParameterTypeSelector
DurationMinutesMaxAllowedint$Config/DurationMinutesMaxAllowed$
WriteActionTimeoutSecondsint$Config/WriteActionTimeoutSeconds$
SpreadInitializationOverIntervalSecondsint$Config/SpreadInitializationOverIntervalSeconds$
WaitForStatusToUpdateSecondsint$Config/WaitForStatusToUpdateSeconds$
WriteToEventLogbool$Config/WriteToEventLog$

Source Code:

<WriteActionModuleType ID="SCOMAgentHelper.TriggerMMToggle.WriteStatusEvent.WA" Accessibility="Public" Batching="false">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Action" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="AgentDisplayName" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Comment" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="DurationMinutes" minOccurs="1" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="DurationMinutesMaxAllowed" minOccurs="1" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Force" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Reason" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="SpreadInitializationOverIntervalSeconds" minOccurs="1" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="UserName" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="Verify" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="WaitForStatusToUpdateSeconds" minOccurs="1" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="WorkflowName" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="WriteActionTimeoutSeconds" minOccurs="1" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="WriteToEventLog" minOccurs="1" type="xsd:boolean"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="DurationMinutesMaxAllowed" Selector="$Config/DurationMinutesMaxAllowed$" ParameterType="int"/>
<OverrideableParameter ID="WriteActionTimeoutSeconds" Selector="$Config/WriteActionTimeoutSeconds$" ParameterType="int"/>
<OverrideableParameter ID="SpreadInitializationOverIntervalSeconds" Selector="$Config/SpreadInitializationOverIntervalSeconds$" ParameterType="int"/>
<OverrideableParameter ID="WaitForStatusToUpdateSeconds" Selector="$Config/WaitForStatusToUpdateSeconds$" ParameterType="int"/>
<OverrideableParameter ID="WriteToEventLog" Selector="$Config/WriteToEventLog$" ParameterType="bool"/>
</OverrideableParameters>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<WriteAction ID="POSH_TriggerMMToggleforWinComp" TypeID="Windows!Microsoft.Windows.PowerShellWriteAction">
<ScriptName>TriggerMMToggleforWinComp.ps1</ScriptName>
<ScriptBody><Script>&lt;#
Script: TriggerMMToggleforWinComp.ps1
Author: Tyson Paul (https://monitoringguys.com/)
Description:
1) Will enable/disable maintenance mode for the designated Object.

Version History:
2021.01.21.1153 - Improved logging for DurationMinutes data.
2021.01.08.2347 - Added $DurationMinutesMaxAllowed. Thanks, Richard!
2020.08.10.1342 - Improved MG group connection.
2020.08.07.1454 - Updated Get-SCOMMaintenanceMode to included related objects so that MM window update would be recursive
2020.08.06.1419 - Updated logging.
2020.08.05.1405 - Integrated WriteStatusEvent logic into this script instead of using the separate WriteAction.
2020.08.04.1234 - Added 'Force' param. If object is already in MM window, will updated new End time to agree with DurationMinutes. Added property bag.
2020.07.22.0000 - Added activity #2 above to write current mm status to agent. Added property bag for composite DS. Added SpreadInitializationOverIntervalSeconds.
2020.07.21.0000 - Initial version
#&gt;

Param (

# Enable/Disable maintenance mode
[Parameter(Mandatory=$true,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false,
ValueFromRemainingArguments=$false,
Position=0,
ParameterSetName='Parameter Set 1')]
[ValidateSet("Start", "End")]
[string]$Action,

# Which computer (FQDN) initiated the MM activity
[string]$AgentDisplayName,

# Optional
[string]$Comment = "&lt;no comment provided&gt;",

# How long the MM window will be
[int]$DurationMinutes,

[int]$DurationMinutesMaxAllowed,

# If Computer is already in MM, force the End time to agree with the DurationMinutes
[Parameter(Mandatory=$false,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false,
ValueFromRemainingArguments=$false,
Position=0,
ParameterSetName='Parameter Set 1')]
[ValidateSet("True", "False")]
[string]$Force = 'False',


# Required for initiating MM
[Parameter(Mandatory=$false,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false,
ValueFromRemainingArguments=$false,
Position=0,
ParameterSetName='Parameter Set 1')]
[ValidateSet("PlannedOther","UnplannedOther","PlannedHardwareMaintenance","UnplannedHardwareMaintenance","PlannedHardwareInstallation","UnplannedHardwareInstallation","PlannedOperatingSystemReconfiguration","UnplannedOperatingSystemReconfiguration","PlannedApplicationMaintenance","UnplannedApplicationMaintenance","ApplicationInstallation","ApplicationUnresponsive","ApplicationUnstable","SecurityIssue","LossOfNetworkConnectivity")]
[string]$Reason = "PlannedOther",

# This is a clever way to force the workflow to randomize a delay (seconds) in progress/activity. If a large number of instances trigger at once, the DB could become sluggish. This will help balance load if needed.
[int]$SpreadInitializationOverIntervalSeconds = 0,

# User context that initiated the MM activity
[string]$UserName,

# This is used here to help determine if it is needed to launch the WriteMMStatus task.
[Parameter(Mandatory=$false,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false,
ValueFromRemainingArguments=$false,
ParameterSetName='Parameter Set 1')]
[ValidateSet("Workflows","MgmtPerspectiveOnly","None")]
[string]$Verify ='MgmtPerspectiveOnly',

# After making changes to MM status, wait for mgmt server to recognize this (backend DB really) before proceding. Also this is used for waiting for the agent Task to complete.
[int]$WaitForStatusToUpdateSeconds,

# This is very useful in logging activity to identify the workflow that created the log entries.
[string]$WorkflowName,

[string]$WriteToEventLog = 'False'
)

# Convert to boolean
[Bool]$WriteToEventLog = [System.Convert]::ToBoolean($WriteToEventLog)


&lt;############## TESTING #############
$Action = 'Start'
$Action = 'End'
$AgentDisplayName = 'db01.contoso.com'
$Comment = "My comment"
$DurationMinutes = 11
$SpreadInitializationOverIntervalSeconds = 0
$UserName = "TBone"
$WorkflowName = "SCOMAgentHelper.DetectAgentMMToggleEvent.Rule"
$WriteToEventLog = $true
#&gt;

########################################################################################################
Function LogIt {
Param
(
[int]$EventID,
[int]$Type = 2,
[string]$Message = 'No message specified.',
[bool]$Proceed,
$Line
)

If ($Proceed)
{
$output = @"

WorkflowName: $WorkflowName
Message: $Message

ThisScriptInstanceGUID: $ThisScriptInstanceGUID
ScriptLine: $Line
Running As: $whoami
MM Initiated by user: $UserName
Action: $Action
WriteToEventLog: $WriteToEventLog
WaitForStatusToUpdateSeconds: $WaitForStatusToUpdateSeconds
SpreadInitializationOverIntervalSeconds: $SpreadInitializationOverIntervalSeconds
Verify: $Verify
Original DurationMinutes: $DurationMinutes
DurationMinutesMaxAllowed: $DurationMinutesMaxAllowed
DurationMinutesMinAllowed: $DurationMinutesMinAllowed
DurationMinutes (Actual): $AdjustedDurationMinutes

Any Errors: $Error

"@

$oEvent = New-Object -ComObject 'MOM.ScriptAPI'
If ($output.Length -gt $maxLogLength){
$output = ($output.Substring(0,([math]::Min($output.Length,$maxLogLength) )) + '...TRUNCATED...')
}
$oEvent.LogScriptEvent("$ScriptName",$EventID,$Type,$output )
}
}
########################################################################################################
Function Import-SCOMPowerShellModule{

Import-Module OperationsManager

# Try to locate OperationsManager PowerShell module location
If (-NOT ((Get-Module OperationsManager).Count) ) {
Try {
$InstallDir = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Powershell\V2").InstallDirectory
$SCOMPosh = (Join-Path $InstallDir "OperationsManager")
Import-module $SCOMPosh -ErrorAction Stop
If (-NOT ((Get-Module OperationsManager).Count) ) {
Throw
}
} Catch {
LogIt -EventID 9996 -Type $Critical -Message "Failed to import OperationsManager module. Exiting" -Proceed $WriteToEventLog -Line $(_LINE_)
Exit
}
}
}
########################################################################################################

Function _LINE_ {
$MyInvocation.ScriptLineNumber
}
########################################################################################################
########################################################################################################


[int]$info = 0
[int]$Critical = 1
[int]$warning = 2
[string]$whoami = whoami.exe
$ScriptName ='TriggerMMToggleforWinComp.ps1'
$ThisScriptInstanceGUID = [System.GUID]::NewGuid().ToString().Substring((35 ) -5).ToUpper()
[int]$maxLogLength = 31000 #max chars to allow for event log messages
[int]$DurationMinutesMinAllowed = 10

# Force Duration to within max and min allowed values.
$AdjustedDurationMinutes = [math]::Min(([math]::Max($DurationMinutes,$DurationMinutesMinAllowed)), $DurationMinutesMaxAllowed)

If ($SpreadInitializationOverIntervalSeconds) {
$RandSleep = (Get-Random -Minimum 0 -Maximum ($SpreadInitializationOverIntervalSeconds +1))
LogIt -EventID 9990 -Type $info -Message "Begin script. 'SpreadInitializationOverIntervalSeconds' detected. Pausing for random duration (0 - $($SpreadInitializationOverIntervalSeconds) seconds): [$($RandSleep)]..." -Proceed $WriteToEventLog -Line $(_LINE_)
Start-Sleep -Seconds $RandSleep
}
Else {
LogIt -EventID 9990 -Type $info -Message "Begin script." -Proceed $WriteToEventLog -Line $(_LINE_)
}

If ($AdjustedDurationMinutes -ne $DurationMinutes) {
LogIt -EventID 9992 -Type $info -Message "DurationMinutes adjusted to: [$($AdjustedDurationMinutes)] from original [$($DurationMinutes)]" -Proceed $WriteToEventLog -Line $(_LINE_)
}

. Import-SCOMPowerShellModule

# Connect to SCOM mgmt group
Try{
$MG = Get-SCOMManagementGroup -ErrorAction Stop
If (-NOT $MG) {
Throw "No SCOMManagementGroupConnection"
}
} Catch {
Try {
LogIt -EventID 9992 -Type $info -Message "No mgmt group connection. Establish new connection..." -Proceed $true -Line $(_LINE_)
$MGConnSetting = New-Object Microsoft.EnterpriseManagement.ManagementGroupConnectionSettings('localhost')
$MG = New-Object Microsoft.EnterpriseManagement.ManagementGroup($MGConnSetting)
If (-NOT $MG) {
Throw "No SCOMManagementGroupConnection"
}
}Catch {
LogIt -EventID 9996 -Type $critical -Message "Unable to establish connect to mgmt group on localhost. Exiting." -Proceed $true -Line $(_LINE_)
Exit
}
}


#region ModifyMM

# Get Computer object
$WinCompClass = Get-SCOMClass -Name "Microsoft.Windows.Computer"
$Criteria = "DisplayName = '$AgentDisplayName'"
$objCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectCriteria($Criteria ,$WinCompClass)
$WinComp = $MG.GetMonitoringObjects($objCriteria)

$MMWindowUpdated = 'False'

If (-NOT $WinComp) {
LogIt -EventID 9996 -Type $critical -Message "FAILURE. Unable to find Microsoft.Windows.Computer object with name [$($AgentDisplayName)] . Exiting." -Proceed $true -Line $(_LINE_)
Exit
}

# Either Start or End the MM period
Switch ($Action) {
"Start" {
$EndMMDateStamp = ((Get-Date).AddMinutes($AdjustedDurationMinutes))
If ($WinComp.InMaintenanceMode) {
$tmpMessage = "Maintenance mode already active on Microsoft.Windows.Computer object with name [$($AgentDisplayName)]. 'FORCE' param [$($Force)]."
Switch ($Force){
# Update existing MM. Force the existing MM 'window' to agree with the DurationMinutes
'True' {
Try {
# Update MM for object and any associated objects. This is effectively a recursive action.
$IsInMM = Get-SCOMMaintenanceMode -Instance ($WinComp + ($WinComp.GetRelatedMonitoringObjects()))
Set-SCOMMaintenanceMode -MaintenanceModeEntry $IsInMM -EndTime $EndMMDateStamp -Comment $Comment -Reason $Reason -ErrorAction Stop
$MMWindowUpdated = 'True'
LogIt -EventID 9992 -Type $info -Message "$($tmpMessage) Updated End time: [$($EndMMDateStamp.ToString())]." -Proceed $WriteToEventLog -Line $(_LINE_)
Break;
} Catch {
LogIt -EventID 9992 -Type $info -Message "$($tmpMessage) Failed to update End time: [$($EndMMDateStamp.ToString())]." -Proceed $WriteToEventLog -Line $(_LINE_)
Break;
}
}

Default {
LogIt -EventID 9992 -Type $info -Message "$($tmpMessage) No action taken. Use '-Force' parameter to update the MM window. Exiting now." -Proceed $WriteToEventLog -Line $(_LINE_)
}
}
}
# It is assumed that the object is not already in MM
Else {
Try {
Start-SCOMMaintenanceMode -Instance $WinComp -EndTime $EndMMDateStamp -Comment $Comment -Reason $Reason -ErrorAction Stop
LogIt -EventID 9992 -Type $info -Message "Maintenance mode started on Microsoft.Windows.Computer object with name [$($AgentDisplayName)]." -Proceed $WriteToEventLog -Line $(_LINE_)
} Catch {
LogIt -EventID 9996 -Type $critical -Message "FAILURE initiating maintenance mode on Microsoft.Windows.Computer object with name [$($AgentDisplayName)]. Exiting." -Proceed $true -Line $(_LINE_)
}
}
}#end match


"End" {
If (-NOT ($WinComp.InMaintenanceMode)) {
LogIt -EventID 9992 -Type $info -Message "Maintenance not active on Microsoft.Windows.Computer object with name [$($AgentDisplayName)]. No action taken. Exiting." -Proceed $WriteToEventLog -Line $(_LINE_)
}
Try {
$WinComp.StopMaintenanceMode([DateTime]::Now.ToUniversalTime(),[Microsoft.EnterpriseManagement.Common.TraversalDepth]::Recursive);
LogIt -EventID 9992 -Type $info -Message "Maintenance mode ended on Microsoft.Windows.Computer object with name [$($AgentDisplayName)]." -Proceed $WriteToEventLog -Line $(_LINE_)
} Catch {
Try {
Add-PSSnapin "Microsoft.EnterpriseManagement.OperationsManager.Client"
$WinComp.StopMaintenanceMode([DateTime]::Now.ToUniversalTime(),[Microsoft.EnterpriseManagement.Common.TraversalDepth]::Recursive);
LogIt -EventID 9992 -Type $info -Message "Maintenance mode ended on Microsoft.Windows.Computer object with name [$($AgentDisplayName)]." -Proceed $WriteToEventLog -Line $(_LINE_)
} Catch {
LogIt -EventID 9996 -Type $critical -Message "FAILURE ending maintenance mode on Microsoft.Windows.Computer object with name [$($AgentDisplayName)]. Exiting." -Proceed $true -Line $(_LINE_)
}
}
}#end match
}#end Switch
#endregion ModifyMM


#region AgentTask
#----------------------------- Agent Task Region -----------------------------

If ( ($Verify -match 'None') -OR ( ($Verify -match 'Workflows') -AND ($MMWindowUpdated -match 'False') )) {
$explanation = @"
Verify: [$($Verify)], Force:[$($Force)], MMWindowUpdated:[$($MMWindowUpdated)]. No need to write status to agent log so exiting now.

Explanation:
If the user indicated to 'Verify Workflows' in combination with Force, this gets tricky. If the object was already in MM,
the duration would simply be updated but no workflow activity events would result on the agent so the powershell command would never verify/detect 1215/1216 events.
Check if the object was already in MM and duration was updated successfully. If so, ignore 'Verify Workflows' settings, proceed with triggering task to write to event log
because the posh module on the agent will depend on this data.
"@
LogIt -EventID 9991 -Type $info -Message $explanation -Proceed $WriteToEventLog -Line $(_LINE_)
$MG.Dispose()
Exit
}


# At this point it would be appropriate to trigger task to write status event
########## Launch agent task to write current status to event log ##########

# Get fresh Computer object
$WinCompClass = Get-SCOMClass -Name "Microsoft.Windows.Computer"
$Criteria = "DisplayName = '$AgentDisplayName'"
$objCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectCriteria($Criteria ,$WinCompClass)
$WinComp = $MG.GetMonitoringObjects($objCriteria)
$pauseForStatusCheckSeconds = 15

# If Action is provided it is assumed that MM change was recently triggered, the script will try to wait for the status to be updated so that the resulting status message will be completely accurate.
# Otherwise it is assumed that the intention is to simply trigger a status message with no other action taken.
If ($Action -match 'Enable|Disable') {
switch ($Action){
'Enable' {
$ExpectedStatus = $true
}

'Disable' {
$ExpectedStatus = $false
}
}

If (-NOT (($WinComp.InMaintenanceMode) -eq $ExpectedStatus)) {
LogIt -EventID 9992 -Type $info -Message "WinComp.DisplayName: $($WinComp.DisplayName), 'InMaintenanceMode' status [$($WinComp.InMaintenanceMode.ToString())] is not yet changed to expected status [$($ExpectedStatus)]. Will keep checking every [$($pauseForStatusCheckSeconds)] seconds until timeout [$($WaitForStatusToUpdateSeconds) seconds] is expired. " -Proceed $WriteToEventLog -Line $(_LINE_)
}

$timer = [System.Diagnostics.Stopwatch]::StartNew()
# Loop for a reasonable amount of time to get expected MM status, based on "Action" parameter.
While ( (-NOT (($WinComp.InMaintenanceMode) -eq $ExpectedStatus)) -AND ($timer.Elapsed.TotalSeconds -le ($WaitForStatusToUpdateSeconds + $RandSleep) ) ) {
Start-Sleep -Seconds $pauseForStatusCheckSeconds
$WinComp = $MG.GetMonitoringObjects($objCriteria)
}
}


$hashMMStatus = @{
'True' = 'StatusWindowsComputerInMaintenanceModeTrue'
'False' = 'StatusWindowsComputerInMaintenanceModeFalse'
}
[string]$mmStatus = $hashMMStatus[($WinComp.InMaintenanceMode.ToString())]

LogIt -EventID 9992 -Type $info -Message "WinComp.DisplayName: $($WinComp.DisplayName), MMStatus: [$($mmStatus)] retrieved after $($timer.Elapsed.TotalSeconds) seconds." -Proceed $WriteToEventLog -Line $(_LINE_)
$timer.Stop()

# Get Task
Try {
$TaskName = 'SCOMAgentHelper.WriteMaintModeStatustoEventLog.Task'
$TaskWF = Get-SCOMTask -Name $TaskName -ErrorAction Stop

# Get Task Target Instance
$TaskTargetClass = Get-SCOMClass -Id $TaskWF.Target.Id.Guid -ErrorAction Stop
$objCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectCriteria($Criteria ,$TaskTargetClass)
$TaskTargetInstance = $MG.GetMonitoringObjects($objCriteria)
LogIt -EventID 9992 -Type $info -Message "TaskTargetInstance.DisplayName: $($TaskTargetInstance.DisplayName)" -Proceed $WriteToEventLog -Line $(_LINE_)
} Catch {
LogIt -EventID 9996 -Type $critical -Message "Unable to set up Task: [$($TaskName)]. Exiting." -Proceed $true
$MG.Dispose()
Exit
}

# Set up override for task: status in param[1]. Include data indicating if an existing MM window was updated (param[2]).
$hashOverrides = @{
Message = "$($mmStatus)^PreviousMMSettingsUpdated:$($MMWindowUpdated)^$($ThisScriptInstanceGUID)"
}

Try {
$Task = Start-SCOMTask -Instance $TaskTargetInstance -Override $hashOverrides -Task $TaskWF -Verbose -ErrorAction Stop
LogIt -EventID 9992 -Type $info -Message "Task started for target: [$($TaskTargetInstance.DisplayName)]. Task.Id.Guid:$($Task.Id.Guid)" -Proceed $WriteToEventLog -Line $(_LINE_)
} Catch {
LogIt -EventID 9996 -Type $critical -Message "Error while attempting to start task: [$($TaskName)] for target: [$($TaskTargetInstance.DisplayName)]. Exiting." -Proceed $true -Line $(_LINE_)
$MG.Dispose()
Exit
}

$timer = [System.Diagnostics.Stopwatch]::StartNew()
While ($TaskResult.Status -notmatch 'Succeeded' -AND ($timer.Elapsed.TotalSeconds -le $WaitForStatusToUpdateSeconds) ) {
$TaskResult = (Get-SCOMTaskResult -Id $Task.Id)
Start-Sleep -Seconds 5
}
$timer.Stop()
If ($timer.Elapsed.TotalSeconds -gt $WaitForStatusToUpdateSeconds){
LogIt -EventID 9992 -Type $warning -Message "Timer expired. Check task status manually. TaskID: $($Task.Id.Guid)" -Proceed $WriteToEventLog -Line $(_LINE_)
}
Else {
LogIt -EventID 9991 -Type $info -Message "Script end. Task completed. Output: $($TaskResult.Output.ToString())" -Proceed $WriteToEventLog -Line $(_LINE_)
$TaskResult.Output
}

#endregion AgentTask
$MG.Dispose()
LogIt -EventID 9991 -Type $info -Message "End script." -Proceed $WriteToEventLog -Line $(_LINE_)
</Script></ScriptBody>
<Parameters>
<Parameter>
<Name>Action</Name>
<Value>$Config/Action$</Value>
</Parameter>
<Parameter>
<Name>AgentDisplayName</Name>
<Value>$Config/AgentDisplayName$</Value>
</Parameter>
<Parameter>
<Name>Comment</Name>
<Value>$Config/Comment$</Value>
</Parameter>
<Parameter>
<Name>DurationMinutes</Name>
<Value>$Config/DurationMinutes$</Value>
</Parameter>
<Parameter>
<Name>DurationMinutesMaxAllowed</Name>
<Value>$Config/DurationMinutesMaxAllowed$</Value>
</Parameter>
<Parameter>
<Name>Force</Name>
<Value>$Config/Force$</Value>
</Parameter>
<Parameter>
<Name>Reason</Name>
<Value>$Config/Reason$</Value>
</Parameter>
<Parameter>
<Name>SpreadInitializationOverIntervalSeconds</Name>
<Value>$Config/SpreadInitializationOverIntervalSeconds$</Value>
</Parameter>
<Parameter>
<Name>UserName</Name>
<Value>$Config/UserName$</Value>
</Parameter>
<Parameter>
<Name>Verify</Name>
<Value>$Config/Verify$</Value>
</Parameter>
<Parameter>
<Name>WaitForStatusToUpdateSeconds</Name>
<Value>$Config/WaitForStatusToUpdateSeconds$</Value>
</Parameter>
<Parameter>
<Name>WorkflowName</Name>
<Value>$Config/WorkflowName$</Value>
</Parameter>
<Parameter>
<Name>WriteToEventLog</Name>
<Value>$Config/WriteToEventLog$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>$Config/WriteActionTimeoutSeconds$</TimeoutSeconds>
</WriteAction>
<!--<WriteAction ID="POSH_WriteNewStatusEvent" TypeID="Windows!Microsoft.Windows.PowerShellWriteAction">
<ScriptName>TriggerAgentTask-WriteMMStatus.ps1</ScriptName>
<ScriptBody><Script><![CDATA[<#
Script: TriggerAgentTask-WriteMMStatus.ps1
Description: This is for the WriteAction on the mgmt server to trigger the agent task which will write the MM status to the event log
Author: Tyson Paul
Version History:
2020.08.04.1531 - Added 'Verify' param to enable immediate exit if $Verify -match 'Workflows|None'. No point running this task in this case.
Added logic so task would trigger if an existing MM got updated and the Verify Workflows param was used. The posh module on agent will depend on the event log as no workflow activity events will occur from an 'update' to a MM window.
2020.07.31.1401 - Added logic to account for no Action param value; will immediately trigger status message.
2020.07.22.1935 - Added SpreadInitializationOverIntervalSeconds, WaitForStatusToUpdateSeconds
2020.07.20 - Original

Note: Why use 'Verify=MgmtPerspectiveOnly'? This would be useful if user is rebooting/poweroff a Computer. There's no need to wait for workflows to unload. The only concern is that the
mgmt server knows the object is in MM, and thus it won't alert about heartbeats. This relatively fast response to the agent log will indicate that it's 'safe' to power off the Computer
without waiting for workflows to unload.
#>
Param (
[string]$Action,
[string]$AgentDisplayName,

# True|False. This indicates if the object was already in MM but was simply updated.
[string]$PreviousMMSettingsUpdated = 'False',

[Parameter(Mandatory=$false,
ValueFromPipeline=$false,
ValueFromPipelineByPropertyName=$false,
ValueFromRemainingArguments=$false,
ParameterSetName='Parameter Set 1')]
[ValidateSet("Workflows","MgmtPerspectiveOnly","None")]
[string]$Verify='MgmtPerspectiveOnly',

[int]$SpreadInitializationOverIntervalSeconds = 0,
[int]$WaitForStatusToUpdateSeconds,
[string]$WorkflowName = '<no name provided>',
[String]$WriteToEventLog='False'
)

[Bool]$WriteToEventLog = [System.Convert]::ToBoolean($WriteToEventLog)


<# TESTING #############
$WriteToEventLog = $true
# $AgentDisplayName = 'db01.contoso.com'
#>

########################################################################################################
Function LogIt {
Param
(
[int]$EventID,
[int]$Type = 2,
[string]$Message = 'No message specified.',
[bool]$Proceed,
$Line
)

If ($Proceed)
{
$output = @"

WorkflowName: $WorkflowName
Message: $Message

ThisScriptInstanceGUID: $ThisScriptInstanceGUID
ScriptLine: $Line
Running As: $whoami
Verify: $Verify
WriteToEventLog: $WriteToEventLog

Any Errors: $Error

"@

$oEvent = New-Object -ComObject 'MOM.ScriptAPI'
If ($output.Length -gt $maxLogLength){
$output = ($output.Substring(0,([math]::Min($output.Length,$maxLogLength) )) + '...TRUNCATED...')
}
$oEvent.LogScriptEvent("$ScriptName",$EventID,$Type,$output )
}
}
########################################################################################################
Function Import-SCOMPowerShellModule{

Import-Module OperationsManager

# Try to locate OperationsManager PowerShell module location
If (-NOT ((Get-Module OperationsManager).Count) ) {
Try {
$InstallDir = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Powershell\V2").InstallDirectory
$SCOMPosh = (Join-Path $InstallDir "OperationsManager")
Import-module $SCOMPosh -ErrorAction Stop
If (-NOT ((Get-Module OperationsManager).Count) ) {
Throw
}
} Catch {
LogIt -EventID 9996 -Type $Critical -Message "Failed to import OperationsManager module. Exiting" -Proceed $WriteToEventLog -Line $(_LINE_)
Exit
}
}
}
########################################################################################################

Function Get-CurrentLineNumber {
$MyInvocation.ScriptLineNumber
}
########################################################################################################
New-Alias -Name _LINE_ -Value Get-CurrentLineNumber -Description 'Returns the current line number in a PowerShell script file.' -ErrorAction SilentlyContinue
########################################################################################################
[int]$info = 0
[int]$Critical = 1
[int]$warn = 2
[string]$whoami = whoami.exe
$ScriptName ='TriggerAgentTask-WriteMMStatus.ps1'
$ThisScriptInstanceGUID = (New-Guid).Guid.Substring(((New-Guid).Guid.Length ) -6).ToUpper()
[int]$maxLogLength = 31000 #max chars to allow for event log messages
$pauseForStatusCheckSeconds = 15


<#
If the user indicated to Verify Workflows in combination with Force, this gets tricky. If the object was already in MM,
the duration would simply be updated but no workflow activity events would result on the agent so the powershell command would never verify/detect 1215/1216 events.
Check if the object was already in MM and duration was updated. If so, ignore 'Verify' settings, proceed with triggering task to write to event log.
The posh module on the agent will depend on this data because no workflow activity events will be written after a MM settings update.
#>
If ( ($Verify -match 'None') -OR ( ($Verify -match 'Workflows') -AND ($PreviousMMSettingsUpdated -match 'False') )) {
LogIt -EventID 9991 -Type $info -Message "Verify: [$($Verify)]. No point writing status to agent log. Abandoning this script. Exiting." -Proceed $WriteToEventLog -Line $(_LINE_)
Exit
}

If ($SpreadInitializationOverIntervalSeconds) {
LogIt -EventID 9990 -Type $info -Message "Begin script. 'SpreadInitializationOverIntervalSeconds' detected. Pausing for random duration (seconds): [$($SpreadInitializationOverIntervalSeconds)]..." -Proceed $WriteToEventLog -Line $(_LINE_)
$RandSleep = (Get-Random -Minimum 0 -Maximum ($SpreadInitializationOverIntervalSeconds +1))
Start-Sleep -Seconds $RandSleep
}
Else {
LogIt -EventID 9990 -Type $info -Message "Begin script." -Proceed $WriteToEventLog -Line $(_LINE_)
}

. Import-SCOMPowerShellModule

If (-NOT ((Get-Module OperationsManager).Count) ) {
LogIt -EventID 9996 -Type $Critical -Message "Failed to import OperationsManager module. Exiting" -Proceed $WriteToEventLog -Line $(_LINE_)
Exit
}


# Connect to SCOM mgmt group
Try{
$MG = Get-SCOMManagementGroup -ErrorAction Stop
If (-NOT $MG) {
Throw "No SCOMManagementGroupConnection"
}
} Catch {
Try {
New-SCOMManagementGroupConnection -ErrorAction Stop
$MG = Get-SCOMManagementGroup -ErrorAction Stop
If (-NOT $MG) {
Throw "No SCOMManagementGroupConnection"
}
}Catch {
LogIt -EventID 9996 -Type $critical -Message "Unable to establish connect to mgmt group on localhost. Exiting." -Proceed $true -Line $(_LINE_)
Exit
}
}

# Get Computer object
$WinCompClass = Get-SCOMClass -Name "Microsoft.Windows.Computer"
$Criteria = "DisplayName = '$AgentDisplayName'"
$objCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectCriteria($Criteria ,$WinCompClass)
$WinComp = $MG.GetMonitoringObjects($objCriteria)

# If Action is provided it is assumed that MM change was recently triggered, the script will try to wait for the status to be updated so that the resulting status message will be completely accurate.
# Otherwise it is assumed that the intention is to simply trigger a status message with no other action taken.
If ($Action -match 'Enable|Disable') {
switch ($Action){
'Enable' {
$ExpectedStatus = $true
}

'Disable' {
$ExpectedStatus = $false
}
}

If (-NOT (($WinComp.InMaintenanceMode) -eq $ExpectedStatus)) {
LogIt -EventID 9992 -Type $info -Message "WinComp.DisplayName: $($WinComp.DisplayName), 'InMaintenanceMode' status [$($WinComp.InMaintenanceMode.ToString())] is not yet changed to expected status [$($ExpectedStatus)]. Will keep checking every [$($pauseForStatusCheckSeconds)] seconds until timeout [$($WaitForStatusToUpdateSeconds) seconds] is expired. " -Proceed $WriteToEventLog -Line $(_LINE_)
}

$timer = [System.Diagnostics.Stopwatch]::StartNew()
# Loop for a reasonable amount of time to get expected MM status, based on "Action" parameter.
While ( (-NOT (($WinComp.InMaintenanceMode) -eq $ExpectedStatus)) -AND ($timer.Elapsed.TotalSeconds -le ($WaitForStatusToUpdateSeconds + $RandSleep) ) ) {
Start-Sleep -Seconds $pauseForStatusCheckSeconds
$WinComp = $MG.GetMonitoringObjects($objCriteria)
}
$timer.Stop()
}


$hashMMStatus = @{
'True' = 'StatusWindowsComputerInMaintenanceModeTrue'
'False' = 'StatusWindowsComputerInMaintenanceModeFalse'
}
[string]$mmStatus = $hashMMStatus[($WinComp.InMaintenanceMode.ToString())]

LogIt -EventID 9992 -Type $info -Message "WinComp.DisplayName: $($WinComp.DisplayName), MMStatus: `n$($mmStatus) retrieved after $($timer.Elapsed.TotalSeconds) seconds." -Proceed $WriteToEventLog -Line $(_LINE_)


# Get Task
Try {
$TaskName = 'SCOMAgentHelper.WriteMaintModeStatustoEventLog.Task'
$TaskWF = Get-SCOMTask -Name $TaskName -ErrorAction Stop

# Get Task Target Instance
$TaskTargetClass = Get-SCOMClass -Id $TaskWF.Target.Id.Guid -ErrorAction Stop
$objCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectCriteria($Criteria ,$TaskTargetClass)
$TaskTargetInstance = $MG.GetMonitoringObjects($objCriteria)
LogIt -EventID 9992 -Type $info -Message "TaskTargetInstance.DisplayName: $($TaskTargetInstance.DisplayName)" -Proceed $WriteToEventLog -Line $(_LINE_)
} Catch {
LogIt -EventID 9996 -Type $critical -Message "Unable to set up Task: [$($TaskName)]. Exiting." -Proceed $true
Exit
}

# Set up override for task: status in param[1]. Include data indicating if an existing MM window was updated (param[2]).
$hashOverrides = @{
Message = "$($mmStatus)^PreviousMMSettingsUpdated:$($PreviousMMSettingsUpdated)"
}

Try {
$Task = Start-SCOMTask -Instance $TaskTargetInstance -Override $hashOverrides -Task $TaskWF -Verbose -ErrorAction Stop
LogIt -EventID 9992 -Type $info -Message "Task started. Task.Id.Guid:$($Task.Id.Guid)" -Proceed $WriteToEventLog -Line $(_LINE_)
} Catch {
LogIt -EventID 9996 -Type $critical -Message "Error while attempting to start task: [$($TaskName)]. Exiting." -Proceed $true -Line $(_LINE_)
Exit
}

$ScriptTimer = [System.Diagnostics.Stopwatch]::StartNew()
While ($TaskResult.Status -notmatch 'Succeeded' -AND ($ScriptTimer.Elapsed.TotalSeconds -le $WaitForStatusToUpdateSeconds) ) {
#Write-Host "$(Get-Date): Waiting for task to complete. Sleeping for 5 seconds" -F Yellow
$TaskResult = (Get-SCOMTaskResult -Id $Task.Id)
Start-Sleep -Seconds 5
}
$ScriptTimer.Stop()
If ($ScriptTimer.Elapsed.TotalSeconds -gt $WaitForStatusToUpdateSeconds){
LogIt -EventID 9992 -Type $warn -Message "Timer expired. Check task status manually. TaskID: $($Task.Id.Guid)" -Proceed $WriteToEventLog -Line $(_LINE_)
}
Else {
LogIt -EventID 9991 -Type $info -Message "Script end. Task completed. Output: $($TaskResult.Output.ToString())" -Proceed $WriteToEventLog -Line $(_LINE_)
$TaskResult.Output
}
]]></Script></ScriptBody>
<Parameters>
<Parameter>
<Name>AgentDisplayName</Name>
<Value>$Config/AgentDisplayName$</Value>
</Parameter>
<Parameter>
<Name>Action</Name>
<Value>$Config/Action$</Value>
</Parameter>

<Parameter>
<Name>PreviousMMSettingsUpdated</Name>
<Value>$Data/Property[@Name='PreviousMMSettingsUpdated']$</Value>
</Parameter>

<Parameter>
<Name>Verify</Name>
<Value>$Config/Verify$</Value>
</Parameter>
<Parameter>
<Name>SpreadInitializationOverIntervalSeconds</Name>
<Value>$Config/SpreadInitializationOverIntervalSeconds$</Value>
</Parameter>
<Parameter>
<Name>WaitForStatusToUpdateSeconds</Name>
<Value>$Config/WaitForStatusToUpdateSeconds$</Value>
</Parameter>
<Parameter>
<Name>WorkflowName</Name>
<Value>$Config/WorkflowName$</Value>
</Parameter>
<Parameter>
<Name>WriteToEventLog</Name>
<Value>$Config/WriteToEventLog$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>$Config/WriteActionTimeoutSeconds$</TimeoutSeconds>
</WriteAction>-->
</MemberModules>
<Composition>
<!--<Node ID="POSH_WriteNewStatusEvent">-->
<Node ID="POSH_TriggerMMToggleforWinComp"/>
<!--</Node>-->
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>Windows!Microsoft.Windows.SerializedObjectData</OutputType>
<InputType>System!System.BaseData</InputType>
</WriteActionModuleType>