Update-SCOMComputerGroup.ps1
<#
.DESCRIPTION Will overwrite an existing instance group or computer group containing Windows Computers [Microsoft.Windows.Computer] objects and optionally include related Health Service Watchers [Microsoft.SystemCenter.HealthServiceWatcher] if the original group type is Microsoft.SystemCenter.InstanceGroup. WARNING: This will replace existing group members with the objects for the provided computer names. This will technically not "add" to the exiting group. .EXAMPLE Update-SCOMComputerGroup -ComputerName 'ms01.contoso.com','ms02.contoso.com' -MgmtServerName ms01.contoso.com -GroupId '4f04c0c7-e3c3-8ef0-a710-1414dd320c5b' -PassThru -AddHealthServiceWatchers -Verbose The above example will update the designated instance group with the two Computers and associated Health Service Watchers. It will prompt for confirmation before updating the existing group membership. It will return the group object. .EXAMPLE # $FQDNs = (Get-SCOMAgent | Where-Object Name -like "*ProdDB*").Name Update-SCOMComputerGroup -ComputerName $FQDNs -MgmtServerName ms01.contoso.com -GroupName 'SQL.Production.SQLServerComputers.Group' -PassThru -Verbose -Force The above example will update the the designated group (instance group or Computer group) with all Computer objects that match the name criteria. . It will not prompt for confirmation before updating the existing group membership. It will return the group object. .INPUTS Accepts array of computer FQDN names (fully qualified domain name) .OUTPUTS SCOM Management Pack [Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectGroup] .NOTES Script: Update-SCOMComputerGroup Author: Tyson Paul (https://monitoringguys.com/2019/11/12/scomhelper/) Version History: 2020.08.18.1703 - initial https://docs.microsoft.com/en-us/system-center/scom/manage-create-manage-groups?view=sc-om-2019 #> Function Update-SCOMComputerGroup { [CmdletBinding(SupportsShouldProcess=$false, PositionalBinding=$false, HelpUri = 'http://www.microsoft.com/', ConfirmImpact='Medium')] [Alias()] [OutputType([Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectGroup])] Param ( # Name of computer to be added to the group (and associated Health Service Watcher instance if used with -AddHealthServiceWatchers parameter) [Parameter(Mandatory=$true, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [string[]]$ComputerName, # Name of mgmt server to use for SDK connection [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false)] [string]$MgmtServerName, # Name of the group (not DisplayName) [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, ParameterSetName='Parameter Set 1')] [string]$GroupName, # DisplayName of the group (not Name ) [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, ParameterSetName='Parameter Set 2')] [string]$GroupDisplayName, # ID/GUID of the group [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, ParameterSetName='Parameter Set 3')] [string]$GroupId, # This will include corresponding Health Service Watcher objects in the group if the group is an 'instance group'. [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false)] [switch]$AddHealthServiceWatchers, # This will return the new group [Microsoft.EnterpriseManagement.Monitoring.MonitoringObjectGroup] object. [Parameter(Mandatory=$false, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false)] [switch]$PassThru, # Will not prompt for confirmation [switch]$Force ) ######################################################################################################## ##################################### FUNCTIONS ####################################################### Function Get-GroupType { Param ( [string]$GroupName ) #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 } Else { #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() Foreach ($item in $GroupBaseTypes) { If ($item.Id.Tostring() -eq '4ce499f1-0298-83fe-7740-7a0fbc8e2449') { Write-Verbose "'$GroupName' is an instance group." Return 'Microsoft.SystemCenter.InstanceGroup' } If ($item.Id.Tostring() -eq '0c363342-717b-5471-3aa5-9de3df073f2a') { Write-Verbose "'$GroupName' is a computer group." Return 'Microsoft.SystemCenter.ComputerGroup' } } # Should never reach this if group is valid Return $false } }#End Function Get-GroupType ######################################################################################################## <# Will return the MP which contains the Discovery for a group. Only if there exists a single discovery datasource for the group. #> Function Get-GroupDiscoveryMP { Param ( [string]$GroupName ) #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." } Return $GroupPopDiscoveryMP } ######################################################################################################## Function Create-ExplicitMembershipConfig { Param ( [Parameter(Mandatory=$true, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, ParameterSetName='Parameter Set 1')] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [array]$ComputerName, [Parameter(Mandatory=$true, ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, ValueFromRemainingArguments=$false, ParameterSetName='Parameter Set 1')] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [string]$GroupName, [string]$RelationshipClass, [string]$WindowsAlias='Windows', [string]$SCAlias='SystemCenter', [string]$InstanceGroupAlias='InstanceGroupLibrary' ) $WinCompClass = Get-SCOMClass -Name "Microsoft.Windows.Computer" $HSWClass = Get-SCOMClass -Name "Microsoft.SystemCenter.HealthServiceWatcher" [System.Collections.ArrayList]$arrWinComp = @() [System.Collections.ArrayList]$arrHSW = @() # Find appropriate Computer/HSW objects, build collections 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>`n") 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>`n") } } # Add Windows Computer GUIDs to explicit membership rule $Rules += @" <MembershipRule> <MonitoringClass>`$MPElement[Name="$($WindowsAlias)!Microsoft.Windows.Server.Computer"]`$</MonitoringClass> $($RelationshipClass) <IncludeList> $arrWinComp </IncludeList> </MembershipRule> "@ # If appropriate, add HSW rules to membership rules If ($arrHSW.Count) { $Rules += @" <MembershipRule> <MonitoringClass>`$MPElement[Name="$($SCAlias)!Microsoft.SystemCenter.HealthServiceWatcher"]`$</MonitoringClass> $($RelationshipClass) <IncludeList> $arrHSW </IncludeList> </MembershipRule> "@ } # Assemble the new configuration $NewConfiguration = @" <RuleId>`$MPElement`$</RuleId> <GroupInstanceId>`$MPElement[Name="$($GroupName)"]`$</GroupInstanceId> <MembershipRules> $($Rules) </MembershipRules> "@ Return $NewConfiguration }#End Funtion Create-ExplicitMembershipConfig ######################################################################################################## Write-Verbose "Attempt import of OperationsManager module" . Import-SCOMPowerShellModule Write-Verbose "Verify mgmt group connection..." $MG = Connect-OMManagementGroup -SDK $MgmtServerName -Verbose:$VerbosePreference $DateStamp = (Get-Date -f "yyyyMMdd.hhmmss") $BackupDir = (Join-Path (Join-Path $env:Windir "Temp") (Join-Path 'SCOMHelper' "Update-SCOMComputerGroup_$($DateStamp)" ) ) If ($GroupId){ $objGroup = (Get-SCOMGroup -Id $GroupId) } If ($GroupName) { $Class = Get-SCOMClass -Name $GroupName $objGroup = (Get-SCOMGroup -DisplayName ($Class.DisplayName)) } If ($GroupDisplayName) { $objGroup = (Get-SCOMGroup -DisplayName $GroupDisplayName) } $GroupName = $objGroup.FullName $GroupPopDiscoveryMP = Get-GroupDiscoveryMP -GroupName $GroupName If (-NOT $GroupPopDiscoveryMP) { Write-Error "Unable to retrieve management pack for group [$GroupName]. Exiting." Return } Try { New-Item -Path $BackupDir -ItemType Directory -Verbose -Force $GroupPopDiscoveryMP | Export-SCOMManagementPack -Path $BackupDir -Verbose -ErrorAction Stop Write-Verbose "Backup Directory: [$($BackupDir)]" } Catch { Throw "Unable to backup existing MP to path: [$($BackupDir)]. No action taken. Exiting. Error:`n$_" Return } <# When defining the new discovery configuration we will need to use a specific alias depending on the group type. Aliases tend to be unique in unsealed MPs. Get unique aliases from existing group MP so they can be used in the new/updated member Configuration rules. #> ForEach ($Key in @($GroupPopDiscoveryMP.References.Keys)){ # Locate "Windows" alias If ( ($GroupPopDiscoveryMP.References[$Key].Name -match '^Microsoft.Windows.Library$' )) { $WindowsAlias = $Key Continue; } # Locate "SystemCenter" alias If ( ($GroupPopDiscoveryMP.References[$Key].Name -match '^Microsoft.SystemCenter.Library$' )) { $SCAlias = $Key Continue; } # Locate "InstanceGroup" alias If ( ($GroupPopDiscoveryMP.References[$Key].Name -match '^Microsoft.SystemCenter.InstanceGroup.Library$' )) { $InstanceGroupAlias = $Key Continue; } } # Determine what type of group this is: InstanceGroup or ComputerGroup $GroupClassType = Get-GroupType -GroupName $GroupName # Construct the appropriate RelationshipClass string. This must agree with the exiting discovery/group type Switch ($GroupClassType){ 'Microsoft.SystemCenter.InstanceGroup' { $RelationshipClass = @" <RelationshipClass>`$MPElement[Name="$($InstanceGroupAlias)!Microsoft.SystemCenter.InstanceGroupContainsEntities"]`$</RelationshipClass> "@ } 'Microsoft.SystemCenter.ComputerGroup' { If ($AddHealthServiceWatchers) { Write-Error "Existing group type is [$($GroupClassType)]. Cannot add Health Service Watchers to exiting group. No Action taken. (Must be 'Microsoft.SstemCenter.InstanceGroup' to add other instance types. Exiting.)" Return } $RelationshipClass = @" <RelationshipClass>`$MPElement[Name="$($SCAlias)!Microsoft.SystemCenter.ComputerGroupContainsComputer"]`$</RelationshipClass> "@ } default { Write-Error "Unknown base class type [$($GroupClassType)] for group [$($GroupName)]. Exiting." Return } } Write-Verbose "Building new configuration..." $NewConfiguration = Create-ExplicitMembershipConfig -ComputerName $ComputerName -GroupName $GroupName -RelationshipClass $RelationshipClass -WindowsAlias $WindowsAlias -SCAlias $SCAlias -InstanceGroupAlias $InstanceGroupAlias Write-Verbose "New Configuration:`n$($NewConfiguration)" $Proceed = $false $r = '' If (-NOT $Force) { While ($r -notmatch 'y|n') { Write-Warning "Warning: This will overwrite existing group." $r = Read-Host 'Proceed? (Y/N)' } If ($r -match 'y') { $Proceed = $true } Else { Write-Warning "No action taken. Exiting." Return } } If ($Force -OR $Proceed) { $result = Update-OMGroupDiscovery2 -SDK $MgmtServerName -GroupName $GroupName -NewConfiguration $NewConfiguration -Verbose:$VerbosePreference Write-Verbose "Group Update succesful?: $result" } # Finally, get the new pack If ($PassThru) { Get-SCOMGroup -Id ($objGroup.Id) -Verbose:$VerbosePreference } }#End Function Update-SCOMComputerGroup |