Public/Export-SCOMOverrides.ps1
|
Function Export-SCOMOverrides { <# .Synopsis This script will export all SCOM overrides to various file formats. .DESCRIPTION Will export a generous amount of information about overrides. Output may be filtered by management pack and exported to html, csv, xml, and json file formats. .PARAMETER OutDir Directory where output files will be created. Folder will get created if it doesn't already exist. .PARAMETER ExportFormats File format. TypeInformation is excluded. "CSV","JSON","HTML","XML" .PARAMETER CSS Any custom CSS for the HTML report. This will be inserted into the <Head> tag of the document. See examples. .PARAMETER FilterMPNames This can be a comma-separated string of management pack names or it can be an array of single names or a combination of both. See examples. The report(s) will only include overrides that exist in these MPs. .PARAMETER SortBy Sort rows by column name. .PARAMETER SortOrder Ascending or descending based on SortBy parameter. .PARAMETER DateStampFile By default the output files will include a datestamp in the name. Specify $false to produce a consistent filename. See examples. .PARAMETER WriteToEventLog Will write debugging info to Opsman event log. .EXAMPLE Export-SCOMOverrides -OutDir C:\Reports -ExportFormats HTML -FilterMPNames 'URLGenie.OVERRIDES,OpsMgr.Self.Maintenance.Overrides','PortGenie.OVERRIDES','WindowsService.OVERRIDES' -Verbose This example demonstrates that you can supply a comma-separated list of MP Names or an array of names or both. .EXAMPLE Export-SCOMOverrides -SortBy ORMPName -SortOrder Ascending -DateStampFile:$false -Verbose .EXAMPLE #Example PS C:\> [string]$CSS = @' <style> table, th, td { border: 1px solid black; border-collapse: collapse; } th, td { padding: 5px; } td { text-align: center; } </style> '@ PS C:\> Export-SCOMOverrides -SortBy ORMPName -ExportFormats HTML -CSS $CSS -OutDir "C:\Reports" .NOTES Author: Tyson Paul Blog: https://monitoringguys.com/2019/11/12/scomhelper/ History: 2020.05.13 - First version .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Will output to std output or up to 4 different output file formats. #> [CmdletBinding(DefaultParameterSetName='Parameter Set 1', SupportsShouldProcess=$true, PositionalBinding=$false, HelpUri = 'https://monitoringguys.com/', ConfirmImpact='Medium')] Param ( # Folder for exported files (HTML and CSV) [Parameter(Mandatory=$true, ParameterSetName='FileExport', ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=0)] [string]$OutDir, # Export Formats: <html | csv | xml | json | all> [Parameter(Mandatory=$false, ParameterSetName='FileExport', ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=1)] [ValidateSet( "All", "CSV", "JSON", "HTML", "XML")] $ExportFormats , # Optional CSS for HTML file export. Will appear in <Head> section of output file. [Parameter(Mandatory=$false, ParameterSetName='FileExport', ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=2)] [string]$CSS = @' <style> table, th, td { border: 1px solid black; border-collapse: collapse; } th, td { padding: 3px; } td { text-align: left; } </style> '@ , # Comma-separated list of management pack Names or array of names (or both) for which to include overrides from only THESE MPs. Otherwise ALL ORs from ALL MPs will be exported. [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=3)] [string[]]$FilterMPNames = '', #Report rows will be sorted by this Workflow property [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=4)] [ValidateSet("ContextDN", "ContextInstanceDN", "ContextIsGroup", "Enforced", "ORMPName", "ORName", "Parameter", "Property", "Value", "Workflow", "WorkflowDN", "WorkflowMPName", "WorkflowType")] [string]$SortBy = "WorkflowDN", #Formatting of HTML export [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=5)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [ValidateSet("Ascending", "Descending")] [string]$SortOrder = 'Ascending', # Will export files with unique datestamp name. <true|false> [Parameter(Mandatory=$false, ParameterSetName='FileExport', ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=6)] [switch]$DateStampFile = $true, [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, Position=7)] [switch]$WriteToEventLog ) [int]$maxLogLength = 31000 #max chars to allow for event log messages [string]$ScriptName = 'Export-Overrides.ps1' $ScriptNameInstanceGUID = (New-Guid).Guid.Substring(((New-Guid).Guid.Length ) -6).ToUpper() [string]$whoami = whoami.exe ######################################################## Function LogIt { Param( [switch]$Display, [int]$EventID, [int]$Line, $maxLogLength=31000, [string]$msg = '<No message provided>', [bool]$Proceed, [int]$Type = 2 ) $output = @" Message: $msg ThisScriptInstanceGUID: $ScriptNameInstanceGUID ScriptLine: $Line Running As: $whoami This script: $ScriptName Any Errors: $Error "@ If ($Proceed) { $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 ) #Display output, appropriate for an agent task. } If ($Display ) { Write-Verbose $msg } } ############################################################################### Function Load-Cache { LogIt -msg "Building cache..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display # Cache all classes LogIt -msg "Getting all Classes..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $AllClasses = Get-SCOMClass LogIt -msg "$($AllClasses.Count) found." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $hashAllClasses = @{} $hashAllClassesID = @{} ForEach ($Class in $AllClasses) { $hashAllClasses.Add($Class.Name, $Class) $hashAllClassesID.Add($Class.ID.GUID,$Class) } # Cache all monitors LogIt -msg "Getting all Monitors..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $AllMonitors = Get-SCOMMonitor LogIt -msg "$($AllMonitors.Count) Monitors found." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $hashAllMonitors = @{} ForEach ($Mon in $AllMonitors) { $hashAllMonitors.Add($Mon.Id.Guid, $Mon) } # Cache all rules LogIt -msg "Getting all Rules..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $AllRules = Get-SCOMRule LogIt -msg "$($AllRules.Count) Rules found." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $hashAllRules = @{} ForEach ($Rule in $AllRules) { $hashAllRules.Add($Rule.Id.Guid, $Rule) } # Cache all discoveries LogIt -msg "Getting all Discoveries..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $AllDiscoveries = Get-SCOMDiscovery LogIt -msg "$($AllDiscoveries.Count) Discoveries found." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $hashAllDiscoveries = @{} ForEach ($Disc in $AllDiscoveries) { $hashAllDiscoveries.Add($Disc.Id.Guid, $Disc) } # Cache all groups LogIt -msg "Getting all Groups..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $AllGroups = Get-SCOMGroup LogIt -msg "$($AllGroups.Count) Groups found." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $hashAllGroups = @{} ForEach ($Group in $AllGroups) { $hashAllGroups.Add($Group.FullName, $Group) } } ######################################################## Function Load-MPs { Param( [ValidateSet("Sealed","Unsealed","All")] [string]$type="All" ) $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() If ($Reload -eq $true ){ $type = 'All'} switch ($type) { 'All' { LogIt -msg "Getting ALL MPs. This may take a minute..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $AllMPs = Get-SCOMManagementPack | Where-Object {$_.Name -notin $ExcludedMPs.Values} $hashAllMPs = @{} $AllMPs | ForEach-Object { $hashAllMPs.Add($_.Name,$_) } $tmpmsg = "$($AllMPs.Count ) MPs found..." } {$_ -in 'All','Sealed' } { LogIt -msg "Getting Sealed MPs. This may take a minute..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $SealedMPs = $AllMPs | Where-Object -FilterScript {$_.Sealed -eq $True } | Where-Object {$_.Name -notin $ExcludedMPs.Values} $hashSealedMPs = @{} $SealedMPs | ForEach-Object { $hashSealedMPs.Add($_.Name,$_) } $tmpmsg += " $($SealedMPs.Count ) Sealed MPs found..." } {$_ -in 'All','Unsealed' } { LogIt -msg "Getting unsealed MPs. This may take a minute..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $UnsealedMPs = $AllMPs | Where-Object -FilterScript {$_.Sealed -eq $False } | Where-Object {$_.Name -notin $ExcludedMPs.Values} $hashUnsealedMPs = @{} $UnsealedMPs | ForEach-Object { $hashUnsealedMPs.Add($_.Name,$_) } $tmpmsg += " $($UnsealedMPs.Count ) unsealed MPs found..." } Default {Write-Host "Default switch. Problem." -F Yellow} } [double]$elapsed = $stopwatch.Elapsed.TotalSeconds $stopwatch.Stop() LogIt -msg "$tmpmsg Operation took $($elapsed) seconds." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } ######################################################## Function Select-Overrides { Param( [Parameter( Mandatory=$false, ParameterSetName='1')] [System.Object[]]$MPs, [System.Object[]]$Override, [Switch]$silent ) [System.Collections.ArrayList]$arrayOR = @() If ([bool]$MPs) { LogIt -msg "`nGetting overrides from $($MPs.Count) unsealed MP(s). This may take a minute..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display # Return only the overrides from the previously selected MPs ForEach ($MP in $MPs) { Try{ $arrayOR += ( ($AllMPs | Where-Object { $_.Name -eq $MP.Name}).GetOverrides() ) }Catch{ Write-Host "No valid overrides found in MP: $($MP.Name)" } } } Else { LogIt -msg "`nGetting overrides from $($AllMPs.Count) Management Packs. This may take a minute..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display $arrayOR = $AllMPs | ForEach-Object {$_.GetOverrides()} } If ($arrayOR.Count -eq 0){ LogIt -msg "No valid overrides found in MP(s). To be valid for selection, ORs must not reference locally defined targets or workflows (in the same unsealed pack)." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } # Just set variable here. Function should be dot-sourced when called. $ORs = $arrayOR } ######################################################## Function Format-OR { Param( $ORs ) [System.Collections.ArrayList]$arrObject = @() [int]$i=0 LogIt -msg "Begin formatting ORs..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display # 'MonitorPropertyOverride', 'RulePropertyOverride', 'DiscoveryPropertyOverride' # 'MonitorConfigurationOverride', 'RuleConfigurationOverride', 'DiscoveryConfigurationOverride' # I'm going to have to do this the old-fashioned way ForEach ($item in $ORs) { $i++ #Write-Progress -Activity "Formatting Data" -status "$i of ($ORs.Count)" -percentComplete ($i / ($ORs.Count*100)) $Object = New-Object PSObject # Will address Diagnostic and Recovery overrides at a later time. Maybe. If ($item.XMLTag -match 'Diagnostic|Recovery' ) { Continue; } $Object | add-member Noteproperty ORName ([string]$item.Name) $Object | add-member Noteproperty ORMPName ([string]$item.Identifier.Domain[0]) # is Monitor If ([bool]$item.Monitor.Identifier.Path.Count) { $Object | add-member Noteproperty WorkflowMPName ($item.Monitor.Identifier.Domain[0].ToString()) $Object | add-member Noteproperty WorkflowType "Monitor" $Object | add-member Noteproperty Workflow ($item.Monitor.Identifier.Path[0].ToString()) #$Object | add-member Noteproperty WorkflowDN ((Get-SCOMMonitor -Id $item.Monitor.Id.Guid).DisplayName ) $Object | add-member Noteproperty WorkflowDN (($hashAllMonitors[$item.Monitor.Id.Guid]).DisplayName ) $Object | add-member Noteproperty WorkflowId ([string]$item.Monitor.Id.Guid) } # is Rule ElseIf (($item.Rule.Identifier.Path.Count)) { $Object | add-member Noteproperty WorkflowMPName ($item.Rule.Identifier.Domain[0].ToString()) $Object | add-member Noteproperty WorkflowType "Rule" $Object | add-member Noteproperty Workflow ($item.Rule.Identifier.Path[0].ToString() ) #$Object | add-member Noteproperty WorkflowDN ((Get-SCOMRule -Id $item.Rule.Id.Guid).DisplayName ) $Object | add-member Noteproperty WorkflowDN (($hashAllRules[$item.Rule.Id.Guid]).DisplayName ) $Object | add-member Noteproperty WorkflowId ([string]$item.Rule.Id.Guid) } # is Discovery ElseIf ([bool]($item.Discovery.Identifier.Path.Count)) { $Object | add-member Noteproperty WorkflowMPName ($item.Discovery.Identifier.Domain[0].ToString()) $Object | add-member Noteproperty WorkflowType "Discovery" $Object | add-member Noteproperty Workflow ($item.Discovery.Identifier.Path[0].ToString()) #$Object | add-member Noteproperty WorkflowDN ((Get-SCOMDiscovery -Id $item.Discovery.Id.Guid).DisplayName ) $Object | add-member Noteproperty WorkflowDN (($hashAllDiscoveries[$item.Discovery.Id.Guid]).DisplayName ) $Object | add-member Noteproperty WorkflowId ([string]$item.Discovery.Id.Guid) } Else { $Object | add-member Noteproperty Workflow "ERROR" $Object | add-member Noteproperty WorkflowDN "ERROR" $Object | add-member Noteproperty WorkflowType "ERROR" } # To be added at some point # Diagnostic Task # Recovery Task # Note: $item.Target.Identifier.Path[0] If ([bool]($item.Property.Count)) { $Object | add-member Noteproperty Property ($item.Property.ToString() ) $Object | add-member Noteproperty Parameter "" } Else { $Object | add-member Noteproperty Property "" $Object | add-member Noteproperty Parameter $item.Parameter } $Object | add-member Noteproperty Value $item.Value If ([bool]($hashAllClassesID[$item.Context.Id.Guid])){ $Object | add-member Noteproperty Context ( $hashAllClassesID[$item.Context.Id.Guid].Name ) $Object | add-member Noteproperty ContextID ($item.Context.Id.Guid.ToString()) $Object | add-member Noteproperty ContextDN ( $hashAllClassesID[$item.Context.Id.Guid].DisplayName ) If ( $hashAllGroups.ContainsKey($hashAllClassesID[$item.Context.Id.Guid].Name) ) { $Object | add-member Noteproperty ContextIsGroup 'True' } Else { $Object | add-member Noteproperty ContextIsGroup 'False' } } #If the Context/Target does not exist, then add the ORName to a dirty list Else { Try{ # Need to figure out what to do with this collection of duds. $StaleORNames.Add($item.Name,'Stale') } Catch { #typically it's a bad idea to have an empty Catch but it's fine in this situation } $Object | add-member Noteproperty Context "DOES NOT EXIST - STALE REFERENCE" $Object | add-member Noteproperty ContextID "" $Object | add-member Noteproperty ContextDN "" } If ([Bool]($item.ContextInstance.Count)) { $Object | add-member Noteproperty ContextInstanceID ($item.ContextInstance.Guid.ToString() ) If ([bool]($DN = (Get-SCOMClassInstance -Id $item.ContextInstance.Guid).DisplayName) ) { # This might prove to be very time consuming $Object | add-member Noteproperty ContextInstanceDN $DN } Else{ $Object | add-member Noteproperty ContextInstanceDN "" } } Else { $Object | add-member Noteproperty ContextInstanceID "" $Object | add-member Noteproperty ContextInstanceDN "" } $Object | add-member Noteproperty Enforced $item.Enforced $Object | add-member Noteproperty OR_MPVersion $item.Identifier.Version.ToString() $arrObject.Add($Object) | Out-Null } If ($Object) { Remove-Variable -Name Object -ErrorAction SilentlyContinue } LogIt -msg "Done formatting ORs." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display # Just set variable here. Function should be dot-sourced when called. $ORs_F = $arrObject } ######################################################## Function Verify-OutDir { Param ( [string]$OutDir ) If (-NOT (Test-Path -Path $OutDir -PathType Container )) { Try { New-Item -Path $OutDir -ItemType Directory -Force -ErrorAction Stop Return $true }Catch{ Write-Error "Unable to verify output directory." Return $false } } Else { Return (Test-Path -Path $OutDir -PathType Container ) } } ######################################################## Function __LINE__ { $MyInvocation.ScriptLineNumber } ######################################################## #==================================================================================== #====================================== MAIN ====================================== [int]$info=0 [int]$critical=1 [int]$warn=2 # This will help control output sort order command Switch ($SortOrder ) { 'Ascending' { [BOOL]$SortOrder = $false } 'Descending' { [BOOL]$SortOrder = $true } Default { [BOOL]$SortOrder = $false } } # These are MPs that shouldn't be tampered with or consulted. $ExcludedMPs = @{ 1 = 'Microsoft.SystemCenter.SecureReferenceOverride' 2 = 'Microsoft.SystemCenter.GlobalServiceMonitor.SecureReferenceOverride' 3 = 'Microsoft.SystemCenter.Notifications.Internal' 4 = 'Microsoft.SystemCenter.NetworkDiscovery.Internal' 5 = 'Microsoft.SystemCenter.Advisor.SecureReferenceOverride' } If ($DateStampFile){ [string]$FileName = "overrides_$(Get-Date -F yyyyMMdd_HHmmss)" } Else { [string]$FileName = "overrides" } If (-NOT (Verify-OutDir -OutDir $OutDir)) { LogIt -msg "Unable to verify export directory [$($OutDir)]. Please run this task with permissions to create the directory or specify a valid path. Exiting." -Type $critical -EventID 9995 -Proceed $true -Line $(__LINE__) -Display Exit } # Functions are dot-sourced (which is perfectly fine) because output statements in the functions are meant to be seen in agent task. # Output statements in functions will contaminate function return values. . Load-Cache . Load-MPs -type 'All' [System.Collections.ArrayList]$FilteredMPs = @() ForEach ($Item in $FilterMPNames) { ForEach ($Name in @($Item.Split(',').Split(';').Split(':')) ) { $null = $FilteredMPs.Add($hashAllMPs[$Name]) } } #. Load-MPs -type 'All' . Select-Overrides -MPs $FilteredMPs # Result will populate '$ORs_F' . Format-OR -ORs $ORs If ($ORs_F.Count) { $ORs_F = $ORs_F | Sort-Object -Property $SortBy -Descending:$SortOrder } If (-NOT $ExportFormats){ LogIt -msg "Returning overrides to std out." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display Return $ORs_F } Else { Switch ($ExportFormats) { {$_ -match 'all|csv'} { Try{ $ORs_F | Export-Csv -Path (Join-Path $OutDir "$($FileName).csv") -NoTypeInformation -Force -Encoding UTF8 LogIt -msg "Exporting overrides to: [$((Join-Path $OutDir "$($FileName).csv"))] ..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } Catch { LogIt -msg "FAILED Exporting overrides to: [$((Join-Path $OutDir "$($FileName).csv"))] ..." -Type $warn -EventID 9995 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } } {$_ -match 'all|html'} { Try{ $ORs_F | ConvertTo-Html -Head $css | Set-Content -Path (Join-Path $OutDir "$($FileName).html") -Encoding UTF8 -Force LogIt -msg "Exporting overrides to: [$((Join-Path $OutDir "$($FileName).html"))] ..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } Catch { LogIt -msg "FAILED Exporting overrides to: [$((Join-Path $OutDir "$($FileName).html"))] ..." -Type $warn -EventID 9995 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } } {$_ -match 'all|json'} { Try{ $ORs_F | ConvertTo-Json | Set-Content -Path (Join-Path $OutDir "$($FileName).json") -Force -Encoding UTF8 LogIt -msg "Exporting overrides to: [$((Join-Path $OutDir "$($FileName).json"))] ..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } Catch { LogIt -msg "FAILED Exporting overrides to: [$((Join-Path $OutDir "$($FileName).json"))] ..." -Type $warn -EventID 9995 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } } {$_ -match 'all|xml'} { Try{ $ORs_F | Export-Clixml -Path (Join-Path $OutDir "$($FileName).xml") -Force -Encoding UTF8 LogIt -msg "Exporting overrides to: [$((Join-Path $OutDir "$($FileName).xml"))] ..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } Catch { LogIt -msg "FAILED Exporting overrides to: [$((Join-Path $OutDir "$($FileName).xml"))] ..." -Type $warn -EventID 9995 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } } default { LogIt -msg "FAILED Exporting overrides! Something is wrong with `$ExportFormats [$($ExportFormats)]" -Type $critical -EventID 9995 -Proceed $true -Line $(__LINE__) -Display } }#end switch } LogIt -msg "Script end ..." -Type $info -EventID 9990 -Proceed $WriteToEventLog -Line $(__LINE__) -Display } |