Modifying Explicit Group Membership in SCOM 2012 with PowerShell

This is a continuation of a Data Center Automation series of posts that I have been working on with Anders Bengtsson. Here are the first seven posts in this series:

Creating Management Packs in SCOM 2012 with PowerShell
Creating Performance Collection Rules in SCOM 2012 with PowerShell
Creating Event Based Alerting Rules in SCOM 2012 with PowerShell
Enabling or Disabling Workflows in SCOM 2012 with PowerShell
Deleting Workflows in SCOM 2012 with PowerShell
Creating Groups in SCOM 2012 with PowerShell
Adding References to MPs in SCOM 2012 with PowerShell

*Update v2.0*

Fixed…

  • Group membership being modified but not updating during GroupCalc
  • When all GUIDs are removed from a group it will not save properly
  • When all Membership rules are removed from a group it will not save properly

Improved…

  • XML handling
  • Error handling
  • Script output
  • Reference handling

Added support for…

  • Computer groups
  • Multiple membership rules
  • New management packs
  • New groups
  • Excluded instances (if they are in the UI, then they’ll get added automatically to the any membership rules created by the script)

*Update v2.1*

Improved…

  • New group creation and error handling

As of this post this script is not included as an activity in the Operations Manager Admin Integration Pack but will be in the next version. This was a challenging script to write because group modification requires XML manipulation as there are no methods for managing this. I chose to only support explicit membership with this script because that is likely the most common automation need.

Each group discovery can have multiple membership rules which are really only visible by reviewing the XML of the management pack. This script modifies any of these rules, including rules added via the SCOM Console. If the appropriate membership rule does not exist at runtime, then it will be created and members added. If it does exist then it gets the current explicit members, adds any new members passed, and finally removes any members passed. It then replaces the new old list with the new list while keeping any other membership rules not created with this script intact.

Since the plan is to use this in an integration pack it requires that the instances passed to the script be in GUID format and these GUIDs are validated by the script before being added to the group. You can get the GUIDs by using a simple PowerShell command:

Get-SCOMClass –Name ‘Microsoft.Windows.Computer’ | Get-SCOMClassInstance | select name, id

If you notice after running this script that the new members show up in the console under “Explicit Members” but not when you select “View Group Members” then the UI may just not be up to date. Try the following PowerShell command:

(Get-SCOMGroup –DisplayName ‘My Test Group 1’).GetRelatedMonitoringObjects() | select name, path, id

Syntax:

.\ModifyGroupMembership.ps1 –ManagementServer ‘om01.contoso.com’ –ManagementPackID ‘custom.example.test’ –GroupID ‘custom.example.test.group.testgroup1’ –InstancesToAdd ‘8d5e843d-4d4a-3821-91d1-bc6434d6f9f1, aa390a79-b72a-bf58-412b-ebb94a139e06’ –InstancesToRemove ‘732ac45e-4e1a-9369-c9dd-e3b038537474’

Parameters:

