Private.ps1

#######################################################################
# PRIVATE
#######################################################################

<#
    It is assumed that a management group connection object exists already: $MG
#>

Function Add-MPReference {
  Param (
    $UnsealedMPName,
    $ReferenceMPName, 
    $ReferenceAlias  
  )

  #Get the Reference MP
  Write-Verbose "Getting Reference MP $ReferenceMPName`..."
  $strMPquery = "Name = '$ReferenceMPName' AND Sealed = 'TRUE'"
  $mpCriteria = New-Object  Microsoft.EnterpriseManagement.Configuration.ManagementPackCriteria($strMPquery)
  $RefMP = $MG.GetManagementPacks($mpCriteria)[0]
  If (!$RefMP)
  {
    Write-Error "Unable to find the Reference Sealed MP with the name '$ReferenceMPName'."
    Return $false
  } else {
    $Version = $RefMP.Version
    $KeyToken = $RefMP.KeyToken
    Write-Verbose "Reference MP Version: $Version"
    Write-Verbose "Reference MP Key Token: $KeyToken"
  }

  #Get the destination unsealed MP
  Write-Verbose "Getting Unsealed MP $UnsealedMPName`..."
  $strMPquery = "Name = '$UnsealedMPName' AND Sealed = 'FALSE'"
  $mpCriteria = New-Object  Microsoft.EnterpriseManagement.Configuration.ManagementPackCriteria($strMPquery)
  $DestMP = $MG.GetManagementPacks($mpCriteria)[0]
  If (!$DestMP)
  {
    Write-Error "Unable to find the unsealed MP with the name '$UnsealedMPName'."
    Return $false
  } else {
    Write-Verbose "Adding reference for $ReferenceMPName`..."
    $objMPRef = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackReference($DestMP, $ReferenceMPName, $KeyToken, $Version)

    #Verify and save the monitor
    Write-Verbose "Verifying $UnsealedMPName and save changes..."
    Try {
      $DestMP.References.Add($ReferenceAlias, $objMPRef)
      $DestMP.verify()
      $DestMP.AcceptChanges()
      $Result = $true
      Write-Verbose "MP Reference for sealed MP '$ReferenceMPName' (Alias: $ReferenceAlias; KeyToken: $KeyToken; Version: $Version) added to '$UnsealedMPName'."
    } Catch {
      $Result = $false
      $DestMP.RejectChanges()
      Write-Error "Unable to add MP Reference for $ReferenceMPName (Alias: $ReferenceAlias; KeyToken: $KeyToken; Version: $Version) to $UnsealedMPName."
    }
  }
  $Result
}
#######################################################################
Function Connect-OMManagementGroup
{
  <#
      .Notes
      Author: Tao Yang ( https://blog.tyang.org/ , https://blog.tyang.org/2018/04/18/opsmgrextended-powershell-module-is-now-on-github-and-psgallery/ )
 
      .Synopsis
      Connect to OpsMgr Management Group using SDK
 
      .Description
      Connect to OpsMgr Management Group Data Access Service using SDK
 
      .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 -DLLPath
      Optionally, specify an alternative path to the OpsMgr SDK DLLs if they have not been installed in GAC.
 
      .Example
      # Connect to OpsMgr management group via management server "OpsMgrMS01"
      Connect-OMManagementGroup -SDK "OpsMgrMS01"
 
      .Example
      # Connect to OpsMgr management group via management server "OpsMgrMS01" using different credential
      $Password = ConvertTo-SecureString -AsPlainText "password1234" -force
      $MG = Connect-OMManagementGroup -SDK "OpsMgrMS01" -Username "domain\SCOM.Admin" -Password $Password
 
      .Example
      # Connect to OpsMgr management group via management server "OpsMgrMS01" using current user's credential
      $MG = Connect-OMManagementGroup -SDK "OpsMgrMS01"
      OR
      $MG = Connect-OMManagementGroup -Server "OPSMGRMS01"
 
      .Example
      # Connect to OpsMgr management group using the SMA connection "OpsMgrSDK_TYANG"
      $SDKCOnnection = Get-AutomationConnection "OpsMgrSDK_TYANG"
      $MG = Connect-OMManagementGroup -SDKConnection $SDKConnection
  #>

  [CmdletBinding()]
  PARAM (
    [Parameter(ParameterSetName='SMAConnection',Mandatory=$true,HelpMessage='Please specify the SMA Connection object')][Alias('Connection','c')][Object]$SDKConnection,
    [Parameter(ParameterSetName='IndividualParameter',Mandatory=$true,HelpMessage='Please enter the Management Server name')][Alias('DAS','Server','s')][String]$SDK,
    [Parameter(ParameterSetName='IndividualParameter',Mandatory=$false,HelpMessage='Please enter the user name to connect to the OpsMgr management group')][Alias('u')][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
  )
  If ($SDKConnection)
  {
    $SDK = $SDKConnection.ComputerName
    $Username = $SDKConnection.Username
    $Password = ConvertTo-SecureString -AsPlainText $SDKConnection.Password -force
  }
  #Check User name and password parameter
  If ($Username)
  {
    If (!$Password)
    {
      Write-Error "Password for user name $Username must be specified!"
      Return $null
    }
  }

  #Connect to the management group
  $MGConnSetting = New-Object Microsoft.EnterpriseManagement.ManagementGroupConnectionSettings($SDK)
  If ($Username -and $Password)
  {
    $MGConnSetting.UserName = $Username
    $MGConnSetting.Password = $Password
  }
  $MG = New-Object Microsoft.EnterpriseManagement.ManagementGroup($MGConnSetting)
  $MG
}
#######################################################################
Function New-OMManagementPack
{
  <#
      .Notes
      Author: Tao Yang ( https://blog.tyang.org/ , https://blog.tyang.org/2018/04/18/opsmgrextended-powershell-module-is-now-on-github-and-psgallery/ )
  
      .Synopsis
      Create a new unsealed management pack in an OpsMgr management group.
 
      .Description
      Create a new unsealed management pack in an OpsMgr management group using OpsMgr SDK. A boolean value $true will be returned if the MP 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 -Name
      Management Pack name
 
      .Parameter -DisplayName
      Management Pack display name
 
      .Parameter -Description
      Management Pack description
 
      .Parameter -Version
      Management Pack version
 
      .Example
      # Connect to OpsMgr management group via management server "OpsMgrMS01" and then create an unsealed management pack with the following properties:
      Management Server: "OpsMgrMS01"
      Username: "domain\SCOM.Admin"
      Password "password1234"
      Name: "TYANG.Lab.Test"
      DisplayName: "TYANG Lab Test"
      Version: 1.0.0.0 (default version number)
 
      $Password = ConvertTo-SecureString -AsPlainText "password1234" -force
      $MPCreated = New-OMManagementPack -SDK "OpsMgrMS01" -Username "domain\SCOM.Admin" -Password $Password -Name "TYANG.Lab.Test" -DisplayName "TYANG Lab Test"
 
      .Example
      # Connect to OpsMgr management group via management server "OpsMgrMS01" and then create an unsealed management pack with the following properties:
      OpsMgrSDK Connection (Used in SMA): "OpsMgrSDK_TYANG"
      Name: "TYANG.Lab.Test"
      DisplayName: "TYANG Lab Test"
      Description "Test Managemnet Pack Description"
      Version: 0.0.0.1
 
      $SDKConnection = Get-AutomationConnection -Name OpsMgrSDK_TYANG
      $MPCreated = New-OMManagementPack -SDKConnection $SDKConnection -Name "TYANG.Lab.Test" -DisplayName "TYANG Lab Test" -Description "Test Managemnet Pack Description" -Version "0.0.0.1"
  #>

  [CmdletBinding()]
  PARAM (
    [Parameter(ParameterSetName='SMAConnection',Mandatory=$true,HelpMessage='Please specify the SMA Connection object')][Alias('Connection','c')][Object]$SDKConnection,
    [Parameter(ParameterSetName='IndividualParameter',Mandatory=$true,HelpMessage='Please enter the Management Server name')][Alias('DAS','Server','s')][String]$SDK,
    [Parameter(ParameterSetName='IndividualParameter',Mandatory=$false,HelpMessage='Please enter the user name to connect to the OpsMgr management group')][Alias('u')][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')][String]$Name,
    [Parameter(Mandatory=$true,HelpMessage='Please enter management pack display name')][String]$DisplayName,
    [Parameter(Mandatory=$false,HelpMessage='Please enter management pack description')][String]$Description,
    [Parameter(Mandatory=$false,HelpMessage='Please enter management pack version')][System.Version]$Version="1.0.0.0"
  )

  #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
    }
  }

  $mpStore = New-Object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackFileStore
  $mp = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPack($Name, $DisplayName, $version, $mpStore)
  $mp.DefaultLanguageCode = 'ENU'
  $mpDefaultLanCode = $MP.DefaultLanguageCode
  $LanguagePack = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackLanguagePack($mp, $mpDefaultLanCode)
  $DisplayString = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackDisplayString($mp, $mpDefaultLanCode)
  $Displaystring.Name = $DisplayName
  if ($Description)
  {
    $Displaystring.Description = $Description
  }

  #Add reference to Microsoft.SystemCenter.Library
  Write-Verbose "Adding reference for 'Microsoft.SystemCenter.Library'`..."
  $RefmpCriteria = New-Object  Microsoft.EnterpriseManagement.Configuration.ManagementPackCriteria("Name = 'Microsoft.SystemCenter.Library' AND Sealed = 'TRUE'")
  $RefMP = $MG.GetManagementPacks($RefmpCriteria)[0]
  If (!$RefMP)
  {
    Write-Error "Unable to find the Reference Sealed MP with the name '$ReferenceMPName'."
    Return $false
  } else {
    $RefMPVersion = $RefMP.Version
    $RefMPKeyToken = $RefMP.KeyToken
    Write-Verbose "MP 'Microsoft.SystemCenter.Library' Key Token: $RefMPKeyToken"
    Write-Verbose "MP 'Microsoft.SystemCenter.Library' Version: $RefMPVersion"
  }
  $objMPRef = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackReference($RefMP)
  $mp.References.Add('SystemCenter', $objMPRef)

  #Save the MP
  Write-Verbose "Saving MP $Name"
  Try {
    $mp.verify()
    $mp.acceptchanges()
    $MG.ImportManagementPack($mp)
    $Result = $true
    Write-Verbose "Management Pack $Name successfully created."
  } Catch {
    $Result = $false
    $mp.RejectChanges()
    Write-Error "Failed to create Management Pack $Name."
  }
  $Result
}
########################################################################################################
Function Update-OMGroupDiscovery2
{
  <#
      .Notes
      Author: Tao Yang ( https://blog.tyang.org/ , https://blog.tyang.org/2018/04/18/opsmgrextended-powershell-module-is-now-on-github-and-psgallery/ )
 
      .Synopsis
      Update the group discovery for a computer group or instance group in OpsMgr.
 
      .Description
      Update the group discovery for a computer group or instance group in OpsMgr using OpsMgr SDK. The group discovery must be defined in an unsealed management pack in order for this function to work. A boolean value $true will be returned if the monitoring object has been successfully added, otherwise, a boolean value of $false is returned if any there are any errors occurred during the 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 -GroupName
      The Group name for computer group or instance group
 
      .Parameter -NewConfiguration
      Computer Principal Name for the unsealed MP of which the override is going to stored.
 
      .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 update an instance group by specifying individual parameters:
      Management Server: "OpsMgrMS01"
      Username: "domain\SCOM.Admin"
      Password "password1234"
      Group Name: Test.Instance.Group
      New Configuration: @"
      <RuleId>$MPElement$</RuleId>
      <GroupInstanceId>$MPElement[Name="Group.Creation.Demo.Demo.Instance.Group"]$</GroupInstanceId>
      <MembershipRules>
      <MembershipRule>
      <MonitoringClass>$MPElement[Name="MSV2D!Microsoft.SystemCenter.VirtualMachineManager.2012.HyperVHost"]$</MonitoringClass>
      <RelationshipClass>$MPElement[Name="SCIG!Microsoft.SystemCenter.InstanceGroupContainsEntities"]$</RelationshipClass>
      </MembershipRule>
      </MembershipRules>
      "@
 
      $Password = ConvertTo-SecureString -AsPlainText "password1234" -force
      $NewConfiguration = @'
      <RuleId>$MPElement$</RuleId>
      <GroupInstanceId>$MPElement[Name="Test.Instance.Group"]$</GroupInstanceId>
      <MembershipRules>
      <MembershipRule>
      <MonitoringClass>$MPElement[Name="MSV2D!Microsoft.SystemCenter.VirtualMachineManager.2012.HyperVHost"]$</MonitoringClass>
      <RelationshipClass>$MPElement[Name="SCIG!Microsoft.SystemCenter.InstanceGroupContainsEntities"]$</RelationshipClass>
      </MembershipRule>
      </MembershipRules>
      '@
      Update-OMGroupDiscovery -SDK "OpsMgrMS01" -Username "domain\SCOM.Admin" -Password $Password -GroupName "Test.Instance.Group" -NewConfiguration $NewConfiguration
 
      .Example
      # Connect to OpsMgr management group via management server "OpsMgrMS01" and update an computer group by using a SMA Connection Object:
      OpsMgrSDK Connection (Used in SMA): "OpsMgrSDK_TYANG"
      Instance Group Name: Test.Computer.Group
      New Configuration: @'
      <RuleId>$MPElement$</RuleId>
      <GroupInstanceId>$MPElement[Name="Test.Computer.Group"]$</GroupInstanceId>
      <MembershipRules>
      <MembershipRule Comment="Empty Rule">
      <MonitoringClass>$MPElement[Name="Windows!Microsoft.Windows.Computer"]$</MonitoringClass>
      <RelationshipClass>$MPElement[Name="SystemCenter!Microsoft.SystemCenter.ComputerGroupContainsComputer"]$</RelationshipClass>
      <IncludeList>
      <MonitoringObjectId>8d8e7e81-fa51-5248-6f52-3dd8761238ee</MonitoringObjectId>
      <MonitoringObjectId>1153fce1-9a23-ceee-55c2-bc06bb44aa6b</MonitoringObjectId>
      </IncludeList>
      </MembershipRule>
      </MembershipRules>
      '@
      Increase Management Pack version by 0.0.0.1
   
      $SDKConnection = Get-AutomationConnection -Name OpsMgrSDK_TYANG
      $NewConfiguration = @'
      <RuleId>$MPElement$</RuleId>
      <GroupInstanceId>$MPElement[Name="Test.Computer.Group"]$</GroupInstanceId>
      <MembershipRules>
      <MembershipRule Comment="Empty Rule">
      <MonitoringClass>$MPElement[Name="Windows!Microsoft.Windows.Computer"]$</MonitoringClass>
      <RelationshipClass>$MPElement[Name="SystemCenter!Microsoft.SystemCenter.ComputerGroupContainsComputer"]$</RelationshipClass>
      <IncludeList>
      <MonitoringObjectId>8d8e7e81-fa51-5248-6f52-3dd8761238ee</MonitoringObjectId>
      <MonitoringObjectId>1153fce1-9a23-ceee-55c2-bc06bb44aa6b</MonitoringObjectId>
      </IncludeList>
      </MembershipRule>
      </MembershipRules>
      '@
      Update-OMGroupDiscovery -SDKConnection $SDKConnection -GroupName "Test.Instance.Group" -NewConfiguration $NewConfiguration -IncreaseMPVersion $true
  #>

  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 the group name')][Alias('Group')][System.String]$GroupName,
    [Parameter(Mandatory=$true,HelpMessage='Please enter the new configuration for the group discovery')][Alias('config','Configuration')][System.String]$NewConfiguration,
    [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 group class
  Write-Verbose "Getting the group '$GroupName'."
  $GroupClassCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringClassCriteria("Name='$GroupName'")
  $GroupClass = $MG.GetMonitoringClasses($GroupClassCriteria)[0]
  If ($GroupClass -eq $null)
  {
    Write-Error "$GroupName is not found."
    Return $false
  }

  #Check if this monitoring class is actually an instance group or computer group
  Write-Verbose "Check if the group '$GroupName' is an instance group or a computer group."
  $GroupBaseTypes = $GroupClass.GetBaseTypes()
  $bIsGroup = $false
  Foreach ($item in $GroupBaseTypes)
  {
    If ($item.Id.Tostring() -eq '4ce499f1-0298-83fe-7740-7a0fbc8e2449')
    {
      Write-Verbose "'$GroupName' is an instance group."
      $bIsGroup = $true
    }
    If ($item.Id.Tostring() -eq '0c363342-717b-5471-3aa5-9de3df073f2a')
    {
      Write-Verbose "'$GroupName' is a computer group."
      $bIsGroup = $true
    }
  }
  If ($bIsGroup -eq $false)
  {
    Write-Error "$GroupName is not an instance group or a computer group."
    Return $false
  }

  #Get Group object
  $GroupObject = $MG.GetMonitoringObjects($GroupClass)[0]

  $GroupDiscoveries = $GroupObject.GetMonitoringDiscoveries()
  $iGroupPopDiscoveryCount = 0
  $GroupPopDiscovery = $null
  Foreach ($Discovery in $GroupDiscoveries)
  {
    $DiscoveryDS = $Discovery.DataSource
    #Microsft.SystemCenter.GroupPopulator ID is 488000ef-e20b-1ac4-d3b1-9d679435e1d7
    If ($DiscoveryDS.TypeID.Id.ToString() -eq '488000ef-e20b-1ac4-d3b1-9d679435e1d7')
    {
      #This data source module is using Microsft.SystemCenter.GroupPopulator
      $iGroupPopDiscoveryCount = $iGroupPopDiscoveryCount + 1
      $GroupPopDiscovery = $Discovery
      Write-Verbose "Group Populator discovery found: '$($GroupPopDiscovery.Name)'"
    }
  }
  If ($iGroupPopDiscoveryCount.count -eq 0)
  {
    Write-Error "No group populator discovery found for $GroupName."
    Return $false
  }

  If ($iGroupPopDiscoveryCount.count -gt 1)
  {
    Write-Error "$GroupName has multiple discoveries using Microsft.SystemCenter.GroupPopulator Module type. Unable to continue."
    Return $false
  }

  #Get the MP of where the group populator discovery is defined
  $GroupPopDiscoveryMP = $GroupPopDiscovery.GetManagementPack()
  $GroupPopDiscoveryMPName = $GroupPopDiscoveryMP.Name
  Write-Verbose "The group populator discovery '$($GroupPopDiscovery.Name)' is defined in management pack '$GroupPopDiscoveryMPName'."

  #Write Error and exit if the group discovery MP is sealed
  Write-Verbose "Checking if '$GroupPopDiscoveryMPName' MP is sealed."
  If ($GroupPopDiscoveryMP.sealed -eq $true)
  {
    Write-Error "Unable to update the group discovery because it is defined in a sealed MP: '$($GroupPopDiscoveryMP.DisplayName)'."
    Return $false
  } else {
    Write-Verbose "'$GroupPopDiscoveryMPName' MP is unsealed. OK to continue."
  }

  #Increase MP version
  If ($IncreaseMPVersion)
  {
    $CurrentVersion = $GroupPopDiscoveryMP.Version.Tostring()
    $vIncrement = $CurrentVersion.Split('.')
    $vIncrement[$vIncrement.Length - 1] = ([System.Int32]::Parse($vIncrement[$vIncrement.Length - 1]) + 1).ToString()
    $NewVersion = ([System.String]::Join('.', $vIncrement))
    Write-Verbose "Increasing the group discovery MP version to $NewVersion"
    $GroupPopDiscoveryMP.Version = $NewVersion
  }

  #Update the Group Discovery Data Source configuration
  Write-verbose "Updating the data source configuration for the group discovery '$($GroupPopDiscovery.Name)'."
  Try {
    $GroupPopDiscovery.Datasource.Configuration = $NewConfiguration
    $GroupPopDiscovery.Status = [Microsoft.EnterpriseManagement.Configuration.ManagementPackElementStatus]::PendingUpdate
    $GroupPopDiscoveryMP.AcceptChanges()
    $bGroupUpdated = $true
  } Catch {
    Write-Error $_.Exception.InnerException.Message
    # If the MP update failes, dump the MP so it can be analyzed/debugged.
    Try {
      $SubDir = (Join-Path $BackupDir 'UpdateFailure')
      New-Item -Path $SubDir -ItemType Directory -Verbose -Force
      $GroupPopDiscoveryMP | Export-SCOMManagementPack -Path $SubDir -Verbose -ErrorAction Stop
      Write-Verbose "Backup Directory: [$($SubDir)]"
    } Catch {
      Throw "Unable to output failed MP content to path: [$($SubDir)]. No action taken. Exiting. Error:`n$_"
      Return 
    }
    $bGroupUpdated = $false
  }
  $bGroupUpdated
}
########################################################################################################
Function Load-SCOMCache {
  [CmdletBinding(DefaultParameterSetName='Parameter Set 1', 
      SupportsShouldProcess=$false, 
      PositionalBinding=$false,
      HelpUri = 'https://monitoringguys.com/',
  ConfirmImpact='Medium')]
  Param (
  
    # User can include additional MPs (typically from MP files: xml,mp,mpb )
    [Microsoft.EnterpriseManagement.Configuration.ManagementPack[]]$ManagementPack,
    
    [Parameter(ParameterSetName='Parameter Set 1')]
    [switch]$LoadClasses,

    [Parameter(ParameterSetName='Parameter Set 1')]
    [switch]$LoadRules,

    [Parameter(ParameterSetName='Parameter Set 1')]
    [switch]$LoadMonitors,

    [Parameter(ParameterSetName='Parameter Set 1')]
    [switch]$LoadDiscoveries,

    # Will create hash tables for Classes, Rules, Monitors, Discoveries. Will also combine all Rules,Monitors,Discoveries into a hash.
    [Parameter(ParameterSetName='Parameter Set 2')]
    [switch]$LoadEverything
  )
  Write-Host "Building cache..." -F Yellow
  $hashAllWFs = @{}

  # If MPs are provided, make sure all references are available
  If ($ManagementPack) {
    $hashAllMPs = @{}
    $SealedMPs = Get-SCOMManagementPack | Where-Object -FilterScript {$_.Sealed -eq $True }
    $CombinedMPs = ($ManagementPack + $SealedMPs)
    $CombinedMPs | ForEach-Object {
      Try
      {
        $hashAllMPs.Add($_.Name,$_)
      }
      Catch
      {
        Write-Verbose "Unable to add: $($_.Name), $_"
      }
    }

    ForEach ($Ref in @($ManagementPack.References.Value.Name | Sort-Object -Unique) ) {
      Try {
        $hashAllMPs[$Ref]
      } Catch {
        ForEach ($item in $ManagementPack) {
          If ($Ref -in @($ManagementPack.References.Value.Name)){
            $InvalidMPName = $ManagementPack.Name
          }
        }
        Throw "Reference MP Name:[$Ref] in provided MP:[$($InvalidMPName)] does not exist in mgmt group or in other provided MPs."
      }
    }
  }


  If ($LoadClasses -OR $LoadEverything) {
    # Cache all classes
    Write-Host "Getting all Classes..."
    [System.Collections.ArrayList]$AllClasses = @()
    If ($ManagementPack) {
      $ManagementPack.GetClasses() | ForEach-Object {$NULL = $AllClasses.Add($_) }
    }
    
    (Get-SCOMClass) | ForEach-Object {$NULL = $AllClasses.Add(([Microsoft.EnterpriseManagement.Configuration.ManagementPackClass]$_)) }
    Write-Host "$($AllClasses.Count) found. Proceed to build hash object..."
    $hashAllClasses = @{}
    $hashAllClassesID = @{}
    ForEach ($tmpClass in $AllClasses) {
      Try{
        $hashAllClasses.Add($tmpClass.Name, $tmpClass)
        $hashAllClassesID.Add($tmpClass.ID.GUID,$tmpClass)
      }Catch{
        Write-Verbose "Unable to add $($tmpClass.Name),$($tmpClass.ID.Guid) to hash. $_"
      }
    }
  }

  If ($LoadMonitors -OR $LoadEverything) {
    # Cache all monitors
    Write-Host "Getting all Monitors..."
    If ($ManagementPack) {
      $AllMonitors = (($ManagementPack.GetMonitors()) + (Get-SCOMMonitor) )
    }
    Else {
      $AllMonitors = (Get-SCOMMonitor)
    }
    Write-Host "$($AllMonitors.Count) Monitors found. Proceed to build hash object..."
    $hashAllMonitors = @{}
    ForEach ($Mon in $AllMonitors) {
      Try {
        $hashAllMonitors.Add($Mon.Id.Guid, $Mon)
      }Catch {
        Write-Verbose "Unable to add $($Mon.Name),$($Mon.ID.Guid) to hash. $_"
      }
    }
    ForEach ($Key in @($hashAllMonitors.Keys)) {
      $hashAllWFs.Add($Key,$hashAllMonitors[$Key])
    }
  }

  If ($LoadRules -OR $LoadEverything) {
    # Cache all rules
    Write-Host "Getting all Rules..."
    If ($ManagementPack) {
      $AllRules = ( ($ManagementPack.GetRules()) + (Get-SCOMRule) )
    }
    Else {
      $AllRules = (Get-SCOMRule)
    }
    Write-Host "$($AllRules.Count) Rules found. Proceed to build hash object..."
    $hashAllRules = @{}
    ForEach ($Rule in $AllRules) {
      Try{
        $hashAllRules.Add($Rule.Id.Guid, $Rule)
      } Catch {
        Write-Verbose "Unable to add $($Rule.Name),$($Rule.ID.Guid) to hash. $_"
      }
    }
    ForEach ($Key in @($hashAllRules.Keys)) {
      $hashAllWFs.Add($Key,$hashAllRules[$Key])
    }
  }

  If ($LoadDiscoveries -OR $LoadEverything) {
    # Cache all discoveries
    Write-Host "Getting all Discoveries..."
    If ($ManagementPack) {
      $AllDiscoveries = ( ($ManagementPack.GetDiscoveries()) + (Get-SCOMDiscovery) )
    }
    Else {
      $AllDiscoveries = (Get-SCOMDiscovery)
    }
    Write-Host "$($AllDiscoveries.Count) Discoveries found. Proceed to build hash object..."
    $hashAllDiscoveries = @{}
    ForEach ($Disc in $AllDiscoveries) {
      Try {
        $hashAllDiscoveries.Add($Disc.Id.Guid, $Disc)
      } Catch {
        Write-Verbose "Unable to add $($Disc.Name),$($Disc.ID.Guid) to hash. $_"
      }
    }
    ForEach ($Key in @($hashAllDiscoveries.Keys)) {
      $hashAllWFs.Add($Key,$hashAllDiscoveries[$Key])
    }
  }

}#end Load-SCOMCache

########################################################################################################
Function Import-SCOMPowerShellModule {
  [CmdletBinding(DefaultParameterSetName='Parameter Set 1', 
      SupportsShouldProcess=$false, 
      PositionalBinding=$false,
      HelpUri = 'https://monitoringguys.com/',
  ConfirmImpact='Medium')]
  Param (
  
  )
  Import-Module OperationsManager -Verbose:$VerbosePreference

  # Try to locate OperationsManager PowerShell module location
  If (-NOT ((Get-Module OperationsManager).Count) ) {
    Try {
      $InstallDir = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Powershell\V2").InstallDirectory 
      $SCOMPoshDir = (Join-Path $InstallDir "OperationsManager")
      Import-module $SCOMPoshDir -ErrorAction Stop -Verbose:$VerbosePreference
      If (-NOT ((Get-Module OperationsManager).Count) ) {
        Throw
      }
    } Catch {
      Write-Warning "$(_LINE_): Failed to import OperationsManager module. Exiting"
      Exit
    }
  }
}
########################################################################################################

<#
    .Link
    http://jdhitsolutions.com/blog/2013/01/
    .Inputs
    Object
    .Outputs
    Simulated graph
    .Notes
    Version: 1.0?
    (Tyson: I'm not sure where I found this seemingly early version of this function. However, it seems likely that this is from Jeffery Hicks.)
    Author? : Jeffery Hicks (http://jdhitsolutions.com/blog)
#>

Function Out-ConsoleGraph {

  [CmdletBinding()]
  Param(
    [Parameter(Position=0,
    ValueFromPipeline=$true)]
    [Object]
    $Object,
    [Parameter(Mandatory=$true)]
    [String]
    $Property,
    $Columns
  )

  BEGIN
  {
    $Width = $Host.UI.RawUI.BufferSize.Width
    $Data = @()
  }

  PROCESS
  {
    # Add all of the objects from the pipeline into an array
    $Data += $Object
  }

  END
  {
    # Determine scale of graph
    Try
    {
      $Largest = $Data.$Property | Sort-Object | Select-Object -Last 1
    }

    Catch
    {
      Write-Warning "Failed to find property $Property"
      Return
    }

    if ($Largest)
    {
      # Add the width of all requested columns to each object
      $Data = $Data | Select-Object -Property $Columns | ForEach-Object{
        $Lengths = @()
        $Len = 0
        $Item = $_
        $Columns | ForEach-Object{
          if ($Item.$($_))
          {
            $Len += $Item.$($_).ToString().Length
          }
        }
        Add-Member -InputObject $Item -MemberType NoteProperty -Name Length -Value $Len -PassThru
        $Lengths += $Len
      }

      # Determine the available chart space based on width of all requested columns
      $Sample = $Lengths | Sort-Object -Property Length | Select-Object -Last 1
      [Int]$Longest = $Sample.Length + ($Columns.Count * 33)
      $Available = $Width-$Longest-4

      ForEach ($Obj in $Data)
      {
        # Set bar length to 0 if it is not a number greater than 0
        if ($Obj.$Property -eq '-' -OR $Obj.$Property -eq 0 -or -not $Obj.$Property)
        {
          [Int]$Graph = 0
        }
        else
        {
          $Graph = (($Obj.$Property) / $Largest) * $Available
        }

        # Based on bar size, use a different character to visualize the bar
        if ($Graph -ge 2)
        {
          [String]$G = [char]9608
        }
        elseif ($Graph -gt 0 -AND $Graph -le 1)
        {
          [String]$G = [char]9612
          $Graph = 1
        }

        # Create the property that will contain the bar
        $Char = $G * $Graph
        $Obj | Select-Object -Property $Columns | Add-Member -MemberType NoteProperty -Name Graph -Value $Char -PassThru

      } # End ForEach
    } # End if ($Largest)
  } # End of END block
} # End Out-ConsoleGraph
########################################################################################################

Function Remove-OMOverride
{
  <#
      .Notes
      Author: Tao Yang ( https://blog.tyang.org/ , https://blog.tyang.org/2018/04/18/opsmgrextended-powershell-module-is-now-on-github-and-psgallery/ )
 
      .Synopsis
      Remove an override in OpsMgr.
 
      .Description
      Remove an override in OpsMgr using OpsMgr SDK. A boolean value $true will be returned if the override removal has been successful, otherwise, a boolean value of $false is returned if any there are any errors occurred during the removal 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 -OverrideName
      Override name
 
      .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 delete an override with the following properties:
      Management Server: "OpsMgrMS01"
      Username: "domain\SCOM.Admin"
      Password "password1234"
      Override Name: Test.Performance.Collection.Rule.Interval.Override
      $Password = ConvertTo-SecureString -AsPlainText "password1234" -force
      Remove-OMOverride -SDK "OpsMgrMS01" -Username "domain\SCOM.Admin" -Password $Password -OverrideName Test.Performance.Collection.Rule.Interval.Override
 
      .Example
      # Connect to OpsMgr management group via management server "OpsMgrMS01" and then delete an override with the following properties:
      OpsMgrSDK Connection (Used in SMA): "OpsMgrSDK_TYANG"
      Override Name: Test.Performance.Collection.Rule.Interval.Override
      Increase Management Pack version by 0.0.0.1
   
      $SDKConnection = Get-AutomationConnection -Name OpsMgrSDK_TYANG
      Remove-OMOverride -SDKConnection $SDKConnection -OverrideName Test.Performance.Collection.Rule.Interval.Override -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 override name')][System.String]$OverrideName,
    [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 override
  Write-Verbose "Getting Override $OverrideName"
  $strQuery = "Name = '$OverrideName'"
  $OverrideCriteria = New-Object Microsoft.EnterpriseManagement.Configuration.MonitoringOverrideCriteria($strQuery)
  $Override = $MG.GetMonitoringOverrides($OverrideCriteria)[0]

  If (!$Override)
  {
    Write-Error "Unable to find the override with name $OverrideName"
    Return $false
  }

  Write-Verbose "Getting the override management pack"
  $MP = $Override.GetManagementPack()

  If ($MP.sealed)
  {
    Write-Error "Unable to delete the override $overrideName because it is stored in a sealed management pack. Please create another override in a unsealed management pack to override the parameter again."
    return $false
  }
  $MPName = $MP.Name

  #Deleting the override
  Write-Verbose "Deleting override $OverrideName"
  $Override.Status = "PendingDelete"
    
  #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 deleted from Management Pack '$MPName'($($MP.Version))."
  } Catch {
    $Result = $false
    Write-Error "Unable to create override $OverrideName in management pack $MPName."
    $MP.RejectChanges()
  }
  $Result
}

#######################################################################
# Private, DO NOT EXPORT THIS FUNCTION.
# This will fix the spelling/case of the override Enabled property value (true|false)
Function Set-OverrideEnabledCase {
  Param (
    [string]$ExportPath = 'C:\Temp\UnsealedMPBackup'
  )
  
  $MPs = Get-SCOMManagementPack | Where-Object {$_.Sealed -eq $false} 
  $today = "UnsealedMPBackup_" + (Get-Date -F yyyyMMdd_HHmmss)
  $todaySubDir = (Join-Path $ExportPath $today)
  New-Item $todaySubDir -ItemType Directory -Force -Verbose
  [int]$globalMPsUpdated=0
  [int]$globalproblemsDetected=0

  Try{
    Write-Host "Attempting backup of unsealed MPs to [ $($todaySubDir) ]" -F Cyan
    $MPs | Export-SCOMManagementPack -Path $todaySubDir
  } Catch {
    Write-Error "Unable to backup unsealed MPs. "
  }

  If (-not (Get-ChildItem -Path $todaySubDir -Filter *.xml).Count ){
    Write-Host "Unsealed MPs were not backed up. Continue anyway? (Not recommended)"  -ForegroundColor Yellow -BackgroundColor Red
    $choice = '' 
    While ($choice -notmatch 'y|n'){
      $choice = (Read-Host "Y/N?").ToLower()
    }
    If ($choice -match 'n') { 
      Write-Host "Exiting..."; 
      Return
    }
  }

  Else {
    Write-Host "`n`nFiles backed up..." -F Green
    (Get-ChildItem -Path $todaySubDir -Filter *.xml) | Select-Object fullname
  }

  Write-Host "Proceed to detect/fix overrides `"Enabled`" property value case (true|false) now..." -F Gray
  ForEach ($mp in $MPs) {
    [bool]$updates =$false
    $ORs = $mp.GetOverrides()
    ForEach ($or in $ORs){
      If ($or.Property -eq 'Enabled'){
        Write-Host "Inspecting: $($or.Name), $($or.Property):$($or.Value)" -F Gray
        # If the Value is not all lower case, fix it
        If (($or.Value -match '^true$|^false$') -AND (-NOT ($or.Value -cmatch '^true$|^false$')) ){
          $or.Value = $or.Value.ToLower()
          $or.Status = 'PendingUpdate'
          Write-Host "New Value: $($or.Name), $($or.Property):$($or.Value)" -ForegroundColor Cyan
          $updates = $true
          $globalproblemsDetected++
        }
      }      
    }
    If ($updates ){
      Try {
        Write-Host "Attempt Verify/Accept changes: $($mp.Name), ID:$($mp.ID)" -F Green
        $mp.verify()
        $mp.Acceptchanges()
        $globalMPsUpdated++
      }Catch {
        Write-Error "$($mp.Name), ID:$($mp.ID) Verify/Accept changes failed!"
      }
    }
  }#ForEach mp

  If ($globalproblemsDetected) {
    Write-Host "$globalproblemsDetected problems detected. $globalMPsUpdated MPs updated." -F Green
  }
  Else {
    Write-Host "No problems detected. No changes performed." -F Green
  }
  
} #End Function Set-OverrideEnabledCase


#######################################################################

<#
    .DESCRIPTION
    Will sanitize a string to comply with allowed characters for either Display Name or ID.
    .EXAMPLE
    Clean-SCOMName -FormatType DisplayName -String "This is !@#$%^&*() 1234567890 my new/crazy\random Display Name"
 
    This is () 1234567890 my newcrazyrandom Display Name
 
    .EXAMPLE
    Clean-SCOMName -FormatType Id -String "This is !@#$%^&*() 1234567890 my.. new/crazy\random Display Name..."
 
    Thisis1234567890my.newcrazyrandomDisplayName
 
    .INPUTS
    [string]
    .OUTPUTS
    [string]
    .NOTES
    Author: Tyson Paul (https://monitoringguys.com/)
    Version History:
    2020.07.09 - Original
#>

Function Clean-SCOMName {
  [CmdletBinding(DefaultParameterSetName='Parameter Set 1', 
      SupportsShouldProcess=$false, 
      PositionalBinding=$false,
      HelpUri = 'http://www.microsoft.com/',
  ConfirmImpact='Medium')]
  [Alias()]
  [OutputType([String])]
  Param (
    [string]$String,

    [ValidateSet("ID", "DisplayName")]
    [string]$FormatType

  )
  Switch ($FormatType) {
    "ID" {
      Return ($string -replace '[^a-zA-Z0-9.]', '').Replace('...','.').Replace('..','.').Replace('..','.').Trim('.')
    }

    "DisplayName" {
      Return ($string -replace '[^a-zA-Z0-9. \-()]', '')
    }

    Default {
      Return ($string -replace '[^a-zA-Z0-9.]', '').Replace('...','.').Replace('..','.').Replace('..','.').Trim('.')
    }
  }
}

#######################################################################

#######################################################################
# END PRIVATE
#######################################################################

########################################################################################################
########################################################################################################