Public/Get-SCOMAgentWorkflows.ps1
|
Function Get-SCOMAgentWorkflows { <# .NOTES Name: Get-SCOMAgentWorkflows Author: Tyson Paul Blog: https://monitoringguys.com/ Version History: 2022.06.09.1749 - v1 Previously Get-SCOMRunningWorkflows was a standalone function for "running" workflows only. .DESCRIPTION Will output an array of objects containing helpful information on instances and the workflows running/failed for those instances. .EXAMPLE # #Example 1 #All workflows (Running and Failed) are retrieved for the agent name provided. The results are saved in a variable, $r, which is piped to GridView. The function call is dot-sourced so that any subsequent calls to this function will execute faster. PS C:\> $r = . Get-SCOMAgentWorkflows -Status All -AgentName 'db01.contoso.com' -Verbose PS C:\> $r | Out-GridView #Example 2 # Agent names matching the mask provided are piped to the function. PS C:\> (Get-SCOMAgent -DNSHostName *db*).DisplayName | Get-SCOMAgentWorkflows #Example 3 # Results are sumarized by agents with failed workflows PS C:\> $result = Get-SCOMAgentWorkflows -AgentName 'ms02.contoso.com','db01.contoso.com','devdb01.contoso.com' PS C:\> $result | Where-Object Status -eq 'Failed' | Group-Object Agent | Select-Object @{Name="Failed";Expression={$_.Count}},Name Failed Name ------ ---- 14 devdb01.CONTOSO.COM 9 DB01.CONTOSO.COM #Advanced examples for analyzing the workflow data # Storing results to XML, then consuming results from XML. PS C:\> Get-SCOMAgentWorkflows -AgentName 'ad01.contoso.com' | Export-Clixml -Path 'C:\Test\ad01.contoso.com.XML' $WFs = Import-Clixml -Path 'C:\Test\ad01.contoso.com.XML' # Total Instances $TOI = $WFs.instance.count Write-Host "Total object instances: $TOI" # Worflows types: $WFAT = $WFs.workflows.workflow | Group-Object Name | Measure-Object | Select-Object count -ExpandProperty count Write-Host "Worflows types: $WFAT" # Total WF instances running, all types: $TWFAT = $WFs.wfcount | Measure-Object -Sum | Select-Object sum -ExpandProperty sum Write-Host "Total WF instances running, all types: $TWFAT" # Workflows of category type $Category = 'EventCollection' $WFoCT = $WFs.workflows.workflow | ? {($_.Category -match $Category) -AND ($_.WriteActionCollection -notmatch 'Alert')} $WFoCT_sum = $WFoCT | Group-Object Name | Measure-Object | Select-Object count -ExpandProperty Count Write-Host "Workflows of category type [$($Category), Not Alert]: $WFoCT_sum" # Total WF instances of category type $TWFIoCT = $WFoCT | Group-Object Name | Measure-Object -sum count | Select-Object sum -ExpandProperty sum Write-Host "Total WF instances of category type [$($Category)]: $TWFIoCT" #Lists ############################################### # Classes $classes = Get-SCOMClass -ID (($wfs.InstanceName).LeastDerivedNonAbstractManagementPackClassId | Group-Object).Name Write-Host "$($Classes.count) class target types" # Class Names $Classes = ($wfs.InstanceName).LeastDerivedNonAbstractManagementPackClassId` | Group-Object ` | Select-Object count ` , @{Name="Class"; Expression = {(Get-SCOMClass -id $_.Name).Name}} ` , @{Name="Id";Expression = {$_.Name}}, @{Name="Identifier"; Expression = {(Get-SCOMClass -id $_.Name).Identifier}} ` | Sort-Object count ` | Format-Table $Classes $allWorkflows = @{} # List of workflows, instance count, category $WFs.workflows.workflow | % {$allWorkflows[($_.Name)] = $_} $WFs.workflows.workflow | ? {($_.Category -match $Category) -AND ($_.WriteActionCollection -notmatch 'Alert')} | Group-Object Name | Select-Object count,name,@{Name='DisplayName';Expression = {$allWorkflows[($_.Name)].DisplayName}} ,@{Name='Category';Expression = {$Category}} | Sort-Object count | Format-Table # All workflow types with counts $WFs.workflows.workflow | Group-Object Name | Select-Object count,name,@{Name='DisplayName';Expression = {$allWorkflows[($_.Name)].DisplayName}} ,@{Name='Category';Expression = {$allWorkflows[($_.Name)].Category}} | Sort-Object count | Format-Table #> [CmdletBinding(DefaultParameterSetName='Parameter Set 1', SupportsShouldProcess=$false, PositionalBinding=$false, HelpUri = 'https://monitoringguys.com/', ConfirmImpact='Medium')] Param( # FQDN of one or more agents [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=0, ParameterSetName='AgentName')] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [string[]]$AgentName, # Param1 help description [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false)] [ValidateSet('TasktResult', 'Detailed')] $OutputType = 'Detailed', [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [ValidateSet('Failed','Running','All')] $Status = 'All', # Seconds before function will abandon waiting for task to complete [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false)] [int]$TimeoutSeconds=600 ) BEGIN { $HSClass = Get-SCOMClass -Name "Microsoft.SystemCenter.HealthService" $AllHS = (Get-SCOMClassInstance -Class $HSClass) [System.Collections.ArrayList]$AllResults = @() [System.Collections.ArrayList]$TaskNames = @() [System.Collections.ArrayList]$Task = @() $thisStatus = @{ "Microsoft.SystemCenter.GetAllRunningWorkflows" = "Running" "Microsoft.SystemCenter.GetAllFailedWorkflows" = "Failed" } $hashTasks = @{ "Microsoft.SystemCenter.GetAllRunningWorkflows" = [System.Collections.ArrayList]@() "Microsoft.SystemCenter.GetAllFailedWorkflows" = [System.Collections.ArrayList]@() } Switch ($Status) { 'All' { $NULL = $TaskNames.Add('Microsoft.SystemCenter.GetAllRunningWorkflows') $NULL = $TaskNames.Add('Microsoft.SystemCenter.GetAllFailedWorkflows') } 'Failed' { $NULL = $TaskNames.Add('Microsoft.SystemCenter.GetAllFailedWorkflows') } 'Running' { $NULL = $TaskNames.Add('Microsoft.SystemCenter.GetAllRunningWorkflows') } } $MinWait = 2 $WaitSeconds = [math]::Min($MinWait,$TimeoutSeconds) ############################ FUNCTIONS ################################ <# .NOTES Name: Get-SCOMAgentWorkflowsReport Author: Tyson Paul Blog: https://monitoringguys.com/ Version History: 2022.10.24.0937 - Added -TaskCredentials $NULL for SCOM2022+ compatibility 2020.09.22.1433 - v1 .DESCRIPTION Will output an array of objects containing helpful information on instances and the workflows running for those instances. .EXAMPLE # #Example PS C:\> $TaskResult = Get-SCOMAgentWorkflows -AgentName 'ad01.contoso.com','db01.contoso.com' -OutputType 'TasktResult' PS C:\> $TaskResult | Get-SCOMAgentWorkflowsReport .EXAMPLE # #Example PS C:\> Get-SCOMAgentWorkflowsReport -TaskResult (Get-SCOMAgentWorkflows -AgentName 'ad01.contoso.com' -OutputType TasktResult) .EXAMPLE # #Example PS C:\> Get-SCOMAgentWorkflowsReport -TaskResult $TaskResult .INPUTS Task result from 'Microsoft.SystemCenter.GetAllRunningWorkflows' agent task. .OUTPUTS [System.Object[]] #> Function Get-SCOMAgentWorkflowsReport { Param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, ParameterSetName='Parameter Set 1')] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [System.Object[]]$TaskResult, [Parameter(Mandatory=$true, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, ParameterSetName='Parameter Set 1')] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [ValidateSet('Failed','Running')] $Status ) BEGIN { ############################ FUNCTIONS ################################ Function Build-Obj { Param ( $WF, [string]$Type ='NotDefined' ) $tmp = New-Object PSCustomObject $tmp | Add-Member -MemberType NoteProperty -Name 'Type' -Value $type $tmp | Add-Member -MemberType NoteProperty -Name 'Workflow' -Value $wf Return $tmp } ######################## END FUNCTIONS ################################ Remove-Variable -Name 'Instances','rpt1' -ErrorAction Ignore [System.Collections.ArrayList]$arrReports = @() # Load all workflow types into one hash. dot-source this function to speed up multiple runs. If (-NOT $allWFTypes) { $allWFTypes=@{} $allClasses=@{} Write-Warning "Loading all workflows into cache object. This may require a few minutes. dot-source this function next time to speed up consecutive uses." ForEach ($item in @(Get-SCOMMonitor)) { $allWFTypes[($item.Name)] = ( Build-Obj -WF $item -Type Monitor) } ForEach ($item in @(Get-SCOMRule)) { $allWFTypes[($item.Name)] = ( Build-Obj -WF $item -Type Rule) } ForEach ($item in @(Get-SCOMDiscovery)) { $allWFTypes[($item.Name)] = ( Build-Obj -WF $item -Type Discovery) } # Build hash of all class types ForEach ($Class in (Get-SCOMClass) ){ $allClasses[$Class.Id.Guid] = $Class} } Else { Write-Verbose "'allWFTypes' object [$($allWFTypes.Count)] already cached. 'allClasses' object already cached [$($allClasses.Count)]. Will not rebuild; use existing. This will save time." } }#end BEGIN #---------------------------------------------------------------- PROCESS { ForEach ($Result in $TaskResult) { $ThisAgent = (Get-SCOMClassInstance -Id $Result.TargetObjectId) # Get the instance objects involved $Instances = (($Result.Output | ForEach-Object {[xml]$_}).DataItem) $hashInstances = @{} $Instances.Details.Instance | ForEach-Object {$hashInstances[($_.ID -Replace '{|}' ,'' )] = $_ } # Get basic workflow data: Instance ID (of target object), Workflow count, Workflow list/array $InstanceDetails = ([xml]($Result.Output)).DataItem.Details.Instance | Select-Object @{Name='Id';Expression={(($_.ID -Replace '{|}','').ToString())}}, @{Name='WFCount';Expression={$_.Workflow.Count} }, @{Name='Workflow';Expression={$_.Workflow | Sort-Object} } | Sort-Object WFCount,Workflow -Descending $InstanceInfo = [ordered]@{} # Build an object with lots of data about each Instance, and the workflows running on its behalf ForEach ($Obj in $InstanceDetails) { $thisInstance = (Get-SCOMClassInstance -Id $Obj.ID ) $objHash = [Ordered]@{ WFCount = $obj.WFCount Status = $Status Agent = $ThisAgent InstanceID = $Obj.ID InstanceFullName = ($thisInstance.FullName) InstanceName = $thisInstance Class = $allClasses[($ThisInstance.LeastDerivedNonAbstractManagementPackClassId.Guid)] Workflows = [array]($obj.Workflow | ForEach-Object {$allWFTypes[$_] }) } Try { $InstanceInfo[$objHash.InstanceFullName] = $objHash } Catch { Write-Warning "$_" } } # Report #1 $rpt1 = ForEach ($key in @($InstanceInfo.Keys)) { $Key | Select-Object ` -Property @{Name='WFCount';Expression={$InstanceInfo[$key].WFCount}} ` ,@{Name='Status';Expression={$InstanceInfo[$key].Status}} ` ,@{Name='Agent';Expression={$InstanceInfo[$key].Agent}} ` ,@{Name='InstanceFullName';Expression={$InstanceInfo[$key].InstanceFullName}} ` ,@{Name='InstanceName';Expression={$InstanceInfo[$key].InstanceName}} ` ,@{Name='InstanceID';Expression={$InstanceInfo[$key].InstanceID}} ` ,@{Name='Class';Expression={$InstanceInfo[$key].Class}} ` ,@{Name='Workflows';Expression={$InstanceInfo[$key].Workflows}} ` } $Null = $arrReports.Add($rpt1) }#end FOREACH }#end PROCESS #---------------------------------------------------------------- END { Return $arrReports }#end END } #End Function Get-SCOMAgentWorkflowsReport ######################## END FUNCTIONS ################################ } #End BEGIN PROCESS { $Instance = $NULL Try { Write-Verbose "Get instances of Health Service class with name(s): $AgentName" $NotAvailable = $AllHS | Where-Object {($_.Path -in $AgentName) -AND ($_.IsAvailable -eq $false )} ForEach ($UnavailableAgent in $NotAvailable) { Write-Warning "UNAVAILABLE: $($UnavailableAgent.DisplayName)" } $Instance = $AllHS | Where-Object {($_.Path -in $AgentName ) -AND ($_.IsAvailable -eq $true )} If (-NOT $Instance) { Throw "Agents not found or unavailable:`n$($AgentName)`n" } } Catch { Write-Warning $_ Continue; } ForEach ($TaskName in $TaskNames) { $TaskWF = $NULL $TaskResult = $NULL Write-Verbose "Get SCOM task: $TaskName" $TaskWF = Get-SCOMTask -Name $TaskName Try { Write-Verbose "Starting the task now $(Get-Date) for [$($Instance.Count)] agents:" #$Instance.DisplayName | ForEach-Object {Write-Verbose $_} $Error.Clear() $thisTask = Start-SCOMTask -Instance $Instance -Task $TaskWF -TaskCredentials $NULL -Verbose:$([bool]($VerbosePreference -eq 'Continue')) -ErrorAction Stop $ThisTask | Where-Object {$_} | ForEach-Object { $NULL = $hashTasks[$TaskName].Add($_) } } Catch { Write-Error "Failed to start task: $TaskName. $_`nExiting. " Continue; } } #End ForEach TaskName } #end PROCESS END { $ScriptTimer = [System.Diagnostics.Stopwatch]::StartNew() ForEach ($TaskName in @($hashTasks.Keys)) { # Keep looping while tasks are still running (Scheduled, Queued, etc. (other than Failed/Succeeded)) Do { $secondsRemain = "{0:N0}" -F $($TimeoutSeconds - $ScriptTimer.Elapsed.TotalSeconds) Write-Verbose "`n$(Get-Date): Waiting for task(s) to complete. [$($secondsRemain)] seconds remain until timeout. Sleeping for $($WaitSeconds) seconds." Start-Sleep -Seconds $WaitSeconds If (-NOT $hashTasks[$TaskName].Id.Count) { Write-Warning "No tasks exist for workflow: $($TaskName)" Continue; } $Results = (Get-SCOMTaskResult -Id $hashTasks[$TaskName].Id) } While ((([regex]::matches(($Results.Status),'Succeeded').Count + [regex]::matches(($Results.Status),'Failed').Count) -LT ($Instance.Count)) -AND ($ScriptTimer.Elapsed.TotalSeconds -le $TimeoutSeconds) ) $ScriptTimer.Stop() # If some task is not Status=Succeeded, show report ForEach ($TaskResult in ($Results | Where-Object {$_.Status -notmatch "Succeeded" }) ) { $ThisInstance = $Instance | Where-object {$_.Id.Guid -match $TaskResult.TargetObjectId.Guid} Write-Warning "$($TaskName):$($ThisInstance.Path):$($TaskResult.Status)" } #end ForEach TaskResult If ($ScriptTimer.Elapsed.TotalSeconds -gt $TimeoutSeconds){ Write-Warning "Timer expired. Check task status manually. TaskID: [$($Task.Id.Guid)]. Exiting" } Else { Switch ($OutputType) { 'TasktResult' { $Results | Where-Object {$_} | ForEach-Object {$NULL = $AllResults.Add($_)} } 'Detailed' { # dot-sourced to potentially reduce multiple loading of the workflow cache in this function (. Get-SCOMAgentWorkflowsReport -Status $thisStatus[$TaskName] -TaskResult $Results -Verbose:$VerbosePreference ) | Where-Object {$_} | ForEach-Object {$_ | ForEach-Object {$NULL = $AllResults.Add($_)} } } } } } Return $AllResults } } |