Name Description
ManagementServer Name of MS to connect to
ManagementPackID ID of the MP you want to create or modify
GroupID ID of the group you want to create or modify
InstancesToAdd Optional: GUIDs of the instances you want to add to the group. You can pass multiple by separating each with a  comma.
InstancesToRemove Optional: GUIDs of the instances you want to remove from the group. You can pass multiple by separating each with a  comma.
   1 Param(            
  2     [parameter(Mandatory=$true)]            
  3     $ManagementServer,            
  4     [parameter(Mandatory=$true)]            
  5     $ManagementPackID,            
  6     [parameter(Mandatory=$true)]            
  7     $GroupID,
  8     $InstancesToAdd,
  9     $InstancesToRemove
 10     )
 11 
 12 Write-Host "Version 2.0"
 13 Write-Host "ManagementServer: "$ManagementServer
 14 Write-Host "ManagementPackID: "$ManagementPackID
 15 Write-Host "GroupID: "$GroupID
 16 Write-Host "InstancesToAdd: "$InstancesToAdd
 17 Write-Host "InstancesToRemove: "$InstancesToRemove
 18 
 19 function GetSCOMManagementGroup
 20 {
 21   param($ms)
 22   try
 23   {
 24     $mg = New-Object Microsoft.EnterpriseManagement.ManagementGroup($ms)
 25   }
 26   catch
 27   {
 28     Write-Host "Failed to Connect to SDK, Exiting:"$ms -ForegroundColor Red
 29     Write-Host $_.Exception.Message -ForegroundColor Yellow
 30     exit
 31   }
 32   return $mg
 33 }
 34 
 35 function GetManagementPackToUpdate
 36 {
 37   param($mg, $mpID)
 38   try
 39   {
 40     $mp = $mg.GetManagementPacks($mpID)[0]
 41     $vIncrement = $mp.Version.ToString().Split('.')
 42     $vIncrement[$vIncrement.Length - 1] = ([system.int32]::Parse($vIncrement[$vIncrement.Length - 1]) + 1).ToString()
 43     $mp.Version = ([string]::Join(".", $vIncrement))
 44   }
 45   catch
 46   {
 47     Write-Host "New MP:"$mpID
 48     $mp = CreateManagementPack -mpID $mpID
 49     $mg.ImportManagementPack($mp)
 50     $mp = GetManagementPack -mg $mg -mpID $mpID
 51   }
 52   return $mp
 53 }
 54 
 55 function GetManagementPack
 56 {
 57   param ($mg, $mpID)
 58   try
 59   {
 60     $mp = $mg.GetManagementPacks($mpID)[0]
 61   }
 62   catch
 63   {
 64     Write-Host "Management Pack Not Found, Exiting:"$mpID -ForegroundColor Red
 65     Write-Host $_.Exception.Message -ForegroundColor Yellow
 66     exit
 67   }
 68   return $mp
 69 }
 70 
 71 function CreateManagementPack
 72 {
 73   param($mpID)
 74   $mpStore = New-Object Microsoft.EnterpriseManagement.Configuration.IO.ManagementPackFileStore
 75   $mp = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPack($mpID, $mpID, (New-Object Version(1, 0, 0)), $mpStore)
 76   return $mp
 77 }
 78 
 79 function GetReferenceAlias
 80 {
 81   param($mp, $mpID)
 82   if ($mp.Name.ToUpper() -ne $mpID.ToUpper())
 83   {
 84     $bFound = $false
 85     foreach ($ref in $mp.References)
 86     {
 87       $s = ($ref.Value.ToString().Split("=")[1]).Split(",")[0]
 88       if ($s.ToUpper() -eq $mpID.ToUpper())
 89       {
 90         $bFound = $true
 91         $alias = $ref.Key
 92       }
 93     }
 94     if (!($bFound))
 95     {
 96       Write-Host "MP Reference Not Found, Exiting:"$mpID
 97       exit
 98     }
 99   }
100 
101   return $alias
102 }
103 
104 function ValidateReference
105 {
106   param($mg, $mp, $mpID)
107   if ($mp.Name.ToUpper() -ne $mpID.ToUpper())
108   {
109     $bFound = $false
110     foreach ($ref in $mp.References)
111     {
112       $s = ($ref.Value.ToString().Split("=")[1]).Split(",")[0]
113       if ($s.ToUpper() -eq $mpID.ToUpper()) {$bFound = $true}
114     }
115     if (!($bFound))
116     {
117       Write-Host "New Reference:"$mpID
118       $mp = CreateReference -mg $mg -mp $mp -mpID $mpID
119     }
120   }
121   return $mp
122 }
123 
124 function ValidateReferencesFromInstances
125 {
126   param($mg, $mp, $ht)
127 
128   $htClasses = @{}
129   foreach($instance in $ht.GetEnumerator())
130   {
131     try {$htClasses.Add($instance.Value.ToString(),$instance.Value)} catch {}
132   }
133 
134   foreach($instance in $htClasses.GetEnumerator())
135   {
136     $classMP = GetClassMPFromMG -mg $mg -criteria ([string]::Format("Name = '{0}'", $instance.Value))
137     $mp = ValidateReference -mg $mg -mp $mp -mpID $classMP.Name
138   }
139 
140   return $mp
141 }
142 
143 function CreateReference
144 {
145   param($mg, $mp, $mpID)
146   try
147   {
148     $newMP = $mg.GetManagementPacks($mpID)[0]
149     if (!($newMP.sealed))
150     {
151       Write-Host "MP to reference is not sealed, cannot add reference to"$mpID -ForegroundColor Red
152       Write-Host "Exiting" -ForegroundColor Red
153       Write-Host $_.Exception.Message -ForegroundColor Yellow
154       exit
155     }
156   }
157   catch
158   {
159     Write-Host "Referenced MP Not Found in Management Group, Exiting:"$mpID -ForegroundColor Red
160     Write-Host $_.Exception.Message -ForegroundColor Yellow
161     exit
162   }
163 
164   $alias = $mpID.Replace(".","")
165   $reference = New-Object Microsoft.EnterpriseManagement.Configuration.ManagementPackReference($newMP)
166   $mp.References.Add($alias, $reference)
167   return $mp
168 }
169 
170 function XMLEncode
171 {
172   param([string]$s)
173   $s = $s.Replace("&", "&")
174   $s = $s.Replace("<", "&lt;")
175   $s = $s.Replace(">", "&gt;")
176   $s = $s.Replace('"', "&quot;")
177   $s = $s.Replace("'", "&apos;")
178   return $s.ToString()
179 }
180 
181 function ValidateMonitoringObjects
182 {
183   param($guids, $mg)
184   [hashtable]$ht = @{}
185   $guids = $guids.Split(",")
186   foreach ($guid in $guids)
187   {
188     $guid = $guid.Trim()
189     try
190     {
191       $mo = $mg.GetMonitoringObject($guid)
192       try {$ht.Add($guid, ($mo.FullName).Split(":")[0])} catch {}
193     }
194     catch
195     {
196       try {$ht.Add($guid, 'NOTFOUND')} catch {}
197     }
198   }
199   return $ht
200 }
201 
202 function GetClassMPFromMG
203 {
204   param($mg, $criteria)
205   $searchCriteria = new-object Microsoft.EnterpriseManagement.Configuration.MonitoringClassCriteria($criteria)
206   $class = ($mg.GetMonitoringClasses($searchCriteria))[0]
207   $mp = $class.GetManagementPack()
208   return $mp
209 }
210 
211 function GetRelationshipMPFromMG
212 {
213   param($mg, $criteria)
214   $searchCriteria = new-object Microsoft.EnterpriseManagement.Configuration.MonitoringRelationshipClassCriteria($criteria)
215   $relationship = ($mg.GetMonitoringRelationshipClasses($searchCriteria))[0]
216   $mp = $relationship.GetManagementPack()
217   return $mp
218 }
219 
220 function GetMPElementClass
221 {
222   param($mg, $mp, $class)
223 
224   $criteria = ([string]::Format("Name = '{0}'", $class))
225   $refMP = GetClassMPFromMG -mg $mg -criteria $criteria
226   $alias = ""
227   if ($refMP.Name -ne $mp.Name)
228   {
229     $alias = (GetReferenceAlias -mp $mp -mpID $refMP.Name) + "!"
230   }
231   $mpElement = '$MPElement[Name="{0}{1}"]$' -f $alias, $class
232 
233   return $mpElement
234 }
235 
236 function GetMPElementRelationship
237 {
238   param($mg, $mp, $class, $relationship)
239 
240   if (($relationship.ToString() -eq 'Microsoft.SystemCenter.ComputerGroupContainsComputer') -and ($class.ToString() -ne 'Microsoft.Windows.Computer'))
241   {
242     $mpName = 'System.Library'
243     $relationship = 'System.ConfigItemContainsConfigItem'
244   }
245   else
246   {
247     $criteria = ([string]::Format("Name = '{0}'", $relationship))
248     $mpName = (GetRelationshipMPFromMG -mg $mg -criteria $criteria).Name
249   }
250 
251   $alias = ""
252   if ($mpName -ne $mp.Name)
253   {
254     $alias = (GetReferenceAlias -mp $mp -mpID $mpName) + "!"
255   }
256   $mpElement = '$MPElement[Name="{0}{1}"]$' -f $alias, $relationship
257 
258   return $mpElement
259 }
260 
261 function GetGroup
262 {
263   param($mg, $mp, $groupID)
264   $group = $mg.GetMonitoringClasses($groupID)[0]
265   if ($group -eq $null)
266   { 
267     Write-Host "Group Not Found:"$groupID -ForegroundColor Red
268     $newGroupID = $mp.Name + ($groupID.Split("."))[-1]
269     $group = $mg.GetMonitoringClasses($groupID)[0]
270     if ($group -eq $null)
271     {
272       Write-Host "Group Not Found, Exiting:"$newGroupID -ForegroundColor Red
273       exit
274     }
275   }
276   return $group
277 }
278 
279 function ValidateGroup
280 {
281   param($mg, $mp, $groupID)
282   $group = $mg.GetMonitoringClasses($groupID)[0]
283   if ($group -eq $null)
284   {
285     $groupName = ($groupID.Split("."))[-1]
286     $groupID = $groupName
287     $newGroupID = $mp.Name + "." + $groupID
288     Write-Host "New Group:"$newGroupID
289     $mp = CreateGroup -mg $mg -mp $mp -groupID $groupID -groupName $groupName
290   }
291   return $mp
292 }
293 
294 function CreateGroup
295 {
296   param($mg, $mp, $groupID, $groupName)
297   $mp = ValidateReference -mg $mg -mp $mp -mpID 'Microsoft.SystemCenter.InstanceGroup.Library'
298   $mp = ValidateReference -mg $mg -mp $mp -mpID 'System.Library'
299   $alias = GetReferenceAlias -mp $mp -mpID 'Microsoft.SystemCenter.InstanceGroup.Library'
300   $systemAlias = GetReferenceAlias -mp $mp -mpID 'System.Library'
301   $formula ='<MembershipRule Comment="Empty Membership Rule">' + ` 
302             '<MonitoringClass>$MPElement[Name="' + $alias + `
303             '!Microsoft.SystemCenter.InstanceGroup"]$</MonitoringClass>' + ` 
304             '<RelationshipClass>$MPElement[Name="' + $alias + `
305             '!Microsoft.SystemCenter.InstanceGroupContainsEntities"]$</RelationshipClass>' + ` 
306             '<Expression>' + ` 
307             '<SimpleExpression>' + ` 
308             '<ValueExpression>' + ` 
309             '<Property>$MPElement[Name="' + $systemAlias + `
310             '!System.Entity"]/DisplayName$' + `
311             '</Property>' + ` 
312             '</ValueExpression>' + ` 
313             '<Operator>Equal</Operator>' + ` 
314             '<ValueExpression>' + ` 
315             '<Value>False</Value>' + ` 
316             '</ValueExpression>' + ` 
317             '</SimpleExpression>' + ` 
318             '</Expression>' + `
319             '</MembershipRule>'
320 
321   $group = New-Object Microsoft.EnterpriseManagement.Monitoring.CustomMonitoringObjectGroup($mp.Name, $groupID, (XMLEncode -s $groupName), $formula)
322   $mp.InsertCustomMonitoringObjectGroup($group)
323   return $mp
324 }
325 
326 function CreateEmptyMembershipRule
327 {
328   param($mg, $mp, $relationship, $rules)
329 
330   if ($relationship -eq 'Microsoft.SystemCenter.InstanceGroupContainsEntities')
331   {
332     $class = 'Microsoft.SystemCenter.InstanceGroup'
333   }
334   else
335   {
336     $class = 'Microsoft.Windows.Computer'
337   }
338   
339   $propertyName = '$MPElement[Name="{0}!System.Entity"]/DisplayName$' -f (GetReferenceAlias -mp $mp -mpID 'System.Library')
340 
341   $rulesNode = $rules.SelectSingleNode("/Node1/MembershipRules")
342   $rule = $rules.CreateElement("MembershipRule")
343   [void]$rule.SetAttribute("Comment", "Scripted Membership Rule")
344   [void]$rulesNode.AppendChild($rule)
345 
346   $mClass = $rules.CreateElement("MonitoringClass")
347   $mClass.InnerText = (GetMPElementClass -mg $mg -mp $mp -class $class)
348   [void]$rulesNode.MembershipRule.AppendChild($mClass)
349 
350   $rClass = $rules.CreateElement("RelationshipClass")
351   $rClass.InnerText = (GetMPElementRelationship -mg $mg -mp $mp -class $class -relationship $relationship)
352   [void]$rulesNode.MembershipRule.AppendChild($rClass)
353 
354   $expression = $rules.CreateElement("Expression")
355   [void]$rulesNode.MembershipRule.AppendChild($expression)
356 
357   $eNode = $rules.SelectSingleNode("/Node1/MembershipRules/MembershipRule/Expression")
358   $simpleExpression = $rules.CreateElement("SimpleExpression")
359   [void]$eNode.AppendChild($simpleExpression)
360 
361   $sNode = $rules.SelectSingleNode("/Node1/MembershipRules/MembershipRule/Expression/SimpleExpression")
362   $valueExpression = $rules.CreateElement("ValueExpression")
363   [void]$sNode.AppendChild($valueExpression)
364 
365   $vNode = $rules.SelectSingleNode("/Node1/MembershipRules/MembershipRule/Expression/SimpleExpression/ValueExpression")
366   $property = $rules.CreateElement("Property")
367   $property.InnerText = $propertyName
368   [void]$vNode.AppendChild($property)
369 
370   $operator = $rules.CreateElement("Operator")
371   $operator.InnerText = "Equal"
372   [void]$sNode.AppendChild($operator)
373 
374   $valueExpression = $rules.CreateElement("ValueExpression")
375   [void]$sNode.AppendChild($valueExpression)
376 
377   $vNode = $sNode.ChildNodes[2]
378   $value = $rules.CreateElement("Value")
379   $value.InnerText = "False"
380   [void]$vNode.AppendChild($value)
381 
382   return $rules
383 }
384 
385 function GetGroupDiscovery
386 {
387   param($group)
388   $groupDiscovery = $group.GetMonitoringDiscoveries()[0]
389   if ($groupDiscovery -eq $null)
390   {
391     Write-Host "Group Discovery Not Found, Exiting" -ForegroundColor Red
392     exit
393   }
394   return $groupDiscovery
395 }
396 
397 function GetGroupMembershipRules
398 {
399   param($config)
400   $rulesStart = $config.IndexOf("<MembershipRules>")
401   $rulesEnd = ($config.IndexOf("</MembershipRules>") + 18) - $rulesStart
402   $rules = $config.Substring($rulesStart, $rulesEnd)
403   $rules = '<Node1>' + $rules + '</Node1>'
404   return $rules
405 }
406 
407 function GetGroupInstances
408 {
409   param($mg, $mp, $groupID)
410 
411   $group = GetGroup -mg $mg -mp $mp -groupID $groupID
412   $discovery = GetGroupDiscovery -group $group
413   $configuration = $discovery.DataSource.Configuration
414   $rules = GetGroupMembershipRules -config $configuration
415   $xPath = "/Node1/MembershipRules/MembershipRule/IncludeList/MonitoringObjectId"
416   $guids = Select-Xml -Content $rules -XPath $xPath
417   $existingInstances = @{}
418 
419   foreach($instance in $guids) 
420   { 
421     try {$existingInstances.Add($instance.ToString(),$instance)} catch {}
422   }
423 
424   return $existingInstances
425 }
426 
427 function UpdateGroup
428 {
429   param($mg, $mp, $groupID, $instancesToAdd, $instancesToRemove)
430 
431   $existingInstances = GetGroupInstances -mg $mg -mp $mp -groupID $groupID
432 
433   if ($instancesToAdd -ne $null){$instancesToAdd = ValidateMonitoringObjects -guids $instancesToAdd -mg $mg}
434   else {$instancesToAdd = @{}}
435 
436   $instancesToAdd = RemoveEntriesFromHashTable -guids $instancesToRemove -existingInstances $existingInstances -ht $instancesToAdd
437   $mp = ValidateReferencesFromInstances -mg $mg -mp $mp -ht $instancesToAdd
438 
439   Write-Host "Update MP:"$mp.Name
440   $mp.AcceptChanges()
441   $mp = GetUpdatedGroupDiscovery -mg $mg -mp $mp -groupID $groupID -instancesToAdd $instancesToAdd -instancesToRemove $instancesToRemove
442 
443   return $mp
444 }
445 
446 function GetUpdatedGroupDiscovery
447 {
448   param($mg, $mp, $groupID, $instancesToAdd, $instancesToRemove)
449   $group = GetGroup -mg $mg -mp $mp -groupID $groupID
450   $discovery = GetGroupDiscovery -group $group
451   $configuration = $discovery.DataSource.Configuration
452   $relationship = GetRelationshipType -mg $mg -discovery $discovery
453   [xml]$rules = GetGroupMembershipRules -config $configuration
454   $exclusions = GetExclusions -rules $rules
455   
456   foreach($instance in $instancesToAdd.GetEnumerator())
457   {
458     $rules = AddGUIDToDiscovery -guid $instance.Key.ToString() -mg $mg -mp $mp -class $instance.Value.ToString() -rules $rules -exclusions $exclusions -relationship $relationship
459   }
460 
461   if ($instancesToRemove -ne $null) 
462   {
463     foreach($instance in $instancesToRemove.Split(","))
464     {
465       Write-Host "Delete GUID:"$instance
466       $rules = RemoveGUIDFromDiscovery -guid $instance.Trim() -rules $rules
467     }
468     
469     $rules = DeleteEmptyIncludeNodes -mg $mg -mp $mp -rules $rules -relationship $relationship
470   }
471   
472   $configuration = CleanUpDiscoveryConfiguration -configuration $configuration -rules $rules
473 
474   $discovery.Status = [Microsoft.EnterpriseManagement.Configuration.ManagementPackElementStatus]::PendingUpdate
475   $discovery.DataSource.Configuration = $configuration.ToString().Trim()
476   $mp = $discovery.GetManagementPack()
477   Write-Host "Update MP:"$mp.Name
478   [void]$mp.AcceptChanges()
479   [void]$mg.RefreshMonitoringGroupMembers($mp)
480 
481   return $mp
482 }
483 
484 function DeleteEmptyIncludeNodes
485 {
486   param($mg, $mp, $relationship, $rules)
487   $xPath = "/Node1/MembershipRules/MembershipRule"
488   $nodes = $rules.SelectNodes($xPath)
489 
490   foreach ($node in $nodes)
491   { 
492     if (($node.IncludeList.ChildNodes.Count -eq 0) -and ($node.IncludeList.MonitoringObjectId.Count -eq 0))
493     {
494       if ((($node.SelectSingleNode("Expression")).Name -eq 'Expression') -and (($node.SelectSingleNode("IncludeList")).Name -eq 'IncludeList'))
495       {
496         [void]$node.RemoveChild($node.SelectSingleNode("IncludeList"))
497       }
498       elseif (($node.SelectSingleNode("IncludeList")).Name -eq 'IncludeList')
499       {
500         [void]$node.ParentNode.RemoveChild($node)
501       }
502     }
503   }
504 
505   if ($rules.SelectNodes($xPath).Count -eq 0)
506   {
507     $rules = CreateEmptyMembershipRule -mg $mg -mp $mp -relationship $relationship -rules $rules
508   }
509 
510   return $rules
511 }
512 
513 function CleanUpDiscoveryConfiguration
514 {
515   param($configuration, $rules)
516   
517   $newRules = ($rules.OuterXml).Replace("<Node1>", "").Replace("</Node1>", "")
518   $i = $configuration.IndexOf("<MembershipRules>")
519   [string]$configuration = $configuration.SubString(0, $i) + $newRules
520 
521   return $configuration
522 }
523 
524 function GetRelationshipType
525 {
526   param($mg, $discovery)
527   $id = ($discovery.DiscoveryRelationshipCollection[0].TypeID).ToString().Split("=")[1]
528   $criteria = ([string]::Format("Id = '{0}'", $id))
529   $searchCriteria = new-object Microsoft.EnterpriseManagement.Configuration.MonitoringRelationshipClassCriteria($criteria)
530   $relationship = ($mg.GetMonitoringRelationshipClasses($searchCriteria))[0]
531   return $relationship
532 }
533 
534 function GetExclusions
535 {
536   param($rules)
537   $xPath = "/Node1/MembershipRules/MembershipRule/ExcludeList/MonitoringObjectId"
538   $guids = $rules.SelectNodes($xPath)
539   $exclusions = @{}
540 
541   foreach($instance in $guids) 
542   { 
543     try {$exclusions.Add($instance.InnerText.ToString(),$instance.InnerText.ToString())} catch {}
544   }
545 
546   return $exclusions
547 }
548 
549 function AddGUIDToDiscovery
550 {
551   param($mg, $mp, $guid, $class, $rules, $exclusions, $relationship)
552 
553   $guids = GetIncludedGUIDS -rules $rules
554   
555   if (!($guids.Contains($guid)))
556   {
557     $classes = GetMembershipRuleMonitoringClasses -rules $rules
558     Write-Host "New GUID:"$guid":"$class
559     if ($classes.ContainsKey($class.ToString()))
560     {
561       $rules = AddIncludeGUID -rules $rules -guid $guid -class $classes.Get_Item($class.ToString())
562     }
563     else
564     {
565       Write-Host "New Membership Rule:"$class
566       $rules = CreateNewMembershipRule -mg $mg -mp $mp -rules $rules -guid $guid -class $class -exclusions $exclusions -relationship $relationship
567     }
568   }
569 
570   return $rules
571 }
572 
573 function CreateNewMembershipRule
574 {
575   param($mg, $mp, $guid, $class, $rules, $exclusions, $relationship)
576 
577   [xml]$xml = $rules
578  
579   $rulesNode = $xml.SelectSingleNode("/Node1/MembershipRules")
580   $rule = $xml.CreateElement("MembershipRule")
581   [void]$rule.SetAttribute("Comment", "Scripted Membership Rule")
582   [void]$rulesNode.AppendChild($rule)
583 
584   $mClass = $xml.CreateElement("MonitoringClass")
585   $mClass.InnerText = (GetMPElementClass -mg $mg -mp $mp -class $class)
586   [void]$rulesNode.MembershipRule.AppendChild($mClass)
587 
588   $rClass = $xml.CreateElement("RelationshipClass")
589   $rClass.InnerText = (GetMPElementRelationship -mg $mg -mp $mp -class $class -relationship $relationship)
590   [void]$rulesNode.MembershipRule.AppendChild($rClass)
591 
592   $iList = $xml.CreateElement("IncludeList")
593   [void]$rulesNode.MembershipRule.AppendChild($iList)
594 
595   $count = ($rulesNode.ChildNodes.Count) - 1
596   $includeNode = $rulesNode.ChildNodes[$count].ChildNodes[2]
597   $mObjectId = $xml.CreateElement("MonitoringObjectId")
598   $mObjectId.InnerText = $guid.Trim()
599   [void]$includeNode.AppendChild($mObjectId)
600 
601   if ($exclusions.Count -gt 0)
602   {
603     $eList = $xml.CreateElement("ExcludeList")
604     [void]$rulesNode.MembershipRule.AppendChild($eList)
605 
606     foreach ($guid in $exclusions.GetEnumerator())
607     {
608       $excludeNode = $rulesNode.ChildNodes[$count].ChildNodes[3]
609       $mObjectId = $xml.CreateElement("MonitoringObjectId")
610       $mObjectId.InnerText = $guid.Value.ToString().Trim()
611       [void]$excludeNode.AppendChild($mObjectId)
612     }
613   }
614   
615   return $xml
616 }
617 
618 function AddIncludeGUID
619 {
620   param($guid, $class, $rules)
621 
622   [xml]$xml = $rules
623   $xPath = "/Node1/MembershipRules/MembershipRule"
624   $ruleNodes = $xml.SelectNodes($xPath)
625 
626   foreach ($rule in $ruleNodes)
627   {
628     $className = ($rule.Get_InnerXML()).Split('"')[1]
629     if ($className -eq $class)
630     {
631       $includeNode = $rule.SelectSingleNode("IncludeList")
632       if ($includeNode -ne $null)
633       {
634         $child = $xml.CreateElement("MonitoringObjectId")
635         $child.InnerText = $guid
636         [void]$includeNode.AppendChild($child)
637         break
638       }      
639     }
640   }
641 
642   return $xml
643 }
644 
645 function GetMembershipRuleMonitoringClasses
646 {
647   param($rules)
648 
649   [xml]$xml = $rules
650   $xPath = "/Node1/MembershipRules/MembershipRule"
651   $ruleNodes = $xml.SelectNodes($xPath)
652   $ht = @{}
653 
654   foreach ($rule in $ruleNodes)
655   {
656     $includeNode = $rule.SelectSingleNode("IncludeList")
657     if ($includeNode -ne $null)
658     {
659       $fullPath = ($rule.Get_InnerXML()).Split('"')[1]
660       $class = $fullPath.Split("!")[1]
661       try { $ht.Add($class.ToString(), $fullPath) } catch {}
662     }
663   }
664 
665   return $ht
666 }
667 
668 function GetIncludedGUIDs
669 {
670   param($rules)
671   $xPath = "/Node1/MembershipRules/MembershipRule/IncludeList/MonitoringObjectId"
672   $guids = $guids = $rules.SelectNodes($xPath)
673   $ht = @{}
674   foreach ($g in $guids) { try { $ht.Add($g.InnerText.ToString(), $g.InnerText.ToString()) } catch {} }
675   return $ht
676 }
677 
678 function RemoveGUIDFromDiscovery
679 {
680   param($guid, $rules)
681   $xPath = "/Node1/MembershipRules/MembershipRule/IncludeList[MonitoringObjectId='{0}']/MonitoringObjectId" -f $guid.ToString()
682   $guids = $rules.SelectNodes($xPath)
683 
684   foreach ($g in $guids)
685   {
686     if ($g.InnerText -eq $guid.ToString())
687     {
688       [void]$g.ParentNode.RemoveChild($g)
689     }
690   }
691 
692   return $rules
693 }
694 
695 function RemoveEntriesFromHashTable
696 {
697   param($guids, $existingInstances, $ht)
698   if ($guids -ne $null)
699   {
700     $guids = $guids.Split(",")
701     foreach ($guid in $guids)
702     {
703       $guid = $guid.Trim()
704       try
705       {
706         $ht.Remove($guid)
707       }
708       catch {}
709     }
710   }
711 
712   foreach ($guid in $existingInstances.GetEnumerator())
713   { 
714     if ($ht.ContainsKey($guid.Key.ToString()))
715     {
716       $ht.Remove($guid.Key.ToString())
717     }
718   }
719   return $ht
720 }
721 
722 try { Import-Module OperationsManager } catch
723 { 
724   Write-Host "SCOM Module Not Found, Exiting" -ForegroundColor Red
725   Write-Host $_.Exception.Message -ForegroundColor Yellow
726   exit
727 }
728 
729 $MG = GetSCOMManagementGroup -ms $ManagementServer
730 $MP = GetManagementPackToUpdate -mg $MG -mpID $ManagementPackID
731 $MP = ValidateGroup -mg $MG -mp $MP -groupID $GroupID
732 $MP = UpdateGroup -mg $MG -mp $MP -groupID $GroupID -instancesToAdd $InstancesToAdd -instancesToRemove $InstancesToRemove
733 
734 Write-Host "Script Complete"

