Public/New-SCOMComputerGroup.ps1

Function New-SCOMComputerGroup {
  <#
      .DESCRIPTION
      Will create an instance group containing Windows Computers [Microsoft.Windows.Computer] objects and optionally include related Health Service Watchers [Microsoft.SystemCenter.HealthServiceWatcher].
 
      .EXAMPLE
      "ms01.contoso.com","db01.contoso.com" | New-SCOMComputerGroup -MPName "Accounting Team Computer/HSW Group 2020" -NameSpace "Accounting" -Verbose
 
      .EXAMPLE
      $FQDNs = (Get-SCOMAgent | Where-Object Name -like "*DB*").Name
      New-SCOMComputerGroup -MPName "My Database Computers-HSW Group" -ComputerName $FQDNs -NameSpace "DBSERVERS" -AddHealthServiceWatchers -Verbose -PassThru
 
      The above example will get all agent names which match the pattern and add the Computer objects as well as the corresponding Health Service Watcher objects.
 
      .INPUTS
      Accepts array of computer FQDN names (fully qualified domain name)
 
      .OUTPUTS
      SCOM Management Pack [Microsoft.EnterpriseManagement.Configuration.ManagementPack]
 
      .NOTES
      Script: New-SCOMComputerGroup
      Author: Tyson Paul (https://monitoringguys.com/2019/11/12/scomhelper/)
      Version History:
      2020.08.18.1333 - Added MP and Group display name parameters
      2020.08.11.1309 - Polished a bit. HSW objects are optional.
      2020.06.17 - v1.0
  #>


  [CmdletBinding(DefaultParameterSetName='Parameter Set 1',
      SupportsShouldProcess=$false,
      PositionalBinding=$false,
      HelpUri = 'http://www.microsoft.com/',
  ConfirmImpact='Medium')]
  [Alias()]
  [OutputType([Microsoft.EnterpriseManagement.Configuration.ManagementPack])]

  Param (
    # New MP custom name
    [Parameter(Mandatory=$false,
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [string]$MPName='',

    # New MP custom DISPLAY name
    [Parameter(Mandatory=$false,
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [string]$MPDisplayName='',


    # Name of computer to be added to the group (and associated Health Service Watcher instance)
    [Parameter(Mandatory=$true,
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [ValidateNotNull()]
    [ValidateNotNullOrEmpty()]
    [string[]]$ComputerName,

    # Name of mgmt server to use for SDK connection
    [Parameter(Mandatory=$false,
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [string]$MgmtServerName,

    [Parameter(Mandatory=$false,
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [string]$GroupName,

    [Parameter(Mandatory=$false,
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [string]$GroupDisplayName,

    # Namespace for group name. Example: "Lab" or "Accounting". This will affect only the hidden/ugly element "ID" in the MP.
    [Parameter(Mandatory=$false,
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [string]$NameSpace = "GROUP",

    # This will include corresponding Health Service Watcher objects in the group.
    [Parameter(Mandatory=$false,
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [switch]$AddHealthServiceWatchers,

    # This will return the new [Microsoft.EnterpriseManagement.Configuration.ManagementPack] object.
    [Parameter(Mandatory=$false,
        ValueFromPipeline=$false,
        ValueFromPipelineByPropertyName=$false,
        ValueFromRemainingArguments=$false,
    ParameterSetName='Parameter Set 1')]
    [switch]$PassThru

  )

  Begin {

    ###################################### FUNCTIONS #######################################################
    ########################################################################################################
    Function Build-RefString {
      Param (
        $MP
      )
      Return ("$($MP.Name)$($MP.version.tostring())".Replace('.',''))
    }
    ########################################################################################################
    Function Clean-DisplayName {
      [OutputType([String])]
      Param(
        # Param1 help description
        [Parameter(Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$false,
            ValueFromRemainingArguments=$false,
            Position=0,
        ParameterSetName='Parameter Set 1')]
        [string]$Name=''
      )
      Begin{}
      Process{
        Return ($Name -replace '[^a-zA-Z0-9. \-()]', '')
      }
      End {}
    }
    ########################################################################################################
    Function Clean-ID {
      [OutputType([String])]
      Param(
        # Param1 help description
        [Parameter(Mandatory=$false,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$false,
            ValueFromRemainingArguments=$false,
            Position=0,
        ParameterSetName='Parameter Set 1')]
        [string]$Name=''
      )
      Begin{}
      Process{
        $tmp = $Name.Replace(' ','.').Replace('..','.').Replace('..','.')
        $tmp2 = ( ($tmp -replace '[^a-zA-Z0-9.]', '').Replace('..','.').Replace('..','.') )
        Return $tmp2
      }
      End{}
    }
    ########################################################################################################
    Function _LINE_ {
      $MyInvocation.ScriptLineNumber
    }
    ########################################################################################################
    ########################################################################################################
    Function Connect-MgmtGroup {
      # Connect to SCOM mgmt group
      Try{
        $MG = Get-SCOMManagementGroup -ErrorAction Stop -Verbose:$VerbosePreference
        If (-NOT $MG ) {
          Write-Verbose "No SCOMManagementGroupConnection"
          Throw
        }
        ElseIf($MgmtServerName) {
          If ($MG.ConnectionSettings.ServerName -ne $MgmtServerName){
            Write-Verbose "Existing MG connection, wrong server: [$($MG.ConnectionSettings.ServerName)]. Establish connection to new server: [$($MgmtServerName)]..."
            Throw
          }
        }
      } Catch {
        Try {
          $MG = Connect-OMManagementGroup -SDK $MgmtServerName
          If (-NOT $MG) {
            Throw "No SCOMManagementGroupConnection"
          }
        }Catch {
          Write-Error "$(_LINE_): Unable to establish connection to designated mgmt server: [$($MgmtServerName)]. Exiting."
          Exit
        }
      }
    }
    ########################################################################################################

    $DateStamp = (Get-Date -f "yyyyMMdd.hhmmss")

    # Clean names to remove invalid characters
    $CleanMPDisplayName = Clean-DisplayName -Name $MPDisplayName
    $CleanMPID = Clean-ID -Name $MPName
    If (-NOT $CleanMPID) {
      $CleanMPID = Clean-ID -Name $CleanMPDisplayName
    }

    If (-NOT $CleanMPID) {
      Write-Error "No valid MPName provided. Exiting."
      Return
    }

    # Will try to accomodate both group Name and DisplayName
    If ($GroupDisplayName) {
      Write-Verbose "Group DisplayName provided. Will attempt to clean it..."
      $GroupDisplayName = $GroupDisplayName | Clean-DisplayName
    }
    $CleanGroupID = $GroupName | Clean-ID
    If (-NOT $CleanGroupID) {
      Write-Verbose "No valid GroupName. Attempt to use cleaned group DisplayName (if provided)..."
      $CleanGroupID = $GroupDisplayName | Clean-ID
    }
    If (-NOT $CleanGroupID) {
      $CleanGroupID = "$($CleanMPID).$($DateStamp).Group"
      Write-Verbose "No valid GroupName. Will use default group naming: [$($CleanGroupID)]"
    }

    Write-Verbose @"
New MP DisplayName provided by user:[$MPName]
DisplayName after being sanitized:[$($CleanMPDisplayName)]
MPID after being sanitized:[$($CleanMPID)]]
 
"@


    Write-Verbose "Attempt import of OperationsManager module"
    . Import-SCOMPowerShellModule

    Write-Verbose "Verify mgmt group connection..."
    . Connect-MgmtGroup

    Write-Verbose "Get necessary MP objects for building group definitions..."
    $Formula = ''
    # Get the necessary reference MPs
    $LibMp = Get-SCOMManagementPack -Name Microsoft.Windows.Library
    $InsMp = Get-SCOMManagementPack -Name Microsoft.SystemCenter.InstanceGroup.Library
    $SCmp = Get-SCOMManagementPack -Name Microsoft.SystemCenter.Library

    # Build the alias Strings
    $Libalias = Build-RefString -MP $LibMp
    $InsAlias = Build-RefString -MP $InsMp
    $SCAlias = Build-RefString -MP $SCmp

    # Check if the MP already exists if not we will create it
    $MPAlreadyExists = Get-SCOMManagementPack -Name $CleanMPID
    If($MPAlreadyExists)
    {
      Throw @"
 
Error: Management pack with name [$($MPName)] already exists.
New MP DisplayName provided by user:[$MPName]
DisplayName after being sanitized:[$($CleanMPDisplayName)]
MPID after being sanitized:[$($CleanMPID)]]
 
"@

      Try {$MG.Dispose()}Catch{<#Tidy up#>}
      Exit
    }

    [System.Collections.ArrayList]$arrWinComp = @()
    [System.Collections.ArrayList]$arrHSW = @()

    $WinCompClass = Get-SCOMClass -Name "Microsoft.Windows.Computer"
    $HSWClass = Get-SCOMClass -Name "Microsoft.SystemCenter.HealthServiceWatcher"
  }#end Begin

  Process {
    # Create collection of Computers and HSW objects
    ForEach ($CName in $ComputerName) {
      $WinComp = $NULL
      $Criteria = "DisplayName = '$CName'"

      $objCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectCriteria($Criteria ,$WinCompClass)
      $WinComp = $MG.GetMonitoringObjects($objCriteria)
      If (-NOT $WinComp) {
        Write-Warning "Computer: [$($CName)] not found! Verify that the fully qualified domain name exists."
        Continue
      }
      $CompID = $WinComp.Id.Guid
      Write-Verbose "Found Computer:`t[$($CName)], [$($CompID)]"
      $Null = $arrWinComp.Add(" <MonitoringObjectId>$CompID</MonitoringObjectId>")

      If ($AddHealthServiceWatchers) {
        $objCriteria = New-Object Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectCriteria($Criteria ,$HSWClass)
        $HSW = $MG.GetMonitoringObjects($objCriteria)
        $WatchID = $HSW.Id.Guid
        Write-Verbose "Found HSW:`t`t[$($CName)], [$($WatchID)]"
        $Null = $arrHSW.Add(" <MonitoringObjectId>$WatchID</MonitoringObjectId>")
      }
    }
  }#end Process

  End {
    # Add Windows Computer GUIDs to explicit membership rule
    $Formula += @"
  <MembershipRule>
  <MonitoringClass>`$MPElement[Name="$($Libalias)!Microsoft.Windows.Server.Computer"]`$</MonitoringClass>
  <RelationshipClass>`$MPElement[Name="$($InsAlias)!Microsoft.SystemCenter.InstanceGroupContainsEntities"]`$</RelationshipClass>
  <IncludeList>
  $arrWinComp
  </IncludeList>
  </MembershipRule>
"@

    # Add HSW GUIDs to explicit membership rule, if appropriate
    If ($AddHealthServiceWatchers) {
      $Formula += @"
 
  <MembershipRule>
  <MonitoringClass>`$MPElement[Name="$($SCAlias)!Microsoft.SystemCenter.HealthServiceWatcher"]`$</MonitoringClass>
  <RelationshipClass>`$MPElement[Name="$($InsAlias)!Microsoft.SystemCenter.InstanceGroupContainsEntities"]`$</RelationshipClass>
  <IncludeList>
  $arrHSW
  </IncludeList>
  </MembershipRule>
"@

    }

    $store = New-Object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackFileStore

    # Create the MP Object
    Write-Verbose "Create new MP object (virtual)..."
    $MP = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPack($CleanMPID, $CleanMPID, (New-Object Version(1, 0, 0)), $store)

    # Set the MP Display Name
    $MP.DisplayName = $CleanMPDisplayName

    # Add MP references
    Write-Verbose "Add references to MP object..."
    $LibRef = New-object Microsoft.EnterpriseManagement.Configuration.ManagementPackReference($MP, $LibMp.Name, $LibMp.KeyToken, $LibMp.Version)
    $InsRef = New-object Microsoft.EnterpriseManagement.Configuration.ManagementPackReference($MP, $InsMp.Name, $InsMp.KeyToken, $InsMp.Version)
    $SCRef = New-object Microsoft.EnterpriseManagement.Configuration.ManagementPackReference($MP, $SCmp.Name, $SCmp.KeyToken, $SCmp.Version)
    $MP.References.Add($Libalias,$LibRef)
    $MP.References.Add($InsAlias,$InsRef)
    $MP.References.Add($SCAlias,$SCRef)

    Try {
      # Save Changes
      $MP.AcceptChanges()
      Write-Verbose "MP object was verified successfully."
    } Catch {
      Throw "Failed to verify new MP content.`nError:$($_)`nExiting."
      Try {$MG.Dispose()}Catch{<#Tidy up#>}
      Exit
    }

    Try {
      # Import MP
      Write-Verbose "Importing new empty MP into management group: [$($MG.Name)]..."
      $MG.ImportManagementPack($mp)
    } Catch {
      Throw "Failed to import new MP.`nError:$($_)`nExiting."
      Try {$MG.Dispose()}Catch{<#Tidy up#>}
      Exit
    }

    #TYSON $GroupID = "$($CleanMPID).Group"
    $GroupID = "$($CleanGroupID)"
    If ($GroupID -notmatch 'group$') {
      $GroupID = "$($GroupID).Group"
    }

    # Create Group Object and try to insert it into the MP.
    Write-Verbose "Create new group object: [$($GroupID)]..."
    $group = New-Object Microsoft.EnterpriseManagement.Monitoring.CustomMonitoringObjectGroup($NameSpace, $GroupID, $CleanMPID, $Formula)
    If ($GroupDisplayName) {
      $group.DisplayName = $GroupDisplayName
    }
    Try{
      Write-Verbose "Inserting new Group formula into MP..."
      $MP.InsertCustomMonitoringObjectGroup($Group)
      Write-Verbose "Success!"
    }
    Catch{
      Throw "Failed to add group formula to MP.`nError:$($_)`nExiting."
      Try {$MG.Dispose()}Catch{<#Try to tidy up#>}
      Exit
    }

    # Finally, get the new pack
    If ($PassThru) {
      Get-SCOMManagementPack -Name $CleanMPID
    }

  }#end End
}