Start-SCOMOverrideTool.ps1

Function Start-SCOMOverrideTool {
  <#
      .NOTES
      Script: SCOMOverrideTool.ps1
      Author: Tyson Paul ( https://monitoringguys.com/2019/11/12/scomhelper/ )
      Version History:
      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/
 
  #>



  Import-Module -Name OperationsManager,OpsMgrExtended
  'OperationsManager' | ForEach-Object {
    If (-Not [bool](Get-Module -Name $_ -ErrorAction SilentlyContinue )) {
      Write-Error "Required module '$_' does not exist. Please install the module or run this function from a machine where the Operations Manager Console exists. Typically the required module will exists wherever the Console has been installed. Exiting."
      Break
    }
  }
    
  # Make sure OpsmgrExtended is available/loaded
  If (-Not [bool](Get-Module -Name 'OpsMgrExtended' -ErrorAction SilentlyContinue  )) {
    Write-Error "Required module 'OpsMgrExtended' does not exist. Please install module."
    $choice = Read-Host "Install module: OpsMgrExtended (Y/N) ?"
    While ($choice -notmatch '[y]|[n]'){
      $choice = Read-Host "Y/N?"
    }
    Switch ($choice){
      'y' {
        # Install PSGraph from the Powershell Gallery
        Write-Host "Find-Module OpsMgrExtended | Install-Module -Verbose"
        Find-Module OpsMgrExtended | Install-Module -Verbose
        If ($?){
          Write-Host "Module installed!"
          Import-Module OpsMgrExtended
        }
        Else {
          Write-Error "Problem installing module: OpsMgrExtended.
            You may manually download from this location: 'https://www.powershellgallery.com/packages/OpsMgrExtended/1.3.1' or here: 'https://github.com/tyconsulting/OpsMgrExtended-PS-Module'.
            Additional information here: 'http://blog.tyang.org/2015/06/24/automating-opsmgr-part-1-introducing-opsmgrextended-powershell-sma-module/'.
          For information on how to install a PowerShell module, see this article: 'https://docs.microsoft.com/en-us/powershell/developer/module/installing-a-powershell-module'.`nExiting.`n"
;
          Break
        }
      }
      'n' {
        Write-Host "Module will not be installed.`nExiting.`n"
        Break
      }
    }
  }
    

  $Version = "1.02.05"
  $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()
      $batchstamp = Get-Date -Format o | ForEach-Object -Process {$_ -replace ":", "."}
      $batchstamp += "_ALL_UNSEALED"
      . Backup-MP -rootDir $BackupDir -MPs $MPName -Stamp $batchstamp
    }
    ElseIf ([bool]$MPName) {
      LogIt -msg "Getting unsealed MP(s) to backup. This may take a minute..." -OutDir $rootDir

      ForEach ($Name in $MPName) {
        LogIt -msg "Backing up MP [$($mpName)] to [$($batchSubDir)] ..." -OutDir $rootDir
        $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('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-Cache {

    LogIt -msg "Building cache..." -OutDir $BackupDir -Display -F Yellow

    # Cache all classes
    LogIt -msg "Getting all Classes..." -OutDir $BackupDir -Display
    $AllClasses = Get-SCOMClass
    LogIt -msg "$($AllClasses.Count) found." -OutDir $BackupDir -Display
    $hashAllClasses = @{}
    $hashAllClassesID = @{}
    ForEach ($Class in $AllClasses) {
      $hashAllClasses.Add($Class.Name, $Class)
      $hashAllClassesID.Add($Class.ID.GUID,$Class)
    }

    # Cache all monitors
    LogIt -msg "Getting all Monitors..." -OutDir $BackupDir -Display
    $AllMonitors = Get-SCOMMonitor
    LogIt -msg "$($AllMonitors.Count) Monitors found." -OutDir $BackupDir -Display
    $hashAllMonitors = @{}
    ForEach ($Mon in $AllMonitors) {
      $hashAllMonitors.Add($Mon.Id.Guid, $Mon)
    }

    # Cache all rules
    LogIt -msg "Getting all Rules..." -OutDir $BackupDir -Display
    $AllRules = Get-SCOMRule
    LogIt -msg "$($AllRules.Count) Rules found." -OutDir $BackupDir -Display
    $hashAllRules = @{}

    ForEach ($Rule in $AllRules) {
      $hashAllRules.Add($Rule.Id.Guid, $Rule)
    }


    # Cache all discoveries
    LogIt -msg "Getting all Discoveries..." -OutDir $BackupDir -Display
    $AllDiscoveries = Get-SCOMDiscovery
    LogIt -msg "$($AllDiscoveries.Count) Discoveries found." -OutDir $BackupDir -Display
    $hashAllDiscoveries = @{}
    ForEach ($Disc in $AllDiscoveries) {
      $hashAllDiscoveries.Add($Disc.Id.Guid, $Disc)
    }


    # Cache all WORKFLOWS into one big hash table
    $hashAllWFs = @{}
    ForEach ($Key in @($hashAllMonitors.Keys)) {
      $hashAllWFs.Add($Key,$hashAllMonitors[$Key])
    }
    ForEach ($Key in @($hashAllRules.Keys)) {
      $hashAllWFs.Add($Key,$hashAllRules[$Key])
    }
    ForEach ($Key in @($hashAllDiscoveries.Keys)) {
      $hashAllWFs.Add($Key,$hashAllDiscoveries[$Key])
    }

  }

  ########################################################
  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
        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
        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-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 -PassThru -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
  #==============================================================================
  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)
          {
            LogIt -OutDir $BackupDir -Display -msg " - Updating MP Version from $CurrentVersion to $NewVersion" -F White
            $MP.Version = $NewVersion
          }
            
          Try
          {
            $bVerified = $MP.Verify()
          }Catch {
            $bVerified = $false
          }

          #accept changes if MP is verified. otherwise reject changes
          If ($bVerified -eq $false)
          {
            LogIt -OutDir $BackupDir -Display -msg "MP Verify failed. Reject changes." -F Yellow
            $MP.RejectChanges()
          } else {
            LogIt -OutDir $BackupDir -Display -msg "MP Verified. Accepting changes." -F Green
            $MP.AcceptChanges()

            #Increase total updated count only when changes are accepted.
            $iTotalUpdated ++
          }
        } else {
          If ($IncrementVersion -eq $true)
          {
            Write-Host " - The MP version would have been updated from $CurrentVersion to $NewVersion."
            $iTotalUpdated ++
          }
        }            
      }
      Write-Host ""
    }

    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 -PassThru -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) --for selecting overrides, workflows, or delete MP.";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-Module -Name OperationsManager
  Import-Module -Name OpsMgrExtended
  if (-not(Get-Module -Name OpsMgrExtended )){
    Write-Host "`nOpsMgrExtended module not found." -F Yellow -B Red
    Write-Host "Please install the OpsMgrExtended module located at this address:"
    Write-Host "https://www.powershellgallery.com/packages/OpsMgrExtended/1.3.1" -F Blue -B White
    Exit
  }
  [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-Cache


  #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