ModifyGroupMembership.renametops1

Comments

  • Anonymous
    October 02, 2013
    Any idea why this error is happening whenever I run this script. It happens both just creating the groups and when using the -InstancesToAdd argument. New Reference: Microsoft.SystemCenter.InstanceGroup.Library You cannot call a method on a null-valued expression. At S:ModifyGroupMembership.ps1:112 char:32
  •       $s = ($ref.Value.ToString <<<< ().Split("=")[1]).Split(",")[0]    + CategoryInfo          : InvalidOperation: (ToString:String) [], RuntimeException    + FullyQualifiedErrorId : InvokeMethodOnNull You cannot call a method on a null-valued expression. At S:ModifyGroupMembership.ps1:113 char:21
  •       if ($s.ToUpper <<<< () -eq $mpID.ToUpper()) {$bFound = $true}    + CategoryInfo          : InvalidOperation: (ToUpper:String) [], RuntimeException    + FullyQualifiedErrorId : InvokeMethodOnNull New Reference: System.Library You cannot call a method on a null-valued expression. At S:ModifyGroupMembership.ps1:87 char:32
  •       $s = ($ref.Value.ToString <<<< ().Split("=")[1]).Split(",")[0]    + CategoryInfo          : InvalidOperation: (ToString:String) [], RuntimeException    + FullyQualifiedErrorId : InvokeMethodOnNull You cannot call a method on a null-valued expression. At S:ModifyGroupMembership.ps1:88 char:21
  •       if ($s.ToUpper <<<< () -eq $mpID.ToUpper())    + CategoryInfo          : InvalidOperation: (ToUpper:String) [], RuntimeException    + FullyQualifiedErrorId : InvokeMethodOnNull You cannot call a method on a null-valued expression. At S:ModifyGroupMembership.ps1:87 char:32
  •       $s = ($ref.Value.ToString <<<< ().Split("=")[1]).Split(",")[0]    + CategoryInfo          : InvalidOperation: (ToString:String) [], RuntimeException    + FullyQualifiedErrorId : InvokeMethodOnNull You cannot call a method on a null-valued expression. At S:ModifyGroupMembership.ps1:88 char:21
  •       if ($s.ToUpper <<<< () -eq $mpID.ToUpper())    + CategoryInfo          : InvalidOperation: (ToUpper:String) [], RuntimeException    + FullyQualifiedErrorId : InvokeMethodOnNull MP Reference Not Found, Exiting: Microsoft.SystemCenter.InstanceGroup.Library
  • Anonymous
    October 09, 2013
    Hi, it's hard to tell where the issue is just by the exception. Can you send me more details, such as the existing management pack you're trying to modify? I assume it doesn't happen when you pass in a management pack that doesn't exist? Thanks.

  • Anonymous
    December 04, 2013
    Thanks, this is a great script! I just had to search for the correct input for the parameters ManagementPackID & GroupID. Because the it is slightly different than the Ids i get from the get-scomgroup and get-scommanagementpack .id value.

  • Anonymous
    February 23, 2014
    Some additional clarification on the inputs for "ManagementPackID" and "GroupID" would be appreciated, as the default "ID" properties of the returned objects from "Get-SCOMGroup" and "Get-SCOMManagementPack" don't appear to work.

  • Anonymous
    March 09, 2014
    Hey Greg. In the XML the management pack ID is the name of the MP file itself (minus the file extension). The group ID is under <TypeDefinitions><EntityTypes><ClassType ID="<thegroupid">.

  • Anonymous
    June 13, 2014
    Hi Russ, thank you for the script. Got a few messages, but it did the job. I wonder if the format I'm passing to the script has some casting issue: Method invocation failed because [System.Guid] does not contain a method named 'Split'. At C:UsersadministratorModifyGroupMembership.PS1:185 char:3

  •   $guids = $guids.Split(",")
  •   ~~~~~~~~~~~~~~~~~~~~~~~~~~    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException    + FullyQualifiedErrorId : MethodNotFound Method invocation failed because [System.Guid] does not contain a method named 'Trim'. At C:UsersadministratorModifyGroupMembership.PS1:188 char:5
  •     $guid = $guid.Trim()
  •     ~~~~~~~~~~~~~~~~~~~~    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException    + FullyQualifiedErrorId : MethodNotFound Method invocation failed because [System.Guid] does not contain a method named 'Trim'. At C:UsersadministratorModifyGroupMembership.PS1:188 char:5
  •     $guid = $guid.Trim()
  •     ~~~~~~~~~~~~~~~~~~~~    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException    + FullyQualifiedErrorId : MethodNotFound My Id collection is an array, which seems to work fine, but causes the errors above. Guid                                                                                                                                                                                               ----                                                                                                                                                                                               f25d1a58-53b5-0936-780b-8326e37bcef4                                                                                                                                                               00accda3-17b4-f6b7-e956-adaa17fdc051 That might be something the script could check/handle. Just a suggestion. Thanks again! Jose Fehse
  • Anonymous
    June 16, 2014
    Jose, it looks like the script expects a string, not a GUID. Try putting quotes around the GUIDs when you pass them in (or cast them to a string).Of course you could modify the script to remove that logic as well since you're definitely passing in good data.

  • Anonymous
    October 01, 2014
    Hi Russ, Thank you for the amazing Script.  It's been super useful.  I just wanted to bring this to your attention, as I'm not sure if it's a bug or some issue with the way the group is setup.  Do you have any idea what the cause might be? Exception calling "AcceptChanges" with "0" argument(s): "Verification failed with 1 errors:


Error 1: Found error in 1|QUT.IT.Network.Servers|1.0.0.0|UINameSpace889d6dc7465f4dd691924fc7b9819c6b.Group.DiscoveryRule/GroupPopulationDataSource|| with message: The configuration specified for Module GroupPopulationDataSource is not valid. : Cannot find specified MPElement MicrosoftWindowsLibrary!Microsoft.Windows.Computer in expression: $MPElement[Name="MicrosoftWindowsLibrary!Microsoft.Windows.Computer"]$ Cannot resolve identifier MicrosoftWindowsLibrary!Microsoft.Windows.Computer in the context of management pack QUT.IT.Network.Servers. Unknown alias: MicrosoftWindowsLibrary

" At C:essscriptsSCOMAutomationAutoupdate_NOC_GroupsModifyGroupMembership.ps1:467 char:3

  •   $mp.AcceptChanges()
  •   ~~~~~~~~~~~~~~~~~~~    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException    + FullyQualifiedErrorId : ManagementPackException
  • Anonymous
    October 13, 2014
    Hey Greg, could be an issue but I'm not exactly sure what it is based on the output. It is has to do with the alias in the manifest of the MP though.

  • Anonymous
    January 22, 2015
    The comment has been removed

  • Anonymous
    January 23, 2015
    I unfortunately did not, but this script could easily be launched from Orchestrator, SMA, etc...