Start-SCOMOverrideTool.ps1
Function Start-SCOMOverrideTool { <# .NOTES Script: SCOMOverrideTool.ps1 Author: Tyson Paul ( https://monitoringguys.com/2019/11/12/scomhelper/ ) Version History: 2022.06.20.1729 - v1.02.11 - Updated a few Gridview prompts to control multi vs single output mode. (-OutputMode Single) 2021.07.08.1510 - v1.02.10 - Fixed destination MP param for Enable-Workflow. 2020.08.24.1545 - v1.02.09 - Removed dependency check/exit. Oops. 2020.08.11 - v1.02.08. Removed dependency on OpsMgrExtended module. Baked Tao's functions into this module. 2020.07.09 - v1.02.07. Added 'New-BatchDir' function. Added logic to dump MP.References to file upon MP.Verify() failure in RemoveObsoleteMPRefTask. 2020.06.18 - v1.02.06. Moved Load-Cache (renamed to Load-SCOMCache) to main module private functions area. 2020.02.28 - v1.02.05. Fixed temp directory creation/verification. Updated blog links. 2020.02.19 - v1.02.04. Fixed case-sensitive true/false for override "Enabled" property. This was breaking the EffectiveConfig report. 2019.11.XX - Fixed override name excessive length issue. New name will get truncated at 254 chars. 2019.08.12 - Fixed mgmt group connection issue. Fixed misc New-xxoverride functions to accept WorfklowID instead of WorkflowName 2019.07.09 - Fixed problem with identifying multiple overrides on a single workflow. 2019.06.28 - Fixed another flaw in enumerating overrides. Will correctly identify inherited ORs as well. 2019.06.27 - Fixed flaw in enumerating overrides. Will now display all applicable overrides related to a workflow (targeted at Class, not Instance of Group) 2019.05.28 - Improved Connect-SDK. Improved New-MP. Numbers and periods will now appear correctly in MP Name. 2019.05.22 - Fixed New-MP naming to allow digits 0-9 2019.03.22 - Added enable/disable (+ a few other functions) 2018.01.19 - Initial version, moved overrides only .SYNOPSIS This script will allow the user to manipulate SCOM overrides and unsealed management packs. See the Help menu for more info. .LINK https://monitoringguys.com/2019/11/12/scomhelper/ #> # Load private functions . (Join-Path $PSScriptRoot Private.ps1) . Import-SCOMPowerShellModule $Version = "1.02.11" $Title = "------ SCOM Override Tool (v$($Version)) ------" # These are MPs that shouldn't be tampered with $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' } ######################################################## Function Backup-MP { Param( [switch]$All, [Alias("MPs")] [System.Object[]]$MPName, [string]$Stamp, [String]$rootDir = $BackupDir #root dir ) <# #Create subdir for today with timestamped name $today = (Get-Date -F yyyyMMdd) $todaySubDir = (Join-Path $rootDir $today) If (-not (Test-Path -Path $todaySubDir -PathType Container)) { LogIt -OutDir $rootDir -msg "Creating BU directory for today [$today]..." -Display If (-not(. My-NewItem -type Directory -Path $todaySubDir)) { DoExit -msg "Fatal error with task: [$msg] Any Error Data: $Error " } } #Create a batch level backup dir with unique name $batchSubDir = (Join-Path $todaySubDir "$($Stamp)") If (-not (Test-Path -Path $batchSubDir -PathType Container)) { LogIt -OutDir $rootDir -msg "Creating BU directory [$batchSubDir] for this batch#: $Stamp" If (-not(. My-NewItem -Type Directory -Path $batchSubDir)) { DoExit -msg "Fatal error with task: [$msg] Any Error Data: $Error " } } #> If ($All) { LogIt -msg "`nBackup of all unsealed MPs initiated." -OutDir $rootDir . Load-MPs -type All $MPName = ($UnsealedMPs.Name) $error.Clear() . New-BatchDir -rootdir $BackupDir -Label "_ALL_UNSEALED" . Backup-MP -rootDir $BackupDir -MPs $MPName } ElseIf ([bool]$MPName) { LogIt -msg "Getting unsealed MP(s) to backup. This may take a minute..." -OutDir $rootDir ForEach ($Name in $MPName) { . New-BatchDir -rootdir $BackupDir LogIt -msg "Backing up MP [$($mpName)] to [$($batchSubDir)] ..." -OutDir $rootDir -Display $error.Clear() ($UnsealedMPs | Where-Object { $_.Name -eq $Name}) | Export-SCOMManagementPack -Path $batchSubDir If ($error){ $msg = "`nError backing up MP [$($Name)] to [$batchSubDir]. Please acknowledge" LogIt -msg $msg -F Yellow -B Red -OutDir $rootDir Read-Host -Prompt $msg } } } Else { Write-Host "`nSomething went wrong in the Backup-MP function. No MPs specified.`n" -F Red -B Yellow } } ######################################################## <# Used to unselect source or destination MPs. This can also be accomplished by choosing the menu option to "Select" either source or destination MP, but clicking "Cancel" in the Gridview window. #> Function Clear-Selection { Param( [Parameter(Mandatory=$false)] [ValidateSet("all","dest","cancel","override","source","WF")] [string]$Type ) Switch ($Type) { 'source' {$SourceMP = $null; $SelectedOR_F = $null;Return} 'dest' {$DestMP = $null; Return} 'override' {$SelectedOR_F = $null; Return} 'WF' {$SelectedWFs = $null; $SelectedWFType = $null; Return} 'all' {$SourceMP = $null ;$DestMP = $null; $SelectedOR_F = $null; $SelectedWFs = $null; $SelectedWFType = $null; Return} 'cancel' {Return} Default { $TempMenu = @{} #hash [System.Collections.ArrayList]$TempMenuItems = @() $TempMenuItems.Add(@{DN="Clear Source MP(s)";Action='Clear-Selection -Type "Source"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Clear Destination MP";Action='Clear-Selection -Type "Dest"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Clear Overrides";Action='Clear-Selection -Type "Override"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Clear Workflows";Action='Clear-Selection -Type "WF"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Clear all Selections";Action='Clear-Selection -Type "All"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Cancel (go back to main menu)";Action='Clear-Selection -Type "Cancel"';F="Gray"} ) | Out-Null [int]$j=1 $TempMenuItems | ForEach-Object { $TempMenu.$j = $_ $j++ } $Choice = Get-MenuChoice -Menu $TempMenu # Execute the menu selection Invoke-Expression -Command ". $($TempMenu.($Choice).Action)" }#Default }#Switch }#function ######################################################## Function Connect-SDK { Param( [Parameter(Mandatory=$false)] [ValidateSet("with","without","cancel")] [string]$Type ) If ( ($MG.IsActive) -OR ([bool](Get-SCOMManagementGroupConnection)) ) { LogIt -msg "Management Group connection exists with user [$($env:USERDOMAIN)\$($env:username)]. Server: [$((Get-SCOMManagementGroupConnection).ManagementServerName)], ManagementGroupName: [$((Get-SCOMManagementGroupConnection).ManagementGroupName)], IsConnected: [$((Get-SCOMManagementGroupConnection).IsActive)]" -Display -OutDir $BackupDir -F Gray Write-Host "" } Else{ LogIt -msg "No Management Group connection exists. " -Display -OutDir $BackupDir -F Gray } Switch ($Type) { 'with' { # Remove any previously existing connection If ( ($MG.IsActive) -OR ([bool](Get-SCOMManagementGroupConnection)) ) { LogIt -msg "Removing Management Group connection! Server: [$((Get-SCOMManagementGroupConnection).ManagementServerName)], ManagementGroupName: [$((Get-SCOMManagementGroupConnection).ManagementGroupName)]..." -Display -OutDir $BackupDir -F Cyan Get-SCOMManagementGroupConnection | Remove-SCOMManagementGroupConnection -ErrorAction SilentlyContinue Remove-Variable MG -ErrorAction SilentlyContinue } $SDK = (Read-Host -Prompt "Management Server Name?") $UserName = (Read-Host -Prompt "Username? [domain\user]") $Password = (Read-Host -Prompt "Password?" -AsSecureString) #TEST LogIt -msg "Connecting to Management Group via Server:[$($SDK)] , User:[$($UserName)] ..." -Display -OutDir $BackupDir $Credential = New-Object System.Management.Automation.PSCredential($UserName,$Password) $MG = New-SCOMManagementGroupConnection -ComputerName $SDK -Credential $Credential -PassThru #TEST #LogIt -msg "Connecting to Management Group via Server:[$($SDK)] , User:[$($UserName)] ..." -Display -OutDir $BackupDir #$MG = Connect-OMManagementGroup -SDK $SDK -Username $UserName -Password $Password If ($MG.IsActive){ LogIt -msg "Management Group connection created successfully with user [$($env:USERDOMAIN)\$($env:username)]. Server: [$($MG.ManagementServerName)], ManagementGroupName: [$($MG.ManagementGroupName)], IsActive: [$($MG.IsActive)]" -Display -OutDir $BackupDir -F Cyan Write-Host "" . Load-MPs -type 'All' } Else{ LogIt -msg "Connection to [$($SDK)] with user [$($UserName)] failed. No Management Group connection exists. " -Display -OutDir $BackupDir -F Gray } Return } 'without' { # Remove any previously existing connection If ( ($MG.IsActive) -OR ([bool](Get-SCOMManagementGroupConnection)) ) { LogIt -msg "Removing Management Group connection! Server: [$($MG.ManagementServerName)], ManagementGroupName: [$($MG.ManagementGroupName)], IsActive: [$($MG.IsActive)]" -Display -OutDir $BackupDir -F Cyan Get-SCOMManagementGroupConnection | Remove-SCOMManagementGroupConnection -ErrorAction SilentlyContinue Remove-Variable MG -ErrorAction SilentlyContinue } $SDK = $env:COMPUTERNAME LogIt -msg "Connecting to Management Group via SDK $SDK ..." -Display -OutDir $BackupDir $MG = New-SCOMManagementGroupConnection -ComputerName $SDK -PassThru If ($MG.IsActive){ LogIt -msg "Management Group connection created successfully with user [$($env:USERDOMAIN)\$($env:username)]. Server: [$($MG.ManagementServerName)], ManagementGroupName: [$($MG.ManagementGroupName)], IsActive: [$($MG.IsActive)]" -Display -OutDir $BackupDir -F Cyan Write-Host "" . Load-MPs -type 'All' } Else{ LogIt -msg "Connection to [$($SDK)] with user [$($env:USERDOMAIN)\$($env:username)] failed. No Management Group connection exists. " -Display -OutDir $BackupDir -F Gray } Return } 'cancel' {Return} Default { $TempMenu = @{} #hash [System.Collections.ArrayList]$TempMenuItems = @() $TempMenuItems.Add(@{DN="Connect to localmachine as current user";Action='Connect-SDK -Type "Without"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Connect to specific SDK with credentials";Action='Connect-SDK -Type "With"';F="Green"} ) | Out-Null If ( ($MG.IsActive) -OR ([bool](Get-SCOMManagementGroupConnection)) ) { $existingConnMsg = "Keep existing connection with user [$($env:USERDOMAIN)\$($env:username)]. Server: [$($MG.ManagementServerName)], ManagementGroupName: [$($MG.ManagementGroupName)], IsActive: [$($MG.IsActive)]" } Else { $existingConnMsg = "Go back to main menu" } $TempMenuItems.Add(@{DN="Cancel ($($existingConnMsg))";Action='Connect-SDK -Type "Cancel"';F="Gray"} ) | Out-Null [int]$j=1 $TempMenuItems | ForEach-Object { $TempMenu.$j = $_ $j++ } $Choice = Get-MenuChoice -Menu $TempMenu # Execute the menu selection Invoke-Expression -Command ". $($TempMenu.($Choice).Action)" }#Default }#Switch # Refresh the MPs now that changes have been made #$Reload = $False }#function ######################################################## Function Convert-UTCtoLocal { param( [parameter(Mandatory=$true)] [String] $UTCTime ) $strCurrentTimeZone = (Get-WmiObject win32_timezone).StandardName $TZ = [System.TimeZoneInfo]::FindSystemTimeZoneById($strCurrentTimeZone) $LocalTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($UTCTime, $TZ) Return $LocalTime } ######################################################## Function Create-NewORFirstThenRemoveOld { <# This is the preferred order of operation; Create a similar duplicate override in the destination MP FIRST, then delete the old one. #> LogIt -OutDir $BackupDir -msg "Attempting creation of new override [$($Params.OverrideName)] (first) in Destination MP: [$($Params.MPName)]." If ($OR_F.Property){ $NewORResult = (New-OMPropertyOverride2 @Params) } ElseIf ($OR_F.Parameter){ $NewORResult = (New-OMConfigurationOverride2 @Params) } If ($NewORResult) { $details = "[" ForEach ($key in $Params.keys ){ $details += "($($key)=$($Params.$key));" } $details = $details.TrimEnd(';') $details += "]" LogIt -OutDir $BackupDir -msg "Success creating override. Details: $($details)" LogIt -OutDir $BackupDir -msg "`nAttempting to delete original override: [$($OR_F.ORName)] from: [$($OR_F.ORMPName)]..." #$RemoveORResult = (Remove-OMOverride -SDK $SDK -OverrideName $OR_F.ORName ) $RemoveORResult = Remove-Override -OR_F $OR_F If ($RemoveORResult){ LogIt -Display -OutDir $BackupDir -msg "Success moving override. New Name: [$($Params.OverrideName)], Destination MP: [$($Params.MPName)]" -F 'Green' } Else { $ISFailureDetected = $true LogIt -Display -OutDir $BackupDir -msg "`n FAILURE! New override [$($Params.OverrideName)] created in Destination MP: [$($Params.MPName)] but unable to delete original override: [$($OR_F.ORName)] from: [$($OR_F.ORMPName)]" -F Yellow -B Red Do{ Write-Host "Abort operation (entire batch) ..." -F Red -B Yellow -NoNewline $abort = Read-Host -Prompt " Y/N?" }While($abort -notmatch 'y|n') } } Else { $ISFailureDetected = $true LogIt -OutDir -Display $BackupDir -msg "`n FAILURE! New override [$($Params.OverrideName)] NOT created in Destination MP: [$($Params.MPName)]. Original override: [$($OR_F.ORName)] remains in : [$($OR_F.ORMPName)]" -F Yellow -B Red Do{ Write-Host "`nAbort operation (entire batch) ..." -F Red -B Yellow -NoNewline $abort = Read-Host -Prompt " Y/N?" }While($abort -notmatch 'y|n') } $NewORResult = $false $RemoveORResult = $false } ######################################################## Function Delete-Overrides { Param ( [System.Object[]]$SelectedOR_F ) $ISFailureDetected = $false If ( -NOT($SelectedOR_F.Count)) { Write-Host "You must first select override(s) for this operation! Go do that first." -F Red -B Yellow Return } Do{ Write-Host "CONFIRM: Delete $($SelectedOR_F.Count) overrides? ' " -F Red -B Yellow $temp = Read-Host -Prompt "Y/N?" }While($temp -notmatch 'y|n') $batchstamp = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."} LogIt -msg "Delete override(s) sequence initiated... Batch# [$($batchstamp)]" -OutDir $BackupDir -Display LogIt -msg "Backup SourceMPs initiated..." -OutDir $BackupDir # Backup relevant source MPs $RelevantSourceMPNames = ($SelectedOR_F.ORMPName | Select-Object -Unique) . Backup-MP -MPs $RelevantSourceMPNames -stamp $batchstamp -RootDir $BackupDir # Add all batch info to logfile $thisSourceMP = "" $RelevantSourceMPNames | ForEach { $thisSourceMP = "$thisSourceMP" + " [$($_)]," } $thisSourceMP = $thisSourceMP.TrimEnd(',') LogIt -msg "Batch#:$($batchstamp); Source MP(s):$($thisSourceMP)" -OutDir $BackupDir $thisOR = "" $SelectedOR_F | ForEach-Object -Process { $thisOR = "$thisOR" + " [$($_)]," } $thisOR = $thisOR.TrimEnd(',') LogIt -msg "Batch#:$($batchstamp); Overrides to Delete:$($thisOR)" -OutDir $BackupDir [System.Collections.ArrayList]$SuccessList = @() ForEach ($OR_F in $SelectedOR_F) { $tempResult = Remove-Override -OR_F $OR_F If (-not ([bool]$tempResult)){ LogIt -OutDir $BackupDir -msg "`nFailure removing override: [$($OR_F.ORName)]!" -F Red -B Yellow -Display } Else{ $SuccessList.Add($OR_F.WorkflowId) | Out-Null LogIt -OutDir $BackupDir -msg "`nSuccess removing override: [$($OR_F.ORName)]!" -F Green -Display } } #This should allow only failed ORs to remain in the list of currently selected ORs $SelectedOR_F = @($SelectedOR_F | Where-Object {$_.WorkflowId -notin $SuccessList}) } ######################################################## Function Delete-MPs{ LogIt -Display -OutDir $BackupDir -msg "Clearing any currently selected MPs..." . Clear-Selection -Type "All" . Select-MPs -Type "Source" Remove-Variable -Name 'Temp' -ErrorAction SilentlyContinue If (-NOT $SourceMP.Count){ Write-Host "No management packs selected. " Return } Do{ Write-Host "CONFIRM: Delete $($SourceMP.Count) management pack(s)? " -F Red -B Yellow $Temp = Read-Host -Prompt "Y/N?" }While($Temp -notmatch 'y|n') If ($Temp -like "y"){ $batchstamp = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."} LogIt -msg "Delete MP(s) sequence initiated... Batch# [$($batchstamp)]" -OutDir $BackupDir -Display LogIt -msg "Backup SourceMPs initiated..." -OutDir $BackupDir # Backup source MPs . Backup-MP -MPs $SourceMP.Name -stamp $batchstamp -RootDir $BackupDir Remove-MP -MP $SourceMP } # Refresh the MPs now that changes have been made $Reload = $True } ######################################################## Function Disable-Workflow { Param( $SelectedWFs, $SourceMP, $DestMP ) [bool]$CreateNewOR = $false Do{ Write-Host "CONFIRM: Disable $($SelectedWFs.Count) $($SelectedWFType)(s) " -F Red -B Yellow $temp = Read-Host -Prompt "Y/N?" }While($temp -notmatch 'y|yes|n|no') If ($temp -match 'n|no') { Logit -Display -OutDir $BackupDir -msg 'Disable-Workflow aborted by user.' -F yellow Return } If ([bool]$DestMP.Name) { $DestMPName = $DestMP.Name } #Filter out only valid WFs which are not already enabled [System.Collections.ArrayList]$validSelectedWFs = @() ForEach ($WF in $SelectedWFs) { If ( (($WF.OverrideExists) -AND (-NOT($WF.EffectiveOverrideValue))) -OR ((-NOT($WF.OverrideExists)) -AND (-NOT($WF.'Enabled(by default)'))) ) { LogIt -Display -OutDir $BackupDir -msg "Disable-Workflow: WF [$($WF.Name)] already disabled for Target class [$($WF.TargetName)]. Invalid for this operation. Skipping..." -F Yellow Continue; } $validSelectedWFs.Add($WF) | Out-Null } # If no valid workflows are eligible to 'disable', simply return from function. If ($validSelectedWFs.Count -eq 0 ){ LogIt -Display -OutDir $BackupDir -msg "Disable-Workflow: No valid workflows selected. No action taken." -F Yellow Return } $batchstamp = Get-Date -Format o | ForEach-Object -Process {$_ -replace ":", "."} LogIt -msg "Disable-Workflow sequence initiated. Batch# [$($batchstamp)]" -OutDir $BackupDir -Display # Add all batch info to logfile [string]$thisWFName = "" $validSelectedWFs | ForEach-Object -Process { $thisWFName = "$thisWFName" + " [$($_.Name);'$($_.DisplayName)']," } $thisWFName = $thisWFName.TrimEnd(',') LogIt -msg "Batch#:$($batchstamp); WFs to Disable:$($thisWFName)" -OutDir $BackupDir [System.Collections.ArrayList]$overrideList_F = @() # Build splat ForEach ($WF in $validSelectedWFs) { Switch ($SelectedWFType) { 'Rule' { $OverrideType ='RulePropertyOverride' break; } 'Monitor' { $OverrideType ='MonitorPropertyOverride' break; } 'Discovery' { $OverrideType ='DiscoveryPropertyOverride' break; } }#end Switch # Detect existing "Enabled" override. # If override exists, and workflow is directly enabled by said override, delete override. If (($WF.OverrideExists) ` -AND ($hashORResult[$WF.Id].PropertyName -eq 'Enabled') ` -AND ($hashORResult[$WF.Id].Value -eq 'true' ) ` #-AND (($hashORResult[$WF.Id].TargetName -eq $WF.TargetName) -OR ($hashORResult[$WF.Id].TargetName -eq $WF.Name) ) ) ){ Logit -OutDir $BackupDir -msg "Override exists for WF: [$($WF.Name),$($WF.Id)], to effectively disable WF, must delete override: [$($hashORResult[$WF.Id].Override.Value.LocalOverride.Name)]." # This will tee up the single override to be stored in the correct format/variable ($SelectedOR_F) . Select-Overrides -Override $hashORResult[$WF.Id].Override.Value.LocalOverride -silent # Build list of ORs to be deleted in batch process below $overrideList_F.Add($SelectedOR_F) | Out-Null #This is an unusual case, but possible nonetheless # If WF is enabled by default and also override exists to set Enabled:True, force remove of override AND also creation of new override to Enabled:false If ($WF.'Enabled(by default)'){ [bool]$CreateNewOR = $true If (-NOT [bool]$DestMP.Name){ $DestMPName = $SelectedOR_F.ORMPName } } } #Only attempt backup Dest MP if one is actually selected and changes will be made to it. If (([bool]$validSelectedWFs -OR [bool]$overrideList_F) -AND [bool]$DestMPName) { LogIt -msg "Backup Destination MP..." -OutDir $BackupDir # Backup single Destination MP Backup-MP -MPs $DestMPName -stamp $batchstamp -RootDir $BackupDir } If ( (-NOT($WF.OverrideExists)) -OR ($CreateNewOR) ) { $tempORName = (New-OverrideName -WF $WF -Type $OverrideType) $Params = @{} $Params.Add('SDK',((Get-SCOMManagementGroupConnection).ManagementServerName) ) $Params.Add('MPName',$DestMPName) $Params.Add('OverrideType',$OverrideType) $Params.Add('OverrideName',$tempORName) $Params.Add('OverrideWorkflowID',$WF.ID) $Params.Add('Target',$WF.TargetName) $Params.Add('OverrideProperty','Enabled') $Params.Add('OverrideValue','false') #This value is case sensitive. Learned this the hard way. $Params.Add('Enforced',$false) . LogIt -OutDir $BackupDir -msg "`nAttempting to create new override: [$($Params.OverrideName)] in Destination MP: [$($Params.MPName)]" -Display $NewORResult = New-OMPropertyOverride2 @Params $details = "[" ForEach ($key in $Params.keys ){ $details += "($($key)=$($Params.$key));" } $details = $details.TrimEnd(';') $details += "]" If ($NewORResult){ . LogIt -Display -OutDir $BackupDir -msg "Success Creating override. Details: $($details)" -F Green } Else { LogIt -Display -OutDir $BackupDir -msg "`nFAILED override creation. Details: [$($details)]" -F DarkYellow } } }#end ForEach WF # Backup relevant source MPs If ([bool]$overrideList_F.Count){ $RelevantSourceMPNames = ($overrideList_F.ORMPName | Select-Object -Unique) . Backup-MP -MPs $RelevantSourceMPNames -stamp $batchstamp -RootDir $BackupDir # batch removal of overrides, effectively disable the workflows for their targeted classes ForEach ($deleteOR_F in $overrideList_F) { Remove-Override -OR_F $deleteOR_F } } Remove-Variable -Name SelectedOR_F -ErrorAction SilentlyContinue }#end Function ######################################################## Function DoExit { Param ( [string]$msg = "Exiting..." ) Write-Host $msg LogIt -Display -OutDir $BackupDir -msg $msg #Read-Host -Prompt "Any key to exit..." $script:DoExit = $true } ######################################################## Function Enable-Workflow { Param( $SelectedWFs, $SourceMP, $DestMP ) [bool]$CreateNewOR = $false Do{ Write-Host "CONFIRM: Enable $($SelectedWFs.Count) $($SelectedWFType)(s) " -F Red -B Yellow $temp = Read-Host -Prompt "Y/N?" }While($temp -notmatch 'y|yes|n|no') If ($temp -match 'n|no') { Logit -Display -OutDir $BackupDir -msg 'Enable-Workflow aborted by user.' -F yellow Return } If ([bool]$DestMP.Name) { $DestMPName = $DestMP.Name } #Filter out only valid WFs which are not already enabled [System.Collections.ArrayList]$validSelectedWFs = @() ForEach ($WF in $SelectedWFs) { If ( (($WF.OverrideExists) -AND ($WF.EffectiveOverrideValue)) -OR ((-NOT($WF.OverrideExists)) -AND ($WF.'Enabled(by default)')) ) { LogIt -Display -OutDir $BackupDir -msg "Enable-Workflow: WF [$($WF.Name)] already enabled for Target class [$($WF.TargetName)]. Invalid for this operation. Skipping..." -F Yellow Continue; } $validSelectedWFs.Add($WF) | Out-Null } # If no valid workflows are eligible to 'enable', simply return from function. If ($validSelectedWFs.Count -eq 0 ){ LogIt -Display -OutDir $BackupDir -msg "Enable-Workflow: No valid workflows selected. No action taken." -F Yellow Return } # Add all batch info to logfile [string]$thisWFName = "" $validSelectedWFs | ForEach-Object -Process { $thisWFName = "$thisWFName" + " [$($_.Name);'$($_.DisplayName)']," } $thisWFName = $thisWFName.TrimEnd(',') LogIt -msg "Batch#:$($batchstamp); WFs to Enable:$($thisWFName)" -OutDir $BackupDir [System.Collections.ArrayList]$overrideList_F = @() # Build splat ForEach ($WF in $validSelectedWFs) { Switch ($SelectedWFType) { 'Rule' { $OverrideType ='RulePropertyOverride' Break; } 'Monitor' { $OverrideType ='MonitorPropertyOverride' Break; } 'Discovery' { $OverrideType ='DiscoveryPropertyOverride' Break; } }#end Switch # Detect existing "Disabled" override. # If override exists, and workflow is directly disabled by said override, delete override. If (($WF.OverrideExists) ` -AND ($hashORResult[$WF.Id].PropertyName -eq 'Enabled') ` -AND ($hashORResult[$WF.Id].Value -eq 'False' ) ` #-AND ($hashORResult[$WF.Id].TargetName -eq $WF.TargetName) ){ Logit -OutDir $BackupDir -msg "Override exists for WF: [$($WF.Name),$($WF.Id)], to effectively enable WF, must delete override: [$($hashORResult[$WF.Id].Override.Value.LocalOverride.Name)]." # This will tee up the single override to be stored in the correct format/variable ($SelectedOR_F) . Select-Overrides -Override $hashORResult[$WF.Id].Override.Value.LocalOverride -silent # Build list of ORs to be deleted in batch process below $overrideList_F.Add($SelectedOR_F) | Out-Null #This is an unusual case, but possible nonetheless # If WF is disabled by default and also override exists to set Enabled:False, force remove of override AND also creation of new override to Enabled:true If (-NOT($WF.'Enabled(by default)')){ [bool]$CreateNewOR = $true If (-NOT [bool]$DestMP.Name){ $DestMPName = $SelectedOR_F.ORMPName } } } #Only attempt backup Dest MP if one is actually selected and changes will be made to it. If (([bool]$validSelectedWFs -OR [bool]$overrideList_F) -AND [bool]$DestMPName) { LogIt -msg "Backup Destination MP..." -OutDir $BackupDir # Backup single Destination MP Backup-MP -MPs $DestMPName -stamp $batchstamp -RootDir $BackupDir } If ( (-NOT($WF.OverrideExists)) -OR ($CreateNewOR) ){ # Else, create "Enabled" override for eligible workflows. $tempORName = (New-OverrideName -WF $WF -Type $OverrideType) $Params = @{} $Params.Add('SDK',$SDK) $Params.Add('MPName',$DestMPName) $Params.Add('OverrideType',$OverrideType) $Params.Add('OverrideName',$tempORName) $Params.Add('OverrideWorkflowID',$WF.ID) $Params.Add('Target',$WF.TargetName) $Params.Add('OverrideProperty','Enabled') $Params.Add('OverrideValue','true') $Params.Add('Enforced',$false) LogIt -OutDir $BackupDir -msg "`nAttempting to create new override: [$($Params.OverrideName)] in Destination MP: [$($Params.MPName)]" -Display $NewORResult = New-OMPropertyOverride2 @Params $details = "[" ForEach ($key in $Params.keys ){ $details += "($($key)=$($Params.$key));" } $details = $details.TrimEnd(';') $details += "]" If ($NewORResult){ LogIt -Display -OutDir $BackupDir -msg "Success Creating override. Details: $($details)" -F Green } Else { LogIt -Display -OutDir $BackupDir -msg "`nFAILED override creation. Details: [$($details)]" -F DarkYellow } } }#end ForEach WF # Delete necessary overrides If ([bool]$overrideList_F.Count){ # Backup relevant source MPs $RelevantSourceMPNames = ($overrideList_F.ORMPName | Select-Object -Unique) LogIt -msg "Backup relevant override MP(s)..." -OutDir $BackupDir . Backup-MP -MPs $RelevantSourceMPNames -stamp $batchstamp -RootDir $BackupDir # Batch removal of overrides, effectively enabling the workflows for their targeted classes ForEach ($deleteOR_F in $overrideList_F) { Remove-Override -OR_F $deleteOR_F } } }#end Function ######################################################## Function Format-OR { Param( $ORs ) [System.Collections.ArrayList]$arrObject = @() [int]$i=0 # '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 DisplayName ([string]$item.DisplayName) $Object | add-member Noteproperty ORName ([string]$item.Name) $Object | add-member Noteproperty ORMPName ([string]$item.Identifier.Domain[0]) #Original WF ID #$Object | add-member Noteproperty Id ([string]$item.Id.GUID) 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 WorkflowId ([string]$item.Monitor.Id.Guid) } 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 WorkflowId ([string]$item.Rule.Id.Guid) } 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 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 # $item.Target.Identifier.Path[0] # Recovery Task 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 $Object | add-member Noteproperty ContextID ($item.Context.Id.Guid.ToString()) # Test if Context instance exists, if so get the FullName If ([bool]($thisInstance = Get-SCOMClass -Id $item.Context.Id.Guid)){ $Object | add-member Noteproperty Context ( $thisinstance.Name ) $Object | add-member Noteproperty ContextDN ( $thisinstance.DisplayName ) } #If the Context/Target does not exist, then add the ORName to a dirty list Else { Try{ $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 ContextDN "DOES NOT EXIST - STALE REFERENCE" } 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 MPVersion $item.Identifier.Version.ToString() $arrObject.Add($Object) | Out-Null } If ($Object) { Remove-Variable -Name Object -ErrorAction SilentlyContinue } Return $arrObject } ######################################################## Function Get-MenuChoice { Param( [System.Object]$Menu ) Do { [int]$tempChoice = Show-Menu -Menu $Menu }While ( ($tempChoice -notmatch '^[1-9]$|^[1-9][0-9]$') -or ($tempChoice -gt ($Menu.Count )) ) Return $tempChoice } ######################################################## Function Show-Help { Param( ) '' '' Write-Host $Title -F White $summary = @" This tool can enable or disable a workflow by either deleting an existing override or creating a new override to accomplish the desired effect. (The Enable and Disable actions only affect the workflow Target class.) This tool can also move (or relocate) an existing override to a different unsealed management pack. This tool doesn't technically "move" the override. It deletes the original override and creates a new override in the destination MP. Only valid candidates/overrides will be presented for selection. A valid candidate is one that does not reference or depend on any other objects (workflows, targets) contained within the same original unsealed MP. Example: Overrides that might affect custom groups in the same MP would be ineligible. For all operations that modify or change any management packs, all activities/actions are written to the logfile and unsealed packs are backed up prior to any changes. For Enable/Disable operation: Typically the procedure would be as follows: 1) Select any number of "source" unsealed packs. Only workflows (Rules/Monitors/Discoveries) from these packs will be presented for selection. 2) Select a "destination" unsealed pack into which to store any *necessary (new) override(s). 3) Select action type: Enable or Disable 4) Select 'type' of workflow(s) you wish to enable/disable: Rule, Monitor, or Override 5) Select any quantity of workflows. 6) Confirm to initiate the enable/disable action *Note: a) In some cases an override might already exist which must be deleted to result in the desired effect (enable/disable). b) Enable/Disable actions only affect the workflow Target class. For a "Move" operation: Typically the procedure would be as follows: 1) Select any number of "source" unsealed packs from which to select overrides. 2) Select any number of eligible overrides from that set of source packs. 3) Select a "destination" unsealed pack into which to move the override(s). 4) Initiate the move. You may select one or more 'source' MPs to limit the results shown during the override selection process. This is effectively a way to filter the overrides. Otherwise ALL valid overrides from ALL unsealed MPs will be presented in the selection window. See menu details below. *This tool does not currently move any overrides related to SecureReference, Diagnostic, or Recovery objects. "@ Write-Host $summary -F Cyan Write-Host "" Write-Host " - Select Source MP(s) for Filtered View" -F Green $summary = @" You may select one or more 'source' MPs to limit the results shown during the override selection process. This is effectively a way to filter the overrides. Otherwise ALL valid overrides from ALL unsealed MPs will be presented in the selection window. "@ Write-Host $summary -F Gray Write-Host " - Select Destination MP" -F Green $summary = @" Select the unsealed MP in which you would like to store the new/relocated overrides. Only 1 unsealed MP may be selected at a time. "@ Write-Host $summary -F Gray Write-Host " - Clear Selected Item(s)" -F Green $summary = @" Any management pack or overrides that have been selected may be UNselected. A new MP or override may be selected at anytime without clearing previous selections. "@ Write-Host $summary -F Gray Write-Host " - Select Override(s)" -F Green $summary = @" Select one or more overrides which you intend to move or delete. "@ Write-Host $summary -F Gray Write-Host " - Show Selected Items" -F Green $summary = @" This will allow you to view, in detail, the currently selected MP(s),overrides, or workflows. "@ Write-Host $summary -F Gray Write-Host " - Move Override(s)" -F Green $summary = @" Move one or more overrides from one unsealed MP to another. *Note: This doesn't technically "move" the override(s). It creates a new override in the destination MP and deletes the original override. Only eligible candidates will be available for selection. "@ Write-Host $summary -F Gray Write-Host " - Delete Override(s)" -F Green $summary = @" Delete currently selected overrides. "@ Write-Host $summary -F Gray Write-Host " - Delete MP(s)" -F Green $summary = @" Delete one or more currently selected 'Source" management packs. "@ Write-Host $summary -F Gray Write-Host " - Remove Obsolete References from Unsealed MP(s)" -F Green $summary = @" This will identify and remove obsolete references/dependencies from ALL unsealed packs. (except dependencies to the core packs) "@ Write-Host $summary -F Gray Write-Host " - Create New Override MP" -F Green $summary = @" This will allow you to create a new unsealed, empty management pack. You will be prompted to select a base (sealed) pack for which a suggested MP name will be provided. This is only a suggestion. You can cancel/skip the selection window and enter your own MP name. If you enter a name it will be validated for acceptable characters. "@ Write-Host $summary -F Gray Write-Host " - Reload all MPs" -F Green $summary = @" This will reload all MPs into cache. Sometimes after creating a new MP it will take a few minutes before the MP will become available from the SDK. "@ Write-Host $summary -F Gray Write-Host " - Backup All Unsealed MPs" -F Green $summary = @" Will backup all MPs to the default directory: `t$($BackupDir) *NOTE: there are a few 'special' unsealed packs that do NOT get included/modified by any operations related to this tool. Excluded MPs: $($ExcludedMPs.Values | Out-String ) "@ Write-Host $summary -F Gray Write-Host " - SDK Connection Settings" -F Green $summary = @" Will prompt you to connect to a SCOM mgmt server. "@ Write-Host $summary -F Gray '' '' Read-Host "Press any key to resume..." } ######################################################## 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 -Display -msg "Getting ALL MPs. This may take a minute..." $AllMPs = Get-SCOMManagementPack | Where-Object {$_.Name -notin $ExcludedMPs.Values} $hashAllMPs = @{} $AllMPs | ForEach-Object { Try { $hashAllMPs.Add($_.Name,$_) } Catch [System.ArgumentException] { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "Line:[$($line)], Warning: $_. `nSometimes this happens when unnecessary 'Language' packs have been imported." -F DarkYellow } Catch { $line = $_.InvocationInfo.ScriptLineNumber "Line:[$($line)], Error was: $_" } } $tmpmsg = "$($AllMPs.Count ) MPs found..." } {$_ -in 'All','Sealed' } { LogIt -Display -msg "Getting Sealed MPs. This may take a minute..." $SealedMPs = $AllMPs | Where-Object -FilterScript {$_.Sealed -eq $True } | Where-Object {$_.Name -notin $ExcludedMPs.Values} $hashSealedMPs = @{} $SealedMPs | ForEach-Object { Try { $hashSealedMPs.Add($_.Name,$_) } Catch [System.ArgumentException] { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "Line:[$($line)], Warning: $_. `nSometimes this happens when unnecessary 'Language' packs have been imported." -F DarkYellow } Catch { $line = $_.InvocationInfo.ScriptLineNumber "Line:[$($line)], Error was: $_" } } $tmpmsg += " $($SealedMPs.Count ) Sealed MPs found..." } {$_ -in 'All','Unsealed' } { LogIt -Display -msg "Getting unsealed MPs. This may take a minute..." $UnsealedMPs = $AllMPs | Where-Object -FilterScript {$_.Sealed -eq $False } | Where-Object {$_.Name -notin $ExcludedMPs.Values} $hashUnsealedMPs = @{} $UnsealedMPs | ForEach-Object { Try { $hashUnsealedMPs.Add($_.Name,$_) } Catch [System.ArgumentException] { $line = $_.InvocationInfo.ScriptLineNumber Write-Host "Line:[$($line)], Warning: $_. `nSometimes this happens when unnecessary 'Language' packs have been imported." -F DarkYellow } Catch { $line = $_.InvocationInfo.ScriptLineNumber "Line:[$($line)], Error was: $_" } } $tmpmsg += " $($UnsealedMPs.Count ) unsealed MPs found..." } Default {Write-Host "Default switch. Problem." -F Yellow} } [double]$elapsed = $stopwatch.Elapsed.TotalSeconds $stopwatch.Stop() LogIt -Display -msg "$tmpmsg Operation took $($elapsed) seconds." } ######################################################## Function Load-Workflows { Param( [ValidateSet("Monitor","Rule","Discovery")] [string]$WFType, $SourceMP ) Remove-Variable -Name 'Monitors','Rules','Discoveries','SelectedWFType','WFs' -ErrorAction SilentlyContinue switch ($WFType) { 'Monitor' { $WFs = Get-SCOMMonitor -ManagementPack $SourceMP If ([bool]$WFs.Count) { $SelectedWFType = 'Monitor' } LogIt -Display -msg "Getting Monitors..." -OutDir $BackupDir } 'Rule' { $WFs = Get-SCOMRule -ManagementPack $SourceMP If ([bool]$WFs.Count) { $SelectedWFType = 'Rule' } LogIt -Display -msg "Getting Rules..." -OutDir $BackupDir } 'Discovery' { $WFs = Get-SCOMDiscovery -ManagementPack $SourceMP If ([bool]$WFs.Count) { $SelectedWFType = 'Discovery' } LogIt -Display -msg "Getting Discoveries..." -OutDir $BackupDir } }#end switch } ######################################################## Function LogIt { Param( [String]$B, #backgroundcolor [Switch]$Display, #display the msg with Write-Host [String]$F = 'Gray', #foregroundcolor [Parameter( Mandatory=$true, ValueFromPipeline=$true, ValueFromRemainingArguments=$false, Position=0, ParameterSetName='Parameter Set 1')] [String]$msg, [String]$OutDir = $BackupDir ) If ($Display){ If ([bool]$B){ Write-Host $msg -ForegroundColor $F -BackgroundColor $B }Else{ Write-Host $msg -ForegroundColor $F } } $today = (Get-Date -F yyyyMMdd) $me = whoami $now = (Get-Date -Format o) $data = New-Object PSObject $LogFileName = $thisScriptName If (-not($LogFileName)) { $LogFileName = "Start-SCOMOverrideTool.ps1" } $LogFileName = ($LogFileName.Replace('.ps1',"") + "_$($today).csv") $LogFilePath = (Join-Path $OutDir $LogFileName) # Attempt to create temp folder if needed If (-NOT (Test-Path -Path $OutDir -PathType Container)){ Write-Host "Attempting to create temp folder: $OutDir" -F Gray Try{ New-Item -ItemType Directory -Path $OutDir -Force -ErrorAction Stop }Catch{ Write-Host "Failed to create temp directory. " -F Yellow -B Red } } # Verify logfile exists If (-not(Test-Path -Path $LogFilePath -PathType Leaf)) { Write-Host "Creating log file: [$($LogFilePath)]" -F Gray Try{ New-Item -Type File -Path $LogFilePath -ErrorAction SilentlyContinue | Out-Null }Catch { Write-Host "`n`nError! Unable to create/access log file: [$($LogFilePath)]. " -F Yellow -B Red Write-Host "Run this tool as administrator." -F Yellow -B Red Write-Host "If that doesn't work, create this folder path manually: [$(Split-Path $LogFilePath -Parent)]" -F Yellow -B Red $null = Read-Host "Press any key to exit." Exit } } $data | Add-Member -MemberType NoteProperty -Name TimeStamp $now $data | Add-Member -MemberType NoteProperty -Name User $me $data | Add-Member -MemberType NoteProperty -Name Message $msg $error.Clear() $data | Export-Csv -Path $LogFilePath -Append -NoTypeInformation If ($error){ Write-Host "$($Error[0])" -F Yellow -B Red } } ######################################################## Function Move-Override { Param( ) $ISFailureDetected = $false If ( (-not($DestMP)) -or (-not($SelectedOR_F)) ) { Write-Host "You must first select both: override(s) AND destination MP for this operation!" -F Red -B Yellow Return } Do{ Write-Host "CONFIRM: Move $($SelectedOR_F.Count) overrides to MP: '$($DestMP.DisplayName)' [ $($DestMP.Name) ] " -F Red -B Yellow $temp = Read-Host -Prompt "Y/N?" }While($temp -notmatch 'y|n') #$sig = (Get-Date -Format o) $batchstamp = Get-Date -Format o | ForEach-Object -Process {$_ -replace ":", "."} LogIt -msg "Override move sequence initiated. Batch# [$($batchstamp)]" -OutDir $BackupDir -Display Write-Host "Will delete original overrides, then recreate them in destination MP..." LogIt -msg "Backup SourceMPs..." -OutDir $BackupDir # Backup relevant source MPs $RelevantSourceMPNames = ($SelectedOR_F.ORMPName | Select-Object -Unique) . Backup-MP -MPs $RelevantSourceMPNames -stamp $batchstamp -RootDir $BackupDir LogIt -msg "Backup Destination MP..." -OutDir $BackupDir # Backup single Destination MP . Backup-MP -MPs $DestMP.Name -stamp $batchstamp -RootDir $BackupDir # Add all batch info to logfile $thisSourceMP = "" $RelevantSourceMPNames | ForEach-Object -Process { $thisSourceMP = "$thisSourceMP" + " [$($_)]," } $thisSourceMP = $thisSourceMP.TrimEnd(',') LogIt -msg "Batch#:$($batchstamp); Source MP(s):$($thisSourceMP)" -OutDir $BackupDir LogIt -msg "Batch#:$($batchstamp); Destination MP: [$($DestMP.Name)]" -OutDir $BackupDir $thisOR = "" $SelectedOR_F | ForEach-Object -Process { $thisOR = "$thisOR" + " [$($_)]," } $thisOR = $thisOR.TrimEnd(',') LogIt -msg "Batch#:$($batchstamp); Overrides to Move:$($thisOR)" -OutDir $BackupDir -Display #Attempt to start the move operation(s). Iterate through all selected overrides #region ForEach ForEach ($OR_F in $SelectedOR_F) { $RemoveORResult = . Remove-Override -OR_F $OR_F $Params = . New-OverrideSplat -OR_F $OR_F If ($RemoveORResult) { LogIt -OutDir $BackupDir -msg "`nAttempting to create new override: [$($Params.OverrideName)] in Destination MP: [$($Params.MPName)]" If ($OR_F.Property){ $NewORResult = (New-OMPropertyOverride2 @Params) } ElseIf ($OR_F.Parameter){ $NewORResult = (New-OMConfigurationOverride2 @Params) } $details = "[" ForEach ($key in $Params.keys ){ $details += "($($key)=$($Params.$key));" } $details = $details.TrimEnd(';') $details += "]" If ($NewORResult){ LogIt -Display -OutDir $BackupDir -msg "Success moving override. Details: $($details)" -F Green } Else { $ISFailureDetected = $true $msg = "`n FAILURE! Original override [$($OR_F.ORName)] deleted from [$($OR_F.ORMPName)] " $msg += "but unable to create new override: [$($Params.OverrideName)], Destination MP: [$($Params.MPName)]. " $msg += "This will probably require you to manually recreate the override in the destination MP or in the original MP. See logfile for details." LogIt -Display -OutDir $BackupDir -msg $msg -F Yellow -B Red LogIt -Display -OutDir $BackupDir -msg "`nOriginal override details: $($OR_F) , `nAttempted Destination MP: [$($Params.MPName)]." -F Yellow LogIt -Display -OutDir $BackupDir -msg "`nAttempted (FAILED) override creation details: [$($details)]" -F DarkYellow Do{ Write-Host "`nAbort operation (entire batch) ..." -F Red -B Yellow -NoNewline $abort = Read-Host -Prompt " Y/N?" }While($abort -notmatch 'y|n') } } Else { $ISFailureDetected = $true LogIt -Display -OutDir $BackupDir -msg "`n FAILURE! Unable to remove original override. Original override: [$($OR_F.ORName)] remains in MP: [$($OR_F.ORMPName)]. Skipping this item." -F Yellow -B Red Do{ Write-Host "`nAbort operation (entire batch) ..." -F Red -B Yellow -NoNewline $abort = Read-Host -Prompt " Y/N?" }While($abort -notmatch 'y|n') } $NewORResult = $false $RemoveORResult = $false If ($abort -match 'y' ) { LogIt -OutDir $BackupDir -msg "User is aborting operation/batch..." -F Yellow Break; } } #endregion ForEach If ($ISFailureDetected){ Write-Host "`nFailures Detected! See logfile: " -F Red -NoNewline Write-Host "[" -F Gray -NoNewline; Write-Host $LogFilePath -F Yellow -NoNewline; Write-Host "]`n" -F Gray Explorer.exe $LogFilePath Write-Host "Backed up MPs located here:" -F Red -NoNewline Write-Host "[" -F Gray -NoNewline; Write-Host $batchSubDir -F Yellow -NoNewline Write-Host "]" -F Gray } . Clear-Selection -Type 'override' # Refresh the MPs now that changes have been made $Reload = $True } ######################################################## Function My-NewItem { Param( [string]$path, [ValidateSet("Directory", "Leaf")] [string]$type ) switch ($type) { 'Directory' { New-Item -Type Directory -Path $path -ErrorAction SilentlyContinue | Out-Null If (-not(Test-Path -Path $path -PathType Container)) { LogIt -Display -OutDir $BackupDir -msg "`n`nError! Unable to create/access directory: [$($path)]. " -F Yellow -B Red Return $false } Else { LogIt -OutDir $BackupDir -msg "Directory [$($path)] exists." Return $true } } 'Leaf' { New-Item -Type Leaf -Path $path -ErrorAction SilentlyContinue | Out-Null If (-not(Test-Path -Path $path -PathType Leaf)) { LogIt -Display -OutDir $BackupDir -msg "`n`nError! Unable to create/access file: [$($path)]. " -F Yellow -B Red Return $false } Else { LogIt -OutDir $BackupDir -msg "`n`nFile [$($path)] exists." Return $true } } Default {} } } ######################################################## Function New-OMConfigurationOverride2 { <# .NOTES Original Author: Tao Yang ( https://blog.tyang.org/ , https://blog.tyang.org/2018/04/18/opsmgrextended-powershell-module-is-now-on-github-and-psgallery/ ) Modified by Tyson Paul to accept $WorkflowId (instead of workflow Name). Multiple identical workflow names can exist, unfortunately. .Synopsis Create a configuration override in OpsMgr. .Description Create a configuration (parameter) override in OpsMgr using OpsMgr SDK. A boolean value $true will be returned if the override creation has been successful, otherwise, a boolean value of $false is returned if any there are any errors occurred during the creation process. .Parameter -SDKConnection OpsMgr SDK Connection object (SMA connection or hash table). .Parameter -SDK Management Server name .Parameter -UserName Alternative user name to connect to the management group (optional). .Parameter -Password Alternative password to connect to the management group (optional). .Parameter -MPName Name for the unsealed MP of which the override is going to stored. .Parameter -OverrideType Type of configuration override. Possible values are: 'MonitorConfigurationOverride', 'RuleConfigurationOverride', 'DiscoveryConfigurationOverride', 'DiagnosticConfigurationOverride' and 'RecoveryConfigurationOverride' .Parameter -OverrideName Override name .Parameter -OverrideDisplayName Override Display Name .Parameter -OverrideWorkflow The workflow (rule, monitor or discovery) to be overriden. .Parameter -Target Override Target (context) .Parameter -ContextInstance Override context instance can be a monitoring object or a group that the override should apply to. .Parameter -OverrideParamter The configuration parameter of the workflow which is going to be overriden. .Parameter -OverrideValue The new value of that the override is going set for the override property .Parameter -Enforce (boolean) Specify if the override is enforced. .Parameter -IncreaseMPVersion (boolean) Increase MP version by 0.0.0.1 (Increase revision by 1). .Example # Connect to OpsMgr management group via management server "OpsMgrMS01" and then create a configuration override with the following properties: Management Server: "OpsMgrMS01" Username: "domain\SCOM.Admin" Password "password1234" Management Pack Name: "TYANG.Lab.Override" Override Type: RuleConfigurationOverride Override Name: Test.Performance.Collection.Rule.Interval.Override Override Display Name: Test Performance Collection Rule Interval Override Override Workflow: "Test.Performance.Collection.Rule" Target: "Test.Instance.Group" Override parameter: intervalseconds Override Value: 600 Enforced: False $Password = ConvertTo-SecureString -AsPlainText "password1234" -force New-OMConfigurationOverride -SDK "OpsMgrMS01" -Username "domain\SCOM.Admin" -Password $Password -MPName "TYANG.Lab.Override" -OverrideType RuleConfigurationOverride -OverrideName Test.Performance.Collection.Rule.Interval.Override -OverrideDisplayName "Test Performance Collection Rule Interval Override" -OverrideWorkflow "Test.Performance.Collection.Rule" -Target "Test.Instance.Group" -OverrideParameter IntervalSeconds -OverrideValue 600 .Example # Connect to OpsMgr management group via management server "OpsMgrMS01" and then create a configuration override with the following properties: OpsMgrSDK Connection (Used in SMA): "OpsMgrSDK_TYANG" Management Pack Name: "TYANG.Lab.Override" Override Type: MonitorConfigurationOverride Override Name: Test.Performance.Monitor.Interval.Override Override Workflow: "Test.Performance.Monitor" Target: "Microsoft.Windows.Computer" Context Instance: "6015832d-affa-7a01-10cb-37a37699d904" Override Property: IntervalSeconds Override Value: 600 Enforced: True Increase Management Pack version by 0.0.0.1 $SDKConnection = Get-AutomationConnection -Name OpsMgrSDK_TYANG New-OMConfigurationOverride -SDKConnection $SDKConnection -MPName "TYANG.Lab.Override" -OverrideType MonitorConfigurationOverride -OverrideName Test.Performance.Monitor.Interval.Override -OverrideWorkflow "Test.Performance.Monitor" -Target "Microsoft.Windows.Computer" -ContextInstance "6015832d-affa-7a01-10cb-37a37699d904" -OverrideParameter IntervalSeconds -OverrideValue 600 -Enforced $true -IncreaseMPVersion $true #> [CmdletBinding()] PARAM ( [Parameter(ParameterSetName='SMAConnection',Mandatory=$true,HelpMessage='Please specify the SMA Connection object')][Alias('Connection','c')][System.Object]$SDKConnection, [Parameter(ParameterSetName='IndividualParameter',Mandatory=$true,HelpMessage='Please enter the Management Server name')][Alias('DAS','Server','s')][System.String]$SDK, [Parameter(ParameterSetName='IndividualParameter',Mandatory=$false,HelpMessage='Please enter the user name to connect to the OpsMgr management group')][Alias('u')][System.String]$Username = $null, [Parameter(ParameterSetName='IndividualParameter',Mandatory=$false,HelpMessage='Please enter the password to connect to the OpsMgr management group')][Alias('p')][SecureString]$Password = $null, [Parameter(Mandatory=$true,HelpMessage='Please enter management pack name')][System.String]$MPName, [Parameter(Mandatory=$true,HelpMessage='Please enter Override Type')][ValidateSet('MonitorConfigurationOverride', 'RuleConfigurationOverride', 'DiscoveryConfigurationOverride', 'DiagnosticConfigurationOverride', 'RecoveryConfigurationOverride')][System.String]$OverrideType, [Parameter(Mandatory=$true,HelpMessage='Please enter override name')][System.String]$OverrideName, [Parameter(Mandatory=$false,HelpMessage='Please enter override display name')][System.String]$OverrideDisplayName, [Parameter(Mandatory=$true,HelpMessage='Please enter override workflow name')][System.String]$OverrideWorkflowID, [Parameter(Mandatory=$true,HelpMessage='Please enter override target')][Alias('target')][System.String]$OverrideTarget, [Parameter(Mandatory=$false,HelpMessage='Please enter override context instance ID')][Alias('Instance')][System.String]$ContextInstance = $null, [Parameter(Mandatory=$true,HelpMessage='Please enter override configuration parameter')][System.String]$OverrideParameter, [Parameter(Mandatory=$true,HelpMessage='Please enter override value')]$OverrideValue, [Parameter(Mandatory=$false,HelpMessage='Set override to Enforced')][System.Boolean]$Enforced=$false, [Parameter(Mandatory=$false,HelpMessage='Increase MP version by 0.0.0.1')][System.Boolean]$IncreaseMPVersion=$false ) #Connect to MG If ($SDKConnection) { Write-Verbose "Connecting to Management Group via SDK $($SDKConnection.ComputerName)`..." $MG = Connect-OMManagementGroup -SDKConnection $SDKConnection $SDK = $SDKConnection.ComputerName $Username = $SDKConnection.Username $Password= ConvertTo-SecureString -AsPlainText $SDKConnection.Password -force } else { Write-Verbose "Connecting to Management Group via SDK $SDK`..." If ($Username -and $Password) { $MG = Connect-OMManagementGroup -SDK $SDK -UserName $Username -Password $Password } else { $MG = Connect-OMManagementGroup -SDK $SDK } } #Get the unsealed MP $strMPquery = "Name = '$MPName'" $mpCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackCriteria($strMPquery) $MP = $MG.GetManagementPacks($mpCriteria)[0] If ($MP) { #MP found, now check if it is sealed If ($MP.sealed) { Write-Error 'Unable to save to the management pack specified. It is sealed. Please specify an unsealed MP.' return $false } } else { Write-Error 'The management pack specified cannot be found. please make sure the correct name is specified.' return $false } #Get the monitoring Class (Override target / context) $strMCQuery = "Name = '$OverrideTarget'" $mcCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringClassCriteria($strMCQuery) $MonitoringClass = $MG.GetMonitoringClasses($mcCriteria)[0] if (!$MonitoringClass) { Write-Error 'The override target (context) specified cannot be found. please make sure the correct name is specified.' return $false } <# Disabled. Name ends up being much too long. Cannot exceed 256 chars. #Make sure override name has the MP name as prefix If ($OverrideName -notmatch $MPName) { $OverrideName= "$MPName.$OverrideName" } #> If ([bool]$OverrideWorkflowId){ $criteria = "Id='$OverrideWorkflowId'" } Else{ $criteria = "Name='$OverrideWorkflow'" } #Create new override Switch ($OverrideType) { "MonitorConfigurationOverride" { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitorCriteria($criteria) $Workflow = $MG.GetMonitors($WorkflowCriteria)[0] } "RuleConfigurationOverride" { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringRuleCriteria($criteria) $Workflow = $MG.GetMonitoringRules($WorkflowCriteria)[0] } 'DiscoveryConfigurationOverride' { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringDiscoveryCriteria($criteria) $Workflow = $MG.GetMonitoringDiscoveries($WorkflowCriteria)[0] } 'DiagnosticConfigurationOverride' { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringDiagnosticCriteria($criteria) $Workflow = $MG.GetMonitoringDiagnostics($WorkflowCriteria)[0] } 'RecoveryConfigurationOverride' { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringRecoveryCriteria($criteria) $Workflow = $MG.GetMonitoringRecoveries($WorkflowCriteria)[0] } } $objOverrideParameter = $Workflow.GetOverrideableParameters() | Where-Object{$_.Name -ieq $OverrideParameter} If (!$objOverrideParameter) { Write-Error "Unable to find an overrideable parameter with name '$OverrideParameter' for $($Workflow.Name)!" return $false } else { Write-Verbose "The override parameter '$OverrideParameter' is valid." } Switch ($OverrideType) { "MonitorConfigurationOverride" { Write-Verbose 'Creating Monitor Configuration Override...' #Find the override paramters $objParameters = $Workflow.GetOverrideableParameters() } "RuleConfigurationOverride" { Write-Verbose 'Creating Rule Configuration Override...' #Find the override paramters by module $objParameters = $Workflow.GetOverrideableParametersByModule() } 'DiscoveryConfigurationOverride' { Write-Verbose 'Creating Discovery Configuration Override...' #Find the override paramter by module $objParameters = $Workflow.GetOverrideableParametersByModule() } 'DiagnosticConfigurationOverride' { Write-Verbose 'Creating Diagnostic Configuration Override...' #Find the override paramter by module $objParameters = $Workflow.GetOverrideableParametersByModule() } 'RecoveryConfigurationOverride' { Write-Verbose 'Creating Recovery Configuration Override...' #Find the override paramter by module $objParameters = $Workflow.GetOverrideableParametersByModule() } } #Get all overrideable parameters and its module based on the name $arrModules = New-Object System.Collections.ArrayList Foreach ($module in $objParameters.keys) { foreach ($parameter in $objParameters.$module) { if ($parameter.name -ieq $OverrideParameter) { $objParameter = New-Object psobject Add-Member -InputObject $objParameter -MemberType NoteProperty -Name Module -Value $module.name Add-Member -InputObject $objParameter -MemberType NoteProperty -Name Parameter -Value $parameter.name [System.Void]$arrModules.Add($objParameter) } } } #If there are more than one parameter returned, exit with error If ($arrModules.Count -gt 1) { Write-Error "There are $($arrModules.count) overrideable parameters with the name $OverrideParameter1. The script will not continue. To ensure the correct overrideable parameter is selected, please create the override manually." Return $false } #Create Override Switch ($OverrideType) { "MonitorConfigurationOverride" { $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackMonitorConfigurationOverride($MP, $OverrideName) $Override.Monitor = $Workflow } "RuleConfigurationOverride" { $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackRuleConfigurationOverride($MP, $OverrideName) $Override.Rule = $Workflow $Override.Module = $arrModules[0].Module } 'DiscoveryConfigurationOverride' { $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackDiscoveryConfigurationOverride($MP, $OverrideName) $Override.Discovery = $Workflow $Override.Module = $arrModules[0].Module } 'DiagnosticConfigurationOverride' { $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackDiagnosticConfigurationOverride($MP, $OverrideName) $Override.Diagnostic = $Workflow $Override.Module = $arrModules[0].Module } 'RecoveryConfigurationOverride' { $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackRecoveryConfigurationOverride($MP, $OverrideName) $Override.Recovery = $Workflow $Override.Module = $arrModules[0].Module } } $Override.Parameter = $OverrideParameter $Override.Value = $OverrideValue $Override.Context = $MonitoringClass If ($ContextInstance.Length -gt 0) { $Override.ContextInstance = $ContextInstance } If ($OverrideDisplayName) { $Override.DisplayName = $OverrideDisplayName } Else { Write-Verbose 'Override DisplayName was not specified. The override will not have a display name.' } If ($Enforced) { $Override.Enforced = $true } #Increase MP version If ($IncreaseMPVersion) { $CurrentVersion = $MP.Version.Tostring() $vIncrement = $CurrentVersion.Split('.') $vIncrement[$vIncrement.Length - 1] = ([System.Int32]::Parse($vIncrement[$vIncrement.Length - 1]) + 1).ToString() $NewVersion = ([System.String]::Join('.', $vIncrement)) $MP.Version = $NewVersion } #Verify and save the MP Try { $MP.verify() $MP.AcceptChanges() $Result = $true Write-Verbose "Override '$OverrideName' successfully created in Management Pack '$MPName'($($MP.Version))." } Catch { $Result = $false Write-Error "Unable to create override $OverrideName in management pack $MPName." $MP.RejectChanges() } $MG.Dispose() $Result } ######################################################## Function New-OMPropertyOverride2 { <# .NOTES Original Author: Tao Yang ( https://blog.tyang.org/ , https://blog.tyang.org/2018/04/18/opsmgrextended-powershell-module-is-now-on-github-and-psgallery/ ) Modified by Tyson Paul to accept $WorkflowId (instead of workflow Name). Multiple identical workflow names can exist, unfortunately. .Synopsis Create a property override in OpsMgr. .Description Create a property override in OpsMgr using OpsMgr SDK. A boolean value $true will be returned if the override creation has been successful, otherwise, a boolean value of $false is returned if any there are any errors occurred during the creation process. .Parameter -SDKConnection OpsMgr SDK Connection object (SMA connection or hash table). .Parameter -SDK Management Server name .Parameter -UserName Alternative user name to connect to the management group (optional). .Parameter -Password Alternative password to connect to the management group (optional). .Parameter -MPName Name for the unsealed MP of which the override is going to stored. .Parameter -OverrideType type of the override (MonitorPropertyOverride; RulePropertyOverride, DiscoveryPropertyOverride, DiagnosticPropertyOverride or RecoveryPropertyOverride) .Parameter -OverrideName Override name .Parameter -OverrideDisplayName Override Display Name .Parameter -OverrideType Type of property override. Possible values are: 'MonitorPropertyOverride', 'RulePropertyOverride', 'DiscoveryPropertyOverride', 'DiagnosticPropertyOverride' and 'RecoveryPropertyOverride' .Parameter -OverrideWorkflow The workflow (rule, monitor or discovery) to be overriden. .Parameter -OverrideTarget Override Target (context) .Parameter -ContextInstance Override context instance can be a monitoring object or a group that the override should apply to. .Parameter -OverrideProperty The property of the workflow which is going to be overriden. .Parameter -OverrideValue The new value of that the override is going set for the override property .Parameter -Enforce (boolean) Specify if the override is enforced. .Parameter -IncreaseMPVersion (boolean) Increase MP version by 0.0.0.1 (Increase revision by 1). .Example # Connect to OpsMgr management group via management server "OpsMgrMS01" and then create a property override with the following properties: Management Server: "OpsMgrMS01" Username: "domain\SCOM.Admin" Password "password1234" Management Pack Name: "TYANG.Lab.Override" Override Type: RulePropertyOverride Override Name: Disable.Test.Event.Collection.Rule.Override Override Display Name: Disable Test Event Collection Rule Override Override Workflow: "Test.Event.Collection.Rule" Target: "Test.Instance.Group" Override Property: Enabled Override Value: False Enforced: False $Password = ConvertTo-SecureString -AsPlainText "password1234" -force New-OMPropertyOverride -SDK "OpsMgrMS01" -Username "domain\SCOM.Admin" -Password $Password -MPName "TYANG.Lab.Override" -OverrideType 'RulePropertyOverride' -OverrideName Disable.Test.Event.Collection.Rule.Override -OverrideDisplayName "Disable Test Event Collection Rule Override" -OverrideWorkflow "Test.Event.Collection.Rule" -Target "Test.Instance.Group" -OverrideProperty Enabled -OverrideValue False .Example # Connect to OpsMgr management group via management server "OpsMgrMS01" and then create a property override with the following properties: OpsMgrSDK Connection (Used in SMA): "OpsMgrSDK_TYANG" Management Pack Name: "TYANG.Lab.Override" Override Type: MonitorPropertyOverride Override Name: Disable.Test.Service.Monitor.Override Override Workflow: "Test.Service.Monitor" Target: "Microsoft.Windows.Computer" Context Instance: "6015832d-affa-7a01-10cb-37a37699d904" Override Property: Enabled Override Value: False Enforced: True Increase Management Pack version by 0.0.0.1 $SDKConnection = Get-AutomationConnection -Name OpsMgrSDK_TYANG New-OMPropertyOverride -SDKConnection $SDKConnection -MPName "TYANG.Lab.Override" -OverrideType MonitorPropertyOverride -OverrideName Disable.Test.Service.Monitor.Override -OverrideWorkflow "Test.Service.Monitor" -Target "Microsoft.Windows.Computer" -ContextInstance "6015832d-affa-7a01-10cb-37a37699d904" -OverrideProperty Enabled -OverrideValue False -Enforced $true -IncreaseMPVersion $true #> [CmdletBinding()] PARAM ( [Parameter(ParameterSetName='SMAConnection',Mandatory=$true,HelpMessage='Please specify the SMA Connection object')][Alias('Connection','c')][System.Object]$SDKConnection, [Parameter(ParameterSetName='IndividualParameter',Mandatory=$true,HelpMessage='Please enter the Management Server name')][Alias('DAS','Server','s')][System.String]$SDK, [Parameter(ParameterSetName='IndividualParameter',Mandatory=$false,HelpMessage='Please enter the user name to connect to the OpsMgr management group')][Alias('u')][System.String]$Username = $null, [Parameter(ParameterSetName='IndividualParameter',Mandatory=$false,HelpMessage='Please enter the password to connect to the OpsMgr management group')][Alias('p')][SecureString]$Password = $null, [Parameter(Mandatory=$true,HelpMessage='Please enter management pack name')][System.String]$MPName, [Parameter(Mandatory=$true,HelpMessage='Please enter Override Type')][ValidateSet('MonitorPropertyOverride', 'RulePropertyOverride', 'DiscoveryPropertyOverride', 'DiagnosticPropertyOverride', 'RecoveryPropertyOverride')][System.String]$OverrideType, [Parameter(Mandatory=$true,HelpMessage='Please enter override name')][System.String]$OverrideName, [Parameter(Mandatory=$false,HelpMessage='Please enter override display name')][System.String]$OverrideDisplayName, [Parameter(Mandatory=$true,HelpMessage='Please enter override workflow ID')][System.String]$OverrideWorkflowID, #[Parameter(ParameterSetName='Set 2',Mandatory=$true,HelpMessage='Please enter override workflow name')][System.String]$OverrideWorkflow, [Parameter(Mandatory=$true,HelpMessage='Please enter override target')][Alias('target')][System.String]$OverrideTarget, [Parameter(Mandatory=$false,HelpMessage='Please enter override context instance ID')][Alias('Instance')][System.String]$ContextInstance = $null, [Parameter(Mandatory=$true,HelpMessage='Please enter override property')][System.String]$OverrideProperty, [Parameter(Mandatory=$true,HelpMessage='Please enter override value')]$OverrideValue, [Parameter(Mandatory=$false,HelpMessage='Set override to Enforced')][System.Boolean]$Enforced=$false, [Parameter(Mandatory=$false,HelpMessage='Increase MP version by 0.0.0.1')][System.Boolean]$IncreaseMPVersion=$false ) #Connect to MG If ($SDKConnection) { Write-Verbose "Connecting to Management Group via SDK $($SDKConnection.ComputerName)`..." $MG = Connect-OMManagementGroup -SDKConnection $SDKConnection $SDK = $SDKConnection.ComputerName $Username = $SDKConnection.Username $Password= ConvertTo-SecureString -AsPlainText $SDKConnection.Password -force } else { Write-Verbose "Connecting to Management Group via SDK $SDK`..." If ($Username -and $Password) { $MG = Connect-OMManagementGroup -SDK $SDK -UserName $Username -Password $Password } else { $MG = Connect-OMManagementGroup -SDK $SDK } } #Get the unsealed MP $strMPquery = "Name = '$MPName'" $mpCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackCriteria($strMPquery) $MP = $MG.GetManagementPacks($mpCriteria)[0] If ($MP) { #MP found, now check if it is sealed If ($MP.sealed) { Write-Error 'Unable to save to the management pack specified. It is sealed. Please specify an unsealed MP.' return $false } } else { Write-Error 'The management pack specified cannot be found. please make sure the correct name is specified.' return $false } #Get the monitoring Class (Override target / context) $strMCQuery = "Name = '$OverrideTarget'" $mcCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringClassCriteria($strMCQuery) $MonitoringClass = $MG.GetMonitoringClasses($mcCriteria)[0] if (!$MonitoringClass) { Write-Error 'The override target (context) specified cannot be found. please make sure the correct name is specified.' return $false } If ([bool]$OverrideWorkflowId){ $criteria = "Id='$OverrideWorkflowId'" } Else{ $criteria = "Name='$OverrideWorkflow'" } #Create new override Switch ($OverrideType) { "MonitorPropertyOverride" { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitorCriteria($criteria) $Workflow = $MG.GetMonitors($WorkflowCriteria)[0] $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackMonitorPropertyOverride($MP, $OverrideName) $Override.Monitor = $Workflow } "RulePropertyOverride" { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringRuleCriteria($criteria) $Workflow = $MG.GetMonitoringRules($WorkflowCriteria)[0] $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackRulePropertyOverride($MP, $OverrideName) $Override.Rule = $Workflow } 'DiscoveryPropertyOverride' { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringDiscoveryCriteria($criteria) $Workflow = $MG.GetMonitoringDiscoveries($WorkflowCriteria)[0] $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackDiscoveryPropertyOverride($MP, $OverrideName) $Override.Discovery = $Workflow } 'DiagnosticPropertyOverride' { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringDiagnosticCriteria($criteria) $Workflow = $MG.GetMonitoringDiagnostics($WorkflowCriteria)[0] $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackDiagnosticPropertyOverride($MP, $OverrideName) $Override.Diagnostic = $Workflow } 'RecoveryPropertyOverride' { $WorkflowCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringRecoveryCriteria($criteria) $Workflow = $MG.GetMonitoringRecoveries($WorkflowCriteria)[0] $Override = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackRecoveryPropertyOverride($MP, $OverrideName) $Override.Recovery = $Workflow } } #Finishing creating overrides $Override.Property = $OverrideProperty $Override.Value = $OverrideValue $Override.Context = $MonitoringClass If ($ContextInstance.Length -gt 0) { $Override.ContextInstance = $ContextInstance } If ($OverrideDisplayName) { $Override.DisplayName = $OverrideDisplayName } If ($Enforced) { $Override.Enforced = $true } #Increase MP version If ($IncreaseMPVersion) { $CurrentVersion = $MP.Version.Tostring() $vIncrement = $CurrentVersion.Split('.') $vIncrement[$vIncrement.Length - 1] = ([System.Int32]::Parse($vIncrement[$vIncrement.Length - 1]) + 1).ToString() $NewVersion = ([System.String]::Join('.', $vIncrement)) $MP.Version = $NewVersion } #Verify and save the MP Try { $MP.verify() $MP.AcceptChanges() $Result = $true Write-Verbose "Override '$OverrideName' successfully created in Management Pack '$MPName'($($MP.Version))." } Catch { $Result = $false Write-Error "Unable to create override $OverrideName in management pack $MPName." $MP.RejectChanges() } $MG.Dispose() $Result } ######################################################## Function New-BatchDir { Param ( $rootdir, $Label, $Stamp ) If (-NOT $Stamp){ $Stamp = Get-Date -Format o | ForEach-Object -Process {$_ -replace ":", "."} } #Create subdir for today with timestamped name $today = (Get-Date -F yyyyMMdd) $todaySubDir = (Join-Path $rootDir $today) If (-not (Test-Path -Path $todaySubDir -PathType Container)) { LogIt -OutDir $rootDir -msg "Creating BU directory for today [$today]..." -Display If (-not(. My-NewItem -type Directory -Path $todaySubDir)) { DoExit -msg "Fatal error craeting BU directory for today [$today]. Any Error Data: $Error " } } #Create a batch level backup dir with unique name $batchSubDir = (Join-Path $todaySubDir "$($Stamp)$($Label)") If (-not (Test-Path -Path $batchSubDir -PathType Container)) { LogIt -OutDir $rootDir -msg "Creating BU directory [$batchSubDir] for this batch#: $Stamp" If (-not(. My-NewItem -Type Directory -Path $batchSubDir)) { DoExit -msg "Fatal error with task: `"Creating BU directory [$batchSubDir] for this batch#: $Stamp`" Any Error Data: $Error " } } } #end New-BatchDir ######################################################## Function New-MP { Param( ) . Load-MPs -type 'Sealed' [System.Collections.ArrayList]$Suggestions = @() # If an unsealed override MP already exists, then omit it from the suggestions ForEach ($item in $SealedMPs) { If ([bool]($($UnsealedMPs.Name) -like "*$($item.Name)*override*")) { Continue; } $SuggestedName = "$($item.Name)" + ".OVERRIDES" $SuggestedDN = "$($item.DisplayName)" + " (OVERRIDES)" $Object = New-Object PSObject $Object | add-member Noteproperty SealedMPName ([string]$item.Name) $Object | add-member Noteproperty Suggested_MPName ([string]$SuggestedDN) $Suggestions.Add($Object) | Out-Null } Do { [System.Collections.ArrayList]$newMPName = @() $newMPName = @($Suggestions | Sort-Object SealedMPName | Out-GridView -OutputMode Single -Title "Select a suggested MP name (OR 'Cancel' to type your own name.)") If ($newMPName.Count -gt 1) { Write-Host "`nYou may only select ONE name. Try again or 'Cancel'. " -F Red } }While ($newMPName.Count -gt 1) If (($newMPName.Count -eq 0)) { $NewDN = Read-Host -Prompt "Enter new MP name '[a-z,A-Z,0-9,., ,()]' " $NewDN = ($NewDN -replace '[^a-zA-Z0-9. \-()]', '') Write-Host "Is this correct ('N' to cancel): " -F Green -NoNewline; Write-Host $NewDN -F Yellow Do { $Choice = Read-Host "Y/N? " }While ($Choice -notmatch 'y|n|yes|no|sure|nope') If ($Choice -notmatch 'y|yes|sure') { Write-Host "Cancelling new MP... " -F Yellow Return } Else { $msg = "Custom name accepted: [$($NewDN)]." Write-Host $msg LogIt -OutDir $BackupDir -msg $msg $newName = $NewDN.Replace(' ','.').Replace('-','') $newName = ($newName -replace '[^a-zA-Z0-9.]', '') While ($newName -match '\.\.'){ $newName = $newName.Replace('..','.') } } } If ($newMPName.Count -eq 1) { $NewDN = $newMPName[0].Suggested_MPName $newName = "$($newMPName[0].SealedMPName)" + ".OVERRIDES" } LogIt -OutDir $BackupDir -msg "Attempting to create new, empty, unsealed MP: [$($newDN)] ..." -F Gray -Display If (-not(New-OMManagementPack -SDK $SDK -DisplayName $NewDN -Name $newName)) { $msg = "Problem Creating new empty unsealed MP: [$($NewName)]. $($error[0])" LogIt -OutDir $BackupDir -msg ($msg + ", [$($newName)]" ) -F Yellow -Display } Else{ $msg = "Success creating new empty unsealed MP: [$($NewDN)]." Write-Host "Note: Your new MP may not appear immediately. If so, wait a minute, then use `"Reoload all MPs`" option." -F Yellow LogIt -Display -OutDir $BackupDir -msg ($msg + ", [$($newName)]" ) -F Green } # Refresh the MPs now that changes have been made $Reload = $True } ######################################################## Function New-Override { Param( $Params ) LogIt -OutDir $BackupDir -msg "`nAttempting to create new override: [$($Params.OverrideName)] in Destination MP: [$($Params.MPName)]" If ($OR_F.Property){ $NewORResult = (New-OMPropertyOverride2 @Params) } ElseIf ($OR_F.Parameter){ $NewORResult = (New-OMConfigurationOverride2 @Params) } $details = "[" ForEach ($key in $Params.keys ){ $details += "($($key)=$($Params.$key));" } $details = $details.TrimEnd(';') $details += "]" If ($NewORResult){ LogIt -Display -OutDir $BackupDir -msg "Success moving override. Details: $($details)" -F Green } Else { $ISFailureDetected = $true $msg = "`n FAILURE! Original override [$($OR_F.ORName)] deleted from [$($OR_F.ORMPName)] " $msg += "but unable to create new override: [$($Params.OverrideName)], Destination MP: [$($Params.MPName)]. " $msg += "This will probably require you to manually recreate the override in the destination MP or in the original MP. See logfile for details." LogIt -Display -OutDir $BackupDir -msg $msg -F Yellow -B Red LogIt -Display -OutDir $BackupDir -msg "`nOriginal override details: $($OR_F) , `nAttempted Destination MP: [$($Params.MPName)]." -F Yellow LogIt -Display -OutDir $BackupDir -msg "`nAttempted (FAILED) override creation details: [$($details)]" -F DarkYellow Do{ Write-Host "`nAbort operation (entire batch) ..." -F Red -B Yellow -NoNewline $abort = Read-Host -Prompt " Y/N?" }While($abort -notmatch 'y|n') } Else { $ISFailureDetected = $true LogIt -Display -OutDir $BackupDir -msg "`n FAILURE! Unable to remove original override. Original override: [$($OR_F.ORName)] remains in MP: [$($OR_F.ORMPName)]. Skipping this item." -F Yellow -B Red Do{ Write-Host "`nAbort operation (entire batch) ..." -F Red -B Yellow -NoNewline $abort = Read-Host -Prompt " Y/N?" }While($abort -notmatch 'y|n') } $NewORResult = $false } ######################################################## Function New-OverrideName { Param( $OriginalOR, [string]$ORName, [string]$Type, $WF ) $today = (Get-Date -F yyyyMMdd) [string]$UniqueStamp = "$($today)_$("{0:HH}{0:mm}{0:ss}.{1}" -F (Get-Date),($(Get-Date).Millisecond))" #Prefixing 'z_' to OR name will help to identify ORs created with this tool If ([bool]$OriginalOR){ $partialGuid = $OriginalOR.WorkflowID.Substring($OriginalOR.WorkflowID.Length - 12, 12) $newName = "$($Type)For_$($OriginalOR.Workflow)_$($partialGuid)_Class_$($OriginalOR.Context)" If ($OriginalOR.ContextInstanceID){ $newName += "Instance_$($OriginalOR.ContextInstanceID.Replace('-','.'))" } } ElseIf ([bool]$WF){ $partialGuid = $WF.ID.Substring($WF.ID.Length - 12, 12) $newName = "$($Type)For_$($WF.Name)_$($partialGuid)_Class_$($WF.TargetName)" } $newName = ("z.$($UniqueStamp)." + "$newName").Replace("_",".") Return $newName.Substring(0,([math]::Min(($newName.Length -1),254))) } ######################################################## Function New-OverrideSplat { Param( $OR_F ) $NewORResult = $false $Params = @{} $Params.Add('SDK',$SDK) $Params.Add('MPName',$DestMP.Name) #$Params.Add('OverrideWorkflow',$OR_F.Workflow) $Params.Add('OverrideWorkflowId',$OR_F.WorkflowId) $Params.Add('OverrideTarget',$OR_F.Context) $Params.Add('Enforce',$OR_F.Enforced) $Params.Add('OverrideValue',$OR_F.Value) $Params.Add('IncreaseMPVersion',$True) If ($OR_F.ContextInstanceID){ $Params.Add('ContextInstance',$OR_F.ContextInstanceID) } If ($OR_F.Property){ Switch ($OR_F.WorkflowType) { "Monitor" { $Params.Add('OverrideType','MonitorPropertyOverride') } "Rule" { $Params.Add('OverrideType','RulePropertyOverride') } "Discovery" { $Params.Add('OverrideType','DiscoveryPropertyOverride') } } $Params.Add('OverrideProperty',$OR_F.Property) $Params.Add('OverrideName',(New-OverrideName -OriginalOR $OR_F -Type $Params.OverrideType)) } ElseIf ($OR_F.Parameter){ Switch ($OR_F.WorkflowType) { "Monitor" { $Params.Add('OverrideType','MonitorConfigurationOverride') } "Rule" { $Params.Add('OverrideType','RuleConfigurationOverride') } "Discovery" { $Params.Add('OverrideType','DiscoveryConfigurationOverride') } } $Params.Add('OverrideParameter',$OR_F.Parameter) $Params.Add('OverrideName',(New-OverrideName -OriginalOR $OR_F -Type $Params.OverrideType)) } Else { #No Property or Parameter detected. } Return $Params } ######################################################## Function Open-LogFile { $recentFilePath = (Get-ChildItem -Path $BackupDir -Filter *scom*.csv | Sort-Object -Property LastWriteTime -Descending)[0].FullName If ([bool]$recentFilePath) { Write-Host -Object "`nOpening log/backup file." -ForegroundColor Gray & $recentFilePath } Else { Write-Host -Object 'No daily logfile exists yet. ' -ForegroundColor Yellow If ($BackupDir) { Write-Host -Object 'Opening log/backup folder with Explorer.exe ...' -ForegroundColor Yellow Explorer.exe $BackupDir } } } ######################################################## Function Remove-MP { Param( $MP ) Remove-Variable RemoveMPResult -ErrorAction SilentlyContinue $MP | ForEach-Object { LogIt -OutDir $BackupDir -msg "Attempting deletion of MP: [$($_.Name)]" Get-SCOMManagementPack -Name $_.Name | Remove-SCOMManagementPack -Verbose If ($?){ If ([bool](Get-SCOMManagementPack -Name $_.Name)){ #Failed removal LogIt -Display -OutDir $BackupDir -msg "FAILURE, Removal of MP: [$($_.DisplayName),$($_.Name)]" -F Red -B Yellow } Else{ #Success, MP no longer present LogIt -Display -OutDir $BackupDir -msg "SUCCESS, Removal of MP: [$($_.DisplayName),$($_.Name)]" -F Green } } Else{ #Some error LogIt -Display -OutDir $BackupDir -msg "WARNING: Some type of error (`$?) detected during Removal of MP: [$($_.DisplayName),$($_.Name)]" -F Red -B Yellow } } # Refresh the MPs now that changes have been made $Reload = $True } ######################################################## <# Script Name: RemoveObsoleteMPRefTask.ps1 Original Author: Tao Yang ( http://blog.tyang.org/2015/09/16/opsmgr-self-maintenance-management-pack-2-5-0-0/ ) <-- I love this guy! Check out is other articles. Updated by Tyson Paul : 2019.09.05 COMMENT: - Script to clean up obsolete references from unsealed MPs Version History: 2020.01.06 - Updated MPVerify error handling. #> #============================================================================== Function RemoveObsoleteMPRefTask { Param ( [Parameter(Mandatory=$false)][switch]$BackupBeforeModify, #[Parameter(Mandatory=$false)][string]$BackupLocation, [Parameter(Mandatory=$false)][switch]$IncrementVersion, [Parameter(Mandatory=$false)][switch]$WhatIf, [Parameter(Mandatory=$false)][string]$CommonMP1, [Parameter(Mandatory=$false)][string]$CommonMP2, [Parameter(Mandatory=$false)][string]$CommonMP3, [Parameter(Mandatory=$false)][string]$CommonMP4, [Parameter(Mandatory=$false)][string]$CommonMP5 ) #Region FunctionLibs Function Get-ObsoleteReferences { Param ( [XML]$MPXML, [System.Collections.ArrayList]$arrCommonMPs ) $arrAliasToBeDelete = New-Object System.Collections.ArrayList $Refs = $MPXML.ManagementPack.Manifest.References.Reference Foreach ($Ref in $Refs) { $RefMPID = $Ref.ID $Alias = $Ref.Alias $strRef = "$Alias`!" If (-NOT (($MPXML.InnerXml -match ("$($Alias)!")) -or ($MPXML.InnerXml -match ("$($RefMPID)!"))) ) { #The alias is obsolete #Ignore Common MPs If (!($arrCommonMPs.contains($RefMPID))) { #Referecing MP is not a common MP [Void]$arrAliasToBeDelete.Add($Alias) } } } ,$arrAliasToBeDelete } Function Get-MpXmlString ($MP) { $MPStringBuilder = New-Object System.Text.StringBuilder $MPXmlWriter = New-Object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackXmlWriter([System.Xml.XmlWriter]::Create($MPStringBuilder)) [Void]$MPXmlWriter.WriteManagementPack($MP) $MPXML = [XML]$MPStringBuilder.ToString() $MPXML } Function Increase-Version ([String]$CurrentVersion) { $vIncrement = $CurrentVersion.Split('.') $vIncrement[$vIncrement.Length - 1] = ([system.int32]::Parse($vIncrement[$vIncrement.Length - 1]) + 1).ToString() $NewVersion = ([string]::Join(".", $vIncrement)) $NewVersion } #EndRegion #region Main $batchstamp = Get-Date -Format o | ForEach-Object -Process {$_ -replace ":", "."} #Define an arraylist to store common MPs $arrCommonMPs = New-Object System.Collections.ArrayList [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.Library") [Void]$arrCommonMPs.Add("Microsoft.Windows.Library") [Void]$arrCommonMPs.Add("System.Health.Library") [Void]$arrCommonMPs.Add("System.Library") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.DataWarehouse.Internal") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.Notifications.Library") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.DataWarehouse.Library") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.OperationsManager.Library") [Void]$arrCommonMPs.Add("System.ApplicationLog.Library") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.Advisor.Internal") [Void]$arrCommonMPs.Add("Microsoft.IntelligencePacks.Types") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.Visualization.Configuration.Library") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.Image.Library") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.Visualization.ServiceLevelComponents") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.NetworkDevice.Library") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.InstanceGroup.Library") [Void]$arrCommonMPs.Add("Microsoft.Windows.Client.Library") [Void]$arrCommonMPs.Add("Microsoft.IntelligencePacks.CloudUpload") [Void]$arrCommonMPs.Add("Microsoft.SystemCenter.Advisor") [Void]$arrCommonMPs.Add("Microsoft.IntelligencePacks.Performance") [Void]$arrCommonMPs.Add("Microsoft.IntelligencePacks.Dns") [Void]$arrCommonMPs.Add("Microsoft.IntelligencePacks.SecurityBaselineScom") [Void]$arrCommonMPs.Add("Microsoft.IntelligencePacks.InventoryChangeTracking") #Process additional Common MPs passed in via Overrides $AdditionalCommonMPs = New-Object System.Collections.ArrayList $CommonMP1,$CommonMP2,$CommonMP3,$CommonMP4,$CommonMP5 | Foreach-Object {If($_.Length -gt 0) {[void]$AdditionalCommonMPs.Add($_)}} Foreach ($item in $AdditionalCommonMPs) { If (!$arrCommonMPs.Contains($item)) { [Void]$arrCommonMPs.Add($item) } } $ManagementServer = @($MG | Where-Object {$_.Isactive -eq $true})[0].ManagementServerName LogIt -msg "Running Remove Obsolete MP References task on $managementserver`." -Display -F Gray #Get all unsealed MPs LogIt -msg "Getting Unsealed management packs..." -Display -F Gray $strMPquery = "Sealed = 'false'" $mpCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackCriteria($strMPquery) $arrMPs = Get-SCOMManagementPack | Where-Object Sealed -eq $False LogIt -msg "Total number of unsealed management packs: $($arrMPs.count)" -Display -F Gray $iTotalUpdated = 0 Foreach ($MP in $arrMPs) { LogIt -msg "Checking MP: '$($MP.Name)'..." -Display -F Gray #First, get the XML $MPXML = Get-MpXmlString $MP #Then get obsolete references (if there are any) $arrRefToDelete = Get-ObsoleteReferences -MPXML $MPXML -arrCommonMPs $arrCommonMPs If ($arrRefToDelete.count -gt 0) { LogIt -OutDir $BackupDir -Display -msg " - Number of obsolete references found: $($arrRefToDelete.count)" -F White If ($BackupBeforeModify -eq $true) { If ($WhatIf -eq $false) { #Backup Mp Backup-MP -MPName $MP.Name -rootDir $BackupDir -Stamp $batchstamp } else { Write-Host " - $($MP.Name) would have been backed up to $BackupDir before modifying it." } } Foreach ($item in $arrRefToDelete) { If ($WhatIf -eq $false) { LogIt -OutDir $BackupDir -Display -msg " - Deleting reference '$item'" -F Green $MP.References.remove($item) | out-Null } else { Write-Host " - The reference '$item' would have been deleted." } } #calculate the new version number if $IncrementVersion switch is specified if ($IncrementVersion -eq $true) { $CurrentVersion = $MP.Version.Tostring() $NewVersion = Increase-Version $CurrentVersion } If ($WhatIf -eq $false) { If ($IncrementVersion -eq $true) { $MP.Version = $NewVersion } Try { LogIt -OutDir $BackupDir -Display -msg "Attempt MP Verify()..." -F Yellow $bVerified = $MP.Verify() }Catch { #Verify() failure is very difficult to troubleshoot expecially when I cannot easily reproduce it in my lab. $bVerified = $false $msg = @" MP Verify failed. Rejecting changes. Error Exception Data: $($Error | Select-Object -First 1 -Property *) "@ LogIt -OutDir $BackupDir -Display -msg $msg -F Yellow . New-BatchDir -rootdir $BackupDir -Label "_Failed_MPVerify()_RemoveObsoleteMPRefTask" $MP | Export-SCOMManagementPack -Path $batchSubDir LogIt -OutDir $BackupDir -Display -msg "Failed MPVerify() in RemoveObsoleteMPRefTask. Faulty MP object dumped to: [$($batchSubDir)]" -F Yellow } #accept changes if MP is verified. otherwise reject changes If ($bVerified -eq $false) { $MP.RejectChanges() } else { Try { $MP.AcceptChanges() #Increase total updated count only when changes are accepted. $iTotalUpdated ++ LogIt -OutDir $BackupDir -Display -msg "MP Verified. Changes Accepted." -F Green } Catch { LogIt -OutDir $BackupDir -Display -msg "Task failure. Failed AcceptChanges() for this MP: [$($MP.Name)]." -F Yellow } } } else { If ($IncrementVersion -eq $true) { Write-Host " - The MP version would have been updated from $CurrentVersion to $NewVersion." $iTotalUpdated ++ } } } Write-Host "" } #End ForEach MP If ($WhatIf -eq $true){ Write-host "Total number of unsealed management packs would have been updated: $iTotalUpdated" -ForegroundColor Green } else { Write-host "Total number of unsealed management packs have been updated: $iTotalUpdated" -ForegroundColor White } If ($iTotalUpdated -gt 0){ Clear-Selection -Type all } Write-Host "Done" #endregion } #End Function ######################################################## Function Remove-Override { Param( $OR_F, $Override = $null ) Remove-Variable RemoveORResult -ErrorAction SilentlyContinue If ([bool]$Override){ $orName = $Override.Name $ormpName = $Override.Identifier.Domain } ElseIf ([bool]$OR_F){ $orName = $OR_F.ORName $ormpName = $OR_F.ORMPName } LogIt -OutDir $BackupDir -msg "Attempting deletion of override [$($orName)] in Source MP: [$($ormpName)]." $RemoveORResult = (Remove-OMOverride -SDK $SDK -OverrideName $orName ) If ([bool]$RemoveORResult){ LogIt -OutDir $BackupDir -msg "Success! Deletion of override [$($orName)] in Source MP: [$($ormpName)]." -Display } Else { LogIt -OutDir $BackupDir -msg "FAILED! Deletion of override [$($orName)] in Source MP: [$($ormpName)]." -Display -F Yellow } # Refresh the MPs now that changes have been made $Reload = $True Return $RemoveORResult } ######################################################## Function Select-MPTypeFilter { Param( [ValidateSet("Sealed","Unsealed","All")] [string]$SealedType ) $TempMenu = @{} #hash [System.Collections.ArrayList]$TempMenuItems = @() $TempMenuItems.Add(@{DN="Select only Sealed MPs (to choose workflows to override).";Action='Select-MPs -Type "source" -FilterType "Sealed"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Select only Unsealed MPs (to choose either workflows to override OR overrides to move/relocate).";Action='Select-MPs -Type "source" -FilterType "Unsealed"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Select ALL MPs (to choose either workflows to override OR overrides to move/relocate).";Action='Select-MPs -Type "source" -FilterType "All"';F="Green"} ) | Out-Null $TempMenuItems.Add(@{DN="Cancel (go back to main menu)";Action='Select-MPs -Type "Cancel"';F="DarkRed"} ) | Out-Null [int]$j=1 $TempMenuItems | ForEach-Object { $TempMenu.$j = $_ $j++ } $Choice = Get-MenuChoice -Menu $TempMenu # Execute the menu selection Invoke-Expression -Command ". $($TempMenu.($Choice).Action)" } ######################################################## Function Select-MPs { Param( [ValidateSet("Sealed","Unsealed","All")] [string]$FilterType="All", [ValidateSet("Source","Dest","Cancel")] [string]$Type ) $msg = "MP(s) Selected: " Remove-Variable -Name 'UnsealedMPs','SealedMPs','tempList' -ErrorAction SilentlyContinue Switch ($Type) { #region Source type "source" { [System.Collections.ArrayList]$SourceMP = @() switch ($FilterType) { 'Sealed'{ . Load-MPs -type 'Sealed' $tmpMPs = $SealedMPs } 'Unsealed'{ . Load-MPs -type 'Unsealed' $tmpMPs = $UnsealedMPs } 'All'{ . Load-MPs -type 'All' $tmpMPs = $AllMPs } default { 'SELECT-MPS DEFAULT SWITCH'} } # Do selection here $tempList = @($tmpMPs | ` Select-Object -Property Sealed,DisplayName,Name,Version,ID,` @{Name="TimeCreated";Expression={ (Convert-UTCtoLocal $_.TimeCreated ) }},` @{Name="LastModified";Expression={ (Convert-UTCtoLocal $_.LastModified ) }},` Description,` @{Name="References";Expression={$_.References.GetEnumerator()}} | ` Sort-Object Name | ` Out-GridView -PassThru -Title "Select one or more MPs... or just Cancel.") ForEach ($MPItem in $tempList){ $SourceMP.Add($hashAllMPs[$MPItem.Name]) | Out-Null $msg += "[$($MPItem.DisplayName),$($MPItem.Name)]," } } #endregion Source type "Dest" { <# If ($SourceMP.Count -eq $AllMPs.Count){ $tmpMsg = @" ALL MPs are currently selected for Source. There are no valid unsealed packs remaining to select for Destination. Please allow at least one unsealed pack to remain unselected (Source) so that it may be selected for the Destination (override) pack. "@ LogIt -OutDir $BackupDir -Display -F yellow $tmpMsg Return } #> . Load-MPs -type 'Unsealed' $DestMP = @($UnsealedMPs | ` Select-Object -Property Sealed,DisplayName,Name,Version,ID,` @{Name="TimeCreated";Expression={ (Convert-UTCtoLocal $_.TimeCreated ) }},` @{Name="LastModified";Expression={ (Convert-UTCtoLocal $_.LastModified ) }},` Description,` @{Name="References";Expression={$_.References.GetEnumerator()}} | ` Sort-Object Name | ` Out-GridView -OutputMode Single -Title "Select ONE destination MP. ") If ($DestMP.Count -gt 1) { Write-Host "`nYou may only select ONE destination MP. Try again." -F Red $DestMP = "" . Select-MPs -Type "Dest" } If ($DestMP.Count -eq 0) { LogIt -OutDir $BackupDir -Display "No MP selected. Return to main menu." $Reload=$True Return } # At this point $DestMP should contain a slim/formatted version of an MP, selected above # This will store the whole/pure MP object in $DestMP $DestMP = $hashAllMPs[$DestMP.Name] $DestMP | ForEach-Object {$msg += "[$($_.DisplayName),$($_.Name)]," } } "Cancel" { $Reload=$True Return } Default {Write-Error "Problem with Select-MPs source/dest type. -Type value must be 'source', or 'dest'." } }#end Switch $msg = $msg.Trim(',') LogIt -msg $msg -OutDir $BackupDir -F Cyan } ######################################################## Function Select-Overrides { Param( [Parameter( Mandatory=$false, ParameterSetName='1')] [System.Object[]]$MPs, [System.Object[]]$Override, [Switch]$silent ) . Load-MPs -type 'Unsealed' [System.Collections.ArrayList]$arrayOR = @() $SelectedOR_F = @() If ([bool]$MPs) { LogIt -Display -msg "`nGetting overrides from $($MPs.Count) unsealed MP(s). This may take a minute..." -OutDir $BackupDir # Return only the overrides from the previously selected MPs ForEach ($MP in $MPs) { Try{ $arrayOR += ( ($UnsealedMPs | Where-Object { $_.Name -eq $MP.Name}).GetOverrides() ) }Catch{ Write-Host "No valid overrides found in MP: $($MP.Name)" } } } Else { LogIt -msg "`nGetting overrides from $($UnsealedMPs.Count) unsealed MP(s). This may take a minute..." -OutDir $BackupDir $arrayOR = $UnsealedMPs | ForEach-Object {$_.GetOverrides()} } ##### FILTER OUT ORs THAT REFERENCE LOCAL OBJECTS (local to the same unsealed MP) $arrayOR = @($arrayOR | Where-Object { ` (($_.Context.Identifier.Domain) -notcontains ($_.Identifier.Domain[0])) ` -and (($_.Rule.Identifier.Domain) -notcontains ($_.Identifier.Domain[0])) ` -and (($_.Monitor.Identifier.Domain) -notcontains ($_.Identifier.Domain[0])) ` -and (($_.Discovery.Identifier.Domain) -notcontains ($_.Identifier.Domain[0])) ` } ` ) If ($arrayOR.Count -eq 0){ LogIt -OutDir $BackupDir -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)." -F Red -B Yellow } If ([bool]$Override) { $OR_F = Format-OR -ORs $Override If ($silent){ $SelectedOR_F += $OR_F } Else{ $SelectedOR_F += $OR_F | Out-GridView -PassThru -Title "OVERRIDES: Select one or more overrides, then click 'OK'. Only eligible items will appear below." } } # Format the object for your viewing pleasure and manual selection Else{ If ($arrayOR.Count) { $OR_F = Format-OR -ORs $arrayOR $SelectedOR_F += $OR_F | Out-GridView -PassThru -Title "OVERRIDES: Select one or more overrides, then click 'OK'. Only eligible items will appear below." } Else { LogIt -msg "`nNo overrides detected in selected MP(s)." -OutDir $BackupDir -Display -F Yellow } } $msg = "Override(s) selected: " $SelectedOR_F | ForEach-Object { $msg += "[$($_.ORName)], " } LogIt -OutDir $BackupDir -msg $msg } ######################################################## Function Select-Workflows { Param( [Parameter( Mandatory=$false, ParameterSetName='1')] [System.Object[]]$SourceMP ) #Build Menu $TempMenu = @{} #hash $MenuItems = @() $MenuItems += @{DN="Select WF type: Rule";Action='Load-Workflows -WFType Rule -SourceMP $SourceMP';F="Green"} $MenuItems += @{DN="Select WF type: Monitor";Action='Load-Workflows -WFType Monitor -SourceMP $SourceMP';F="Green"} $MenuItems += @{DN="Select WF type: Discovery";Action='Load-Workflows -WFType Discovery -SourceMP $SourceMP';F="Green"} $MenuItems += @{DN="Cancel (go back to main menu)";Action='Return';F="Gray"} [int]$i=1 $MenuItems | ForEach-Object { $TempMenu.$i = $_ $i++ } $Choice = Get-MenuChoice -Menu $TempMenu # Execute the menu selection Invoke-Expression -Command ". $($TempMenu.($Choice).Action)" $availableWFs = @() $SelectedWFs = @() $tmpIndex = 0 $stopwatch = [system.diagnostics.stopwatch]::StartNew() # $WFs are loaded in the Load-Workflow function ForEach ($WF in $WFs) { $tmpIndex++ $percentComplete = ($tmpIndex/$WFs.Count) $predictedTotalSeconds = ($stopwatch.Elapsed.TotalSeconds / $percentComplete) $predictedRemainingSeconds = ($predictedTotalSeconds - $stopwatch.Elapsed.TotalSeconds) Write-Progress -Activity "Finding all workflows and related overrides..." -Status "$([math]::Round($percentComplete*100))% Complete " -PercentComplete ([math]::Round($percentComplete*100)) -CurrentOperation "$($WF.Name)" -SecondsRemaining $predictedRemainingSeconds $hashORResult = @{} $ORresult = $null [bool]$OverrideExists = $false <# TEST TEST TEST If ($WF.Name -like "*Microsoft.SQLServer.2016.AlwaysOn.BytesSentToTransportPerSecond*") { Read-Host "FOUND $($WF.Name)" Write-Host "STOP Line: $($MyInvocation.ScriptLineNumber)" } #> $TargetClass = $hashAllClasses[([string]$WF.Target.Identifier.Path)] switch ($SelectedWFType) { #NOTE OverrideResults are only returned here if the OR is targeted at the -Class (not ContextInstance/Object) 'Monitor' { $ORresult = Get-SCOMOverrideResult -Monitor $WF -Class $TargetClass <#If ($ORresult.PropertyName -eq 'Enabled') { $hashORResult.Add($WF.Id.Guid,$ORresult) }#> } 'Rule' { $ORresult = Get-SCOMOverrideResult -Rule $WF -Class $TargetClass <#If ($ORresult.PropertyName -eq 'Enabled') { $hashORResult.Add($WF.Id.Guid,$ORresult) }#> } 'Discovery' { $ORresult = Get-SCOMOverrideResult -Discovery $WF -Class $TargetClass <#If ($ORresult.PropertyName -eq 'Enabled') { $hashORResult.Add($WF.Id.Guid,$ORresult) }#> } Default {Write-Host "No workflows selected. " -F Yellow; Return} } If ($ORresult) { #This is a clever way to make sure that if the Enabled Property override exists, it will be first in the array. This will be useful in the bulk Enable/Disable actions $sortedORresult = @($ORresult | ? {$_.PropertyName -eq 'Enabled'} ) $sortedORresult += @($ORresult | ? {$_.PropertyName -ne 'Enabled'} ) Try{ $hashORResult.Add($WF.Id.Guid,$sortedORresult) }Catch{ Write-Host "Problem with sortedORresult" Write-Debug "STOP Line: $($MyInvocation.ScriptLineNumber)" $debug = $true } [bool]$OverrideExists = $TRUE } $tempObj = New-Object pscustomobject $tempObj | Add-Member -MemberType NoteProperty -Name 'Name' -Value $WF.Name $tempObj | Add-Member -MemberType NoteProperty -Name 'DisplayName' -Value $WF.DisplayName $tempObj | Add-Member -MemberType NoteProperty -Name 'Id' -Value $WF.Id.Guid $tempObj | Add-Member -MemberType NoteProperty -Name 'XMLTAG' -Value $WF.XMLTAG $tempObj | Add-Member -MemberType NoteProperty -Name 'TargetName' -Value $TargetClass.Name If ([bool]$TargetClass.DisplayName){ $tempObj | Add-Member -MemberType NoteProperty -Name 'TargetDisplayName' -Value $TargetClass.DisplayName } Else { $tempObj | Add-Member -MemberType NoteProperty -Name 'TargetDN' -Value '' } # Identify the collection type $CollectionTypes = @(( $WF | Select-Object -Property *collection* | Get-Member).Name | Where-Object {$_ -match 'Collection'} ) ForEach ($CollectionType in $CollectionTypes) { $tempObj | Add-Member -MemberType NoteProperty -Name ($CollectionType.ToString()) -Value ( (($WF.($CollectionType.ToString()).TypeID.Identifier.Path) | Out-String).Trim() ) } $tempObj | Add-Member -MemberType NoteProperty -Name 'Category' -Value $WF.Category $tempObj | Add-Member -MemberType NoteProperty -Name 'MPName' -Value $WF.Identifier.Domain[0] $tempObj | Add-Member -MemberType NoteProperty -Name 'MPDisplayName' -Value $hashAllMPs.($WF.Identifier.Domain[0]).DisplayName $tempObj | Add-Member -MemberType NoteProperty -Name 'Sealed' -Value $hashAllMPs.($WF.Identifier.Domain[0]).Sealed $tempObj | Add-Member -MemberType NoteProperty -Name 'MPVersion' -Value $hashAllMPs.($WF.Identifier.Domain[0]).Version.ToString() $tempObj | Add-Member -MemberType NoteProperty -Name 'Enabled(by default)' -Value ($WF.Enabled) If ([bool]$OverrideExists) { $tempObj | Add-Member -MemberType NoteProperty -Name 'Overrides' -Value ([int]0) For ($i=0; $i -lt @($hashORResult[$WF.Id.Guid].Override).Count;$i++) { [String]$ORInherited = '' [String]$ORType = '' # Determine if this OR is inherited or not Try { $Local = ($hashORResult[$WF.Id.Guid].Override)[$i].Value.LocalOverride } Catch{ $Local = $false } Try { $Effective = ($hashORResult[$WF.Id.Guid].Override)[$i].Value.EffectiveOverride } Catch { $Effective = $false } If ( $Local ) { [String]$ORInherited = 'False' [String]$ORType = "Local" } ElseIf ( $Effective ) { [String]$ORInherited = 'True' [String]$ORType = "Effective" } Else{ Write-Error "Problem: OR is not Local or Effective! Something wrong with `$hashORResult. WF:[$($WF.Name)] (`$hashORResult[`$WF.Id.Guid].Override)[`$i].Value : " Try {($hashORResult[$WF.Id.Guid].Override)[$i].Value | Format-List} Catch{Write-Error "Unable to display `$hashORResult"} } Try{ $EffectiveORValue = @($hashORResult[$WF.Id.Guid].Override)[$i].Value.EffectiveValue If ($EffectiveORValue -match '^true$|^false$') { $EffectiveORValue = [System.Convert]::ToBoolean($EffectiveORValue) } }Catch { $EffectiveORValue = $null } Try{ [bool]$OverrideSealedMP = [System.Convert]::ToBoolean( $hashAllMPs[($hashORResult[$WF.Id.Guid].Override)[$i].Value.("$($ORType)Override").Identifier.Domain].Sealed) }Catch { Throw "`$OverrideSealedMP : unable to determine OR MP Sealed/Unsealed" } $tempObj.Overrides = [int]($i+1) $tempObj | Add-Member -MemberType NoteProperty -Name "$($i+1)_ORName" -Value ([string]($hashORResult[$WF.Id.Guid].Override[$i].Value.("$($ORType)Override").Name)) $tempObj | Add-Member -MemberType NoteProperty -Name "$($i+1)_ORPropertyName" -Value ([string]($hashORResult[$WF.Id.Guid][$i].PropertyName)) $tempObj | Add-Member -MemberType NoteProperty -Name "$($i+1)_OREffectiveValue" -Value $EffectiveORValue $tempObj | Add-Member -MemberType NoteProperty -Name "$($i+1)_ORInherited" -Value $ORInherited If ([System.Convert]::ToBoolean($ORInherited)) { $tempObj | Add-Member -MemberType NoteProperty -Name "$($i+1)_ORInheritedFrom" -Value ([string](($hashORResult[$WF.Id.Guid].Override)[$i].Value.("$($ORType)Override").Context.Identifier.Path)) } Else { $tempObj | Add-Member -MemberType NoteProperty -Name "$($i+1)_ORInheritedFrom" -Value '(not inherited)' } $tempObj | Add-Member -MemberType NoteProperty -Name "$($i+1)_ORMPName" -Value ([string]($hashORResult[$WF.Id.Guid].Override[$i].Value.("$($ORType)Override").Identifier.Domain)) $tempObj | Add-Member -MemberType NoteProperty -Name "$($i+1)_ORMPIsSealed" -Value $OverrideSealedMP }#end ForEach Override on the WF. OR count default value is 1. Will add unique info for each property that is overridden } #End if override exists Else{ $tempObj | Add-Member -MemberType NoteProperty -Name '1_ORExistsForClass' -Value ($FALSE) $tempObj | Add-Member -MemberType NoteProperty -Name '1_ORName' -Value '' $tempObj | Add-Member -MemberType NoteProperty -Name '1_OREffectiveValue' -Value '' $tempObj | Add-Member -MemberType NoteProperty -Name '1_ORMPName' -Value '' $tempObj | Add-Member -MemberType NoteProperty -Name '1_ORMPIsSealed' -Value '' } $availableWFs += $tempObj }#end ForEach WF... $stopwatch.stop() Write-Progress -Activity "Finding all workflows and related overrides..." -Status "Ready" -Completed If ($availableWFs.Count -eq 0){ LogIt -OutDir $BackupDir -msg "No $($SelectedWFType) found in MP(s). " -B Yellow } # Display the list of WFs for selection. It's important to sort by Overrides here by most Overrides (columns) first (descending) so that all OR property columns are displayed. $SelectedWFs += $availableWFs | Sort-Object -Property Overrides -Descending | Out-GridView -PassThru -Title "$($SelectedWFType)(s): Select one or more workflows, then click 'OK'. " LogIt -OutDir $BackupDir -msg "Selected WF(s):" $SelectedWFs | ForEach-Object { $msg += "$($_ | Out-String)" } LogIt -OutDir $BackupDir -msg $msg } ######################################################## Function Show-About { Write-Host "`n`n$Title" -F Cyan Write-Host "Author:`t`t" -F Gray -NoNewLine; Write-Host "Tyson Paul" -F Green Write-Host "Blog:`t`t" -F Gray -NoNewLine; Write-Host "https://monitoringguys.com/2019/11/12/scomhelper/" -F Blue -B White $summary = @" This tool is designed to make it easy (easier) to manipulate overrides; create overrides (enabled/disable workflows for their target class), delete overrides, and move overrides between unsealed management packs. Most of the time it seems that overrides are stored randomly in various unsealed packs with no system of organization. Ideally normal overrides should be stored in a "buddy pack" directly related to the "base" MP of the affected workflow. "@ Write-Host $summary -F Gray Write-Host "See this article: " -F Gray Write-Host "https://monitoringguys.com/2016/05/24/how-to-correctly-create-an-override-for-a-scom-workflow/" -F Blue -B White Write-Host "`nThese locations might be blank if no backups have been performed yet:" -F Gray Write-Host "`tBackup Directory Root: " -F White -NoNewLine; Write-Host $BackupDir -F Green Write-Host "`tLast Backup location: " -F White -NoNewLine; Write-Host $batchSubDir -F Green Write-Host "`tLogFile: " -F White -NoNewLine; Write-Host $LogFilePath -F Green Write-Host "`n" Write-Host "Connection settings:" Write-Host "$(Get-SCOMManagementGroupConnection | Out-String)" -F Cyan Read-Host "Press any key to resume..." } ######################################################## Function Show-Menu { Param( [System.Object]$Menu ) Write-Host "`n`n$($Title)" -F white -B DarkMagenta If ($MG.IsActive) { If ($MG.UserName) { $UserName = $MG.UserName } Else { $UserName = "$($env:USERDOMAIN)\$($env:USERNAME)" } } Else{ LogIt -msg "No Management Group connection exists. " -Display -OutDir $BackupDir -F Gray } Write-Host "`t`t$($MG.ManagementGroupName) [$($UserName) on $($MG.ManagementServerName)]" -ForegroundColor Gray Write-Host "`t" -NoNewline; Write-Host "Currently Selected:" -F White Write-Host "`t`tSource MP(s):`t`t$($SourceMP.Name.Count)" -F Magenta -NoNewline; Write-Host "`tMPs from which to select overrides/workflows" -F Gray If ([bool]$DestMP.Name){ Write-Host "`t`tDestination MP:`t`t" -F Magenta -NoNewline; Write-Host "$($DestMP.Name) `"$($DestMP.DisplayName)`"" -F Cyan -NoNewline; Write-Host "`tCustom MP in which to store overrides." -F Gray }Else { Write-Host "`t`tDestination MP:`t`t" -F Magenta -NoNewline; Write-Host "<none>`tCustom MP in which to store overrides." -F Gray } Write-Host "`t`tOverride(s):`t`t$($SelectedOR_F.Count)" -F Magenta If ([bool]$SelectedWFs.Count){ Write-Host "`t`tWorkflow(s):`t`t$($SelectedWFs.Count)" -F Magenta -NoNewline; Write-Host "`tType: $($SelectedWFType)(s)" -F Gray } Else { Write-Host "`t`tWorkflow(s):`t`t$($SelectedWFs.Count)" -F Magenta -NoNewline; Write-Host "`tRules/Monitors/Discoveries" -F Gray } 1..$Title.Length | ForEach-Object {Write-Host "-" -NoNewline -F White} Write-Host "" #newline For ($i = 1; $i -le ($Menu.Count); $i++) { Write-Host "$($i):`t" -F Gray -NoNewline; Write-Host $Menu.$i.DN -F $Menu.$i.F ; } 1..$Title.Length | %{Write-Host '-' -F White -NoNewline} Write-Host "" #newline $c = (Read-Host -Prompt "Menu Option") LogIt -OutDir $BackupDir -msg "Menu Action:$($c) - $($Menu.([int]$c).DN) ..." Return $c } ######################################################## Function Show-Selected { Param( [Parameter(Mandatory=$false)] [ValidateSet("source","dest","override","cancel","WF")] [string]$Type ) Switch ($Type) { 'source' {$SourceMP | Out-GridView -Title "These are the currently selected Source MPs. (Close this window to go back) " -PassThru | Format-List } 'dest' {$DestMP | Out-GridView -Title "This is the currently selected Destination MP. (Close this window to go back) " -PassThru | Format-List} 'override' {$SelectedOR_F | Out-GridView -Title "These are the currently selected Overrides. (Close this window to go back) " -PassThru | Format-List} 'WF' {$SelectedWFs | Out-GridView -Title "These are the currently selected Workflows. (Close this window to go back) " -PassThru | Format-List} 'cancel' {Return} Default { $TempMenu = @{} #hash $TempMenuItems = @() #array If ([bool]($SourceMP)) { $TempMenuItems += @{DN="Show Selected Source MP(s)";Action='Show-Selected -Type "Source"';F="Green"} } If ([bool]($DestMP)) { $TempMenuItems += @{DN="Show Selected Destination MP";Action='Show-Selected -Type "Dest"';F="Green"} } If ([bool]($SelectedOR_F)) { $TempMenuItems += @{DN="Show Selected Overrides";Action='Show-Selected -Type "Override"';F="Green"} } If ([bool]($SelectedWFs)) { $TempMenuItems += @{DN="Show Selected Workflows";Action='Show-Selected -Type "WF"';F="Green"} } $TempMenuItems += @{DN="Cancel (go back to main menu)";Action='Show-Selected -Type "Cancel"';F="Gray"} [int]$j=1 $TempMenuItems | ForEach-Object { $TempMenu.$j = $_ $j++ } Do{ $Choice = Get-MenuChoice -Menu $TempMenu # Execute the menu selection Invoke-Expression -Command ". $($TempMenu.($Choice).Action)" }While ($Choice -ne ($TempMenu.Count)) }#Default }#Switch } ######################################################## Function Build-MainMenu { $MainMenu = @{} #hash # Build an array full of menu items. Doesn't really matter what order and they don't have to be enumerated/numbered. Can be reordered at any time. $MenuItems = @() #array $MenuItems += @{DN="Select Source MP(s) --will determine which overrides and workflows can be presented/selected";Action='Select-MPTypeFilter';F="Green"} $MenuItems += @{DN="Select Destination `"Override`" MP --for creating/moving overrides";Action='Select-MPs -Type "Dest"';F="Green"} If ([bool]$SourceMP) { Switch ([string]($SourceMP.Sealed -contains 'TRUE')){ 'True' { $MenuItems += @{DN="Select Override(s) to Move/Delete --Select unsealed source pack(s) only";Action='Cannot select overrides from sealed packs. Select unsealed source pack(s) only';F="DarkGray"} } 'False' { $MenuItems += @{DN="Select Override(s) to Move/Delete";Action='Select-Overrides -MPs $SourceMP';F="Green"} } } #end Switch $MenuItems += @{DN="Select Workflows(s) (Rule/Monitor/Discovery)";Action='Select-Workflows -SourceMP $SourceMP';F="Green"} } # If SourceMps not yet selected Else{ $MenuItems += @{DN="Select Override(s) --select unsealed source MP(s) first.";Action='Write-Host "Must select source MP(s) first." -F Yellow';F="DarkGray"} $MenuItems += @{DN="Select Workflows(s) --select source MP(s) first.";Action='Write-Host "Must select source MP(s) first." -F Yellow';F="DarkGray"} } #if workflows are selected If ( (([bool]$DestMP) -AND ([bool]$SelectedWFs.Count)) ` -OR ((($SelectedWFs.OverrideExists | Where-Object {$_ -like 'True'} ).Count -eq $SelectedWFs.Count) -AND ($SelectedWFs.Count -ge 1)) ) { $MenuItems += @{DN="Enable Workflows(s) --for Target class only";Action='Enable-Workflow -SourceMP $SourceMP -DestMP $DestMP -SelectedWFs $SelectedWFs ';F="Green"} $MenuItems += @{DN="Disable Workflows(s) --for Target class only";Action='Disable-Workflow -SourceMP $SourceMP -DestMP $DestMP -SelectedWFs $SelectedWFs ';F="Green"} } Else { $MenuItems += @{DN="Enable Workflows(s) --select source/destination MP(s) and workflow(s) first.";Action='Write-Host "Must select workflow and destination MP first." ';F="DarkGray"} $MenuItems += @{DN="Disable Workflows(s) --select source/destination MP(s) and workflow(s) first.";Action='Write-Host "Must select workflow and destination MP first."';F="DarkGray"} } If (([bool]$DestMP) -AND ([bool]$SelectedOR_F) ){ $MenuItems += @{DN="Move Override(s)";Action='Move-Override';F="Green"} } Else{ $MenuItems += @{DN="Move Override(s) --select source/destination MP(s) and override(s) first.";Action='Write-Host "You must first select both override(s) AND destination MP for this operation!" -F Red -B Yellow';F="DarkGray"} } If ([bool]$SelectedOR_F){ $MenuItems += @{DN="Delete Override(s)";Action='Delete-Overrides -SelectedOR_F $SelectedOR_F';F="Green"} } Else{ $MenuItems += @{DN="Delete Override(s) --select source MP(s) and override(s) first.";Action='Write-Host "You must first select override(s) for this operation! Go do that first." -F Red -B Yellow';F="DarkGray"} } $MenuItems += @{DN="Delete MP(s)";Action='Delete-MPs';F="Green"} $MenuItems += @{DN="Remove Obsolete References from Unsealed MP(s)";Action='RemoveObsoleteMPRefTask -BackupBeforeModify $true -IncrementVersion $true ';F="Green"} $MenuItems += @{DN="Create New (empty) Override MP";Action='New-MP';F="Green"} $MenuItems += @{DN="Backup All Unsealed MPs";Action='Backup-MP -ALL';F="Green"} If ( ([bool]$SourceMP) -OR ([bool]$SelectedOR_F) -OR (([bool]$DestMP)) -OR ([bool]($SelectedWFs))){ $MenuItems += @{DN="Show Selected Items";Action='Show-Selected';F="Green"} $MenuItems += @{DN="Clear Selected Item(s)";Action='Clear-Selection';F="Green"} } Else { $MenuItems += @{DN="Show Selected Items --select something first.";Action='Write-Host "Nothing is currently selected." -F Yellow';F="DarkGray"} $MenuItems += @{DN="Clear Selected Item(s) --select something first.";Action='Write-Host "Nothing is currently selected." -F Yellow';F="DarkGray"} } $MenuItems += @{DN="Reload all MPs --refresh list of existing MPs";Action='Load-MPs -Type All';F="Green"} #$MenuItems += @{DN="Delete Stale Overrides";Action='Delete-Override -Stale';F="Red"} $MenuItems += @{DN="SDK Connection Settings";Action='Connect-SDK';F="Green"} $MenuItems += @{DN="Help";Action='Show-Help';F="DarkYellow"} $MenuItems += @{DN="About";Action='Show-About';F="DarkYellow"} $MenuItems += @{DN="View Log";Action='Open-LogFile';F="DarkYellow"} $MenuItems += @{DN="Exit";Action='DoExit';F="DarkYellow"} [int]$i=1 $MenuItems | ForEach-Object { $MainMenu.$i = $_ $i++ } } ######################################################## #----------------------------------------------------------------------- #---------------------------- MAIN ----------------------------------- #----------------------------------------------------------------------- Import-SCOMPowerShellModule [bool]$script:DoExitNow = $false [bool]$Reload = $False $DestMP=$null $SelectedOR_F=$null $SourceMP=$null $StaleORNames = @{} $thisScriptName = 'SCOMOverrideTool' $BackupDir = (Join-Path (Join-Path $env:Windir "Temp") (Join-Path 'SCOMHelper' $thisScriptName ) ) $script:DoExit = $false . LogIt -msg "Loading..." -Display -OutDir $BackupDir -F Gray If (-not (. My-NewItem -Type Directory -Path $BackupDir)) { DoExit -msg "Fatal error accessing backup directory. Make sure you have necessary permissions. Run as admin?" } If ( ($MG.Isconnected) -OR ([bool](Get-SCOMManagementGroupConnection)) ) { LogIt -msg "Management Group connection exists. Server: [$((Get-SCOMManagementGroupConnection).ManagementServerName)], ManagementGroupName: [$((Get-SCOMManagementGroupConnection).ManagementGroupName)], IsConnected: [$((Get-SCOMManagementGroupConnection).IsActive)]" -Display -OutDir $BackupDir -F Gray } Do{ . Connect-SDK }While(-NOT ( ($MG.Isconnected) -OR ([bool](Get-SCOMManagementGroupConnection)) )) . Clear-Selection -Type all . Load-SCOMCache -LoadEverything #region MainLoop WHILE ($TRUE -AND (-NOT [bool]$script:DoExit)){ If ($Reload -OR (-NOT([bool]$AllMPs)) ){ . Load-MPs -type 'All' } $Reload=$False . Build-MainMenu $Choice = Get-MenuChoice -Menu $MainMenu # Execute the menu selection Invoke-Expression -Command ". $($MainMenu.($Choice).Action)" } #endregion MainLoop } #END Start-SCOMOverrideTool |