This post is the 3rd in a series of posts focused on making common administrative tasks in System Center and Azure available via the Service Manager Self-Service Portal. The Configuration Manager Connector pulls a lot of information into Service Manager but not everything necessary to manage collections and collection membership. This solution allows for the synchronizing of Configuration Manager Collections and Collection Members into Service Manager on a schedule and on-demand.
Series
Prerequisites
The scenarios were designed using the following
- System Center Service Manager 2012 R2
- Self-Service Portal configured and working
- Active Directory Connector configured and working
- Configuration Manager Connector configured and working
- Orchestrator Connector configured and working
- System Center Configuration Manager 2012 R2
- Discovery configured and working
- System Center Orchestrator 2012 R2
- SC 2012 Configuration Manager Integration Pack configured and working
- SC 2012 Service Manager Integration Pack configured and working
- Configuration Manager Console installed on runbook servers (open the console, make sure you can connect to your site server)
- Operations Manager Console installed on runbook servers
- Service Manager Console installed on runbook servers
- Runbook servers configured to allow PowerShell scripts to run
Create a service account
- Give the account admin rights to Service Manager
- Give the account admin rights to Configuration Manager
- Give the account admin rights to Operations Manager
Create a share to store scripts and logs
- Create a share that the service account you created and authenticated users will have access to on the Runbook Servers that will be used for this scenario.
- In the share, create a folder called "Automation" and give the service account access to it.
- Copy SyncCollections.ps1 into the Automation Folder
- In the share, create a sub-folder called "Logs" in the Automation Folder and give the applicable administrators access to it. Orchestrator will write logs to this folder and admins can use these logs for troubleshooting.
- In the Logs folder, create a sub-folder called "SRLogs" and give authenticated users access to it. Users of the Service Manager Portal will use these to see the status of the Collection Sync task so they will need rights to this folder.
param ( [Parameter(Mandatory=$true)]$CMSiteCode, [Parameter(Mandatory=$true)]$CMSiteServer, [Parameter(Mandatory=$true)]$SMManagementServer, [Parameter(Mandatory=$true)]$VerboseLogging, [Parameter(Mandatory=$true)]$FullUpdate, [Parameter(Mandatory=$false)]$Collection, [Parameter(Mandatory=$false)]$ServiceRequest )#Functionsfunction LogIt {param ( [Parameter(Mandatory=$true)]$message, [Parameter(Mandatory=$true)]$component, [Parameter(Mandatory=$true)]$type )switch ($type) {1 { $type="Info" }2 { $type="Warning" }3 { $type="Error" }4 { $type="Verbose" } }if (($type-eq"Verbose") -and ($Global:Verbose)) {$toLog="{0} `$$<{1}><{2} {3}><thread={4}>"-f ($type+":"+$message), ($Global:ScriptName+":"+$component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid$toLog| Out-File-Append -Encoding UTF8 -FilePath $Global:LogFile$Global:LogBuffer=$Global:LogBuffer+$toLog+"`r`n" Write-Host $message }elseif ($type-ne"Verbose") {$toLog="{0} `$$<{1}><{2} {3}><thread={4}>"-f ($type+":"+$message), ($Global:ScriptName+":"+$component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid$toLog| Out-File-Append -Encoding UTF8 -FilePath $Global:LogFile$Global:LogBuffer=$Global:LogBuffer+$toLog+"`r`n" Write-Host $message }if (($type-eq 'Warning') -and ($Global:ScriptStatus-ne 'Error')) { $Global:ScriptStatus=$type }if ($type-eq 'Error') { $Global:ScriptStatus=$type } }function CreateServiceRequestLog {param($serviceRequest, $srLogPath) LogIt -message ("Full Log File Path:"+$Global:LogFile) -component "Main()"-type 1if ($serviceRequest) {$srLog= Join-Path $srLogPath ("Logs\SRLogs\"+$serviceRequest+".log") LogIt -message ("Service Request Log File Path:"+$srLog) -component "Main()"-type 1$Global:LogBuffer| Out-File-Append -Encoding UTF8 -FilePath $srLog } }function GetScriptDirectory {$invocation= (Get-Variable MyInvocation -Scope 1).Value Split-Path $invocation.MyCommand.Path }function GetCMSiteConnection {param ($siteCode, $siteServer) try { $CMModulePath= Join-Path -Path (Split-Path -Path "${Env:SMS_ADMIN_UI_PATH}"-ErrorAction Stop) -ChildPath "ConfigurationManager.psd1" } catch { LogIt -message ("Cannot get path to CM console, will try default path: "+$_.Exception.Message) -component "GetCMSiteConnection()"-type 4 LogIt -message ("Trying static path: C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1") -component "GetCMSiteConnection()"-type 4$CMModulePath= 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1' } Import-Module $CMModulePath-ErrorAction Stop try { $CMProvider= Get-PSDrive -PSProvider 'CMSite' -Name $siteCode-ErrorAction Stop } catch { LogIt -message ("Cannot connect to CM site by Site Code, will retry: "+$siteCode+" Error: "+$_.Exception.Message) -component "GetCMSiteConnection()"-type 4 try { $CMProvider= New-PSDrive -PSProvider 'AdminUI.PS.Provider\CMSite' -Name $siteCode-Root $siteServer-Description 'SCCM Site' -ErrorAction Stop} catch { LogIt -message ("Cannot connect to CM site by Server Name, exiting: "+$siteServer+" Error: "+$_.Exception.Message) -component "GetCMSiteConnection()"-type 3 exit } } LogIt -message ("Connected to CM Site: "+$siteCode) -component 'GetCMSiteConnection()' -type 1 CD "$($CMProvider.SiteCode):\"return$CMProvider }function GetSMManagementGroupConnection {param ($computerName)$smDir= (Get-ItemProperty 'hklm:/software/microsoft/System Center/2010/Service Manager/Setup').InstallDirectory try { Import-Module ($smDir+"\Powershell\System.Center.Service.Manager.psd1") -ErrorAction Stop } catch { LogIt -message ("Cannot import SM PowerShell module, Error: "+$_.Exception.Message) -component "GetSMManagementGroupConnection()"-type 3 } try { $SM= New-SCManagementGroupConnection -computerName $computerName-ErrorAction Stop } catch { LogIt -message ("Cannot connect to SM management group: "+$computerName+" Error: "+$_.Exception.Message) -component "GetSMManagementGroupConnection()"-type 3 exit } LogIt -message ("Connected to SM management group: "+$computerName) -component "GetSMManagementGroupConnection()"-type 1return$SM }function GetSCClass {param($name) try { $scClass= Get-SCClass -Name $name } catch { LogIt -message ("Cannot get SM class: "+$name) -component "GetSCClass()"-type 3 } LogIt -message ("Retrieved SM class: "+$name) -component "GetSCClass()"-type 4return$scClass }function GetSCClassInstance {param($class, $filter)if ($filter) { try { $scClassInstance= Get-SCClassInstance -Class $class-Filter$filter } catch { LogIt -message ("Cannot get SM class instance: "+$class+" with filter: "+$filter) -component "GetSCClassInstance()"-type 3 } }else { try { $scClassInstance= Get-SCClassInstance -Class $class } catch { LogIt -message ("Cannot get SM class instance: "+$class) -component "GetSCClassInstance()"-type 3 } } LogIt -message ("Retrieved SM class instance of type: "+$class.Name) -component "GetSCClassInstance()"-type 4return$scClassInstance }function GetSCRelationship {param($name) try { $scRelationship= Get-SCRelationship -Name $name } catch { LogIt -message ("Cannot get SM relationship: "+$name) -component "GetSCRelationship()"-type 3 } LogIt -message ("Retrieved SM relationship: "+$name) -component "GetSCRelationship()"-type 4return$scRelationship }function GetSCRelationshipInstance {param($sourceInstance, $targetInstance)if ($sourceInstance) { try { $scRelationshipInstance= Get-SCRelationshipInstance -SourceInstance $sourceInstance } catch { LogIt -message ("Cannot get source SM relationship instance") -component "GetSCRelationshipInstance()"-type 3 } }else { try { $scRelationshipInstance= Get-SCRelationshipInstance -TargetInstance $targetInstance } catch { LogIt -message ("Cannot get target SM relationship instance") -component "GetSCRelationshipInstance()"-type 3 } } LogIt -message ("Retrieved SM relationship instance") -component "GetSCRelationshipInstance()"-type 4return$scRelationshipInstance }function NewSCDeviceRelationshipInstance {param ($relationshipClass, $source, $target) try { New-SCRelationshipInstance -RelationshipClass $relationshipClass-Source $source-Target $target-ErrorAction Stop } catch { LogIt -message ($target.DisplayName +" cannot be added to "+$source.CollectionID) -component "NewSCDeviceRelationshipInstance()"-type 3 } LogIt -message ($target.DisplayName +" added to "+$source.CollectionID) -component "NewSCDeviceRelationshipInstance()"-type 1 }function RemoveSCDeviceRelationshipInstance {param ($relationship, $collection, $device) try { Remove-SCRelationshipInstance -Instance $relationship-ErrorAction Stop } catch { LogIt -message ($device.PrincipalName +" cannot be removed from "+$collection.CollectionID) -component "RemoveSCDeviceRelationshipInstance()"-type 3 } LogIt -message ($device.PrincipalName +" removed from "+$collection.CollectionID) -component "RemoveSCDeviceRelationshipInstance()"-type 1 }function NewSCUserRelationshipInstance {param ($relationshipClass, $source, $target) try { New-SCRelationshipInstance -RelationshipClass $relationshipClass-Source $source-Target $target-ErrorAction Stop } catch { LogIt -message ($target.UserName +" cannot be added to "+$source.CollectionID) -component "NewSCUserRelationshipInstance()"-type 3 } LogIt -message ($target.UserName +" added to "+$source.CollectionID) -component "NewSCUserRelationshipInstance()"-type 1 Write-Host "ADD"$collection.CollectionID"|"$user.UserName New-SCRelationshipInstance -RelationshipClass $relationship-Source $collection-Target $user }function RemoveSCUserRelationshipInstance {param ($relationship, $collection, $user) try { Remove-SCRelationshipInstance -Instance $relationship-ErrorAction Stop } catch { LogIt -message ($user.UserName +" cannot be removed from "+$collection.CollectionID) -component "RemoveSCUserRelationshipInstance()"-type 3 } LogIt -message ($user.UserName +" removed from "+$collection.CollectionID) -component "RemoveSCUserRelationshipInstance()"-type 1 }function GetCMDeviceCollections { try { $cmDeviceCollections= Get-CMDeviceCollection -ErrorAction Stop } catch { LogIt -message ("Cannot get CM device collections") -component "GetCMDeviceCollections()"-type 3 exit } LogIt -message ("Retrieved CM device collections") -component "GetCMDeviceCollections()"-type 4return$cmDeviceCollections }function GetCMUserCollections { try { $cmUserCollections= Get-CMUserCollection -ErrorAction Stop } catch { LogIt -message ("Cannot get CM user collections") -component "GetCMUserCollections()"-type 3 exit } LogIt -message ("Retrieved CM user collections") -component "GetCMUserCollections()"-type 4return$cmUserCollections }function GetSMCollections {$smCollectionDefinition= GetSCClass -name 'Microsoft.SystemCenter.ConfigurationManager.CollectionInfo'$smCollections= GetSCClassInstance -class $smCollectionDefinition LogIt -message ("Retrieved SM Collections") -component "GetSMCollections()"-type 4return$smCollections }function GetSMCollection {param($collectionID)$smCollectionDefinition= GetSCClass -name 'Microsoft.SystemCenter.ConfigurationManager.CollectionInfo'$smCollection= GetSCClassInstance -class $smCollectionDefinition-filter ('CollectionID -eq {0}' -f$collectionID) LogIt -message ("Retrieved SM Collection: "+$collectionID) -component "GetSMCollection()"-type 4return$smCollection }function GetCMDeviceCollection {param($collectionID) try { $collection= Get-CMDeviceCollection -CollectionID $collectionID-ErrorAction Stop } catch { LogIt -message ("Cannot get CM Device Collection: "+$collectionID) -component "GetCMDeviceCollection"-type 3 }return$collection }function GetCMUserCollection {param($collectionID) try { $collection= Get-CMUserCollection -CollectionID $collectionID } catch { LogIt -message ("Cannot get CM User Collection: "+$collectionID) -component "GetCMUserCollection()"-type 3 }return$collection }function GetCMDeviceCollectionByName {param($collectionName) try { $collection= Get-CMDeviceCollection -Name $collectionName-ErrorAction Stop } catch { LogIt -message ("Cannot get CM Device Collection: "+$collectionName) -component "GetCMDeviceCollectionByName"-type 3 }return$collection }function GetCMUserCollectionByName {param($collectionName) try { $collection= Get-CMUserCollection -Name $collectionName } catch { LogIt -message ("Cannot get CM User Collection: "+$collectionName) -component "GetCMUserCollectionByName()"-type 3 }return$collection }function GetCollectionType {param($collectionID)$collection= GetCMDeviceCollection -collectionID $collectionIDif ($collection) { $collectionType= 'DEVICE' }else {$collection= GetCMUserCollection -collectionID $collectionIDif ($collection) { $collectionType= 'USER' }else { $collectionType= 'NEW' } } LogIt -message ("Retrieved CM Collection: "+$collectionID+" of type: "+$collectionType) -component "GetCollectionType()"-type 4return$collectionType }function GetCollectionTypeByName {param($collectionName)$collection= GetCMDeviceCollectionByName -collectionName $collectionNameif ($collection) { $collectionType= 'DEVICE' }else {$collection= GetCMUserCollectionByName -collectionName $collectionNameif ($collection) { $collectionType= 'USER' }else { $collectionType= 'NEW' } } LogIt -message ("Retrieved CM Collection: "+$collectionName+" of type: "+$collectionType) -component "GetCollectionTypeByName()"-type 4return$collectionType }function GetCollectionID {param($collectionName)$type= GetCollectionTypeByName -collectionName $collectionNameif ($type-eq 'DEVICE') {$collection= GetCMDeviceCollectionByName -collectionName $collectionName }else {$collection= GetCMUserCollectionByName -collectionName $collectionName }return$collection.CollectionID }function GetCollIDs {param ($smCollections)$collIDs=@{}$collIDs.Add(0,0)foreach ($smCollectionin$smCollections) { try {$collIDs.Add($smCollection.CollID, $smCollection.CollID)} catch {} } LogIt -message ("Retrieved SM Collection Ids") -component "GetCollIDs()"-type 4return$collIDs }function GetNextCollID {$collIDs=$Global:CollIDs.GetEnumerator() | Sort-Object Nameforeach ($idin$collIDs) { $i=$id.Key }$i++$Global:CollIDs.Add($i, $i) LogIt -message ("Retrieved Next Collection ID: "+$i) -component "GetNextCollID()"-type 4return$i }function UpdateCollections {param ($cmDeviceCollections, $cmUserCollections, $smCollections) LogIt -message ("Entering UpdateCollections()") -component "UpdateCollections()"-type 4$htCM=@{}$htSM=@{}$htToUpdate=@{}#Put CM device collections in hash tableforeach ($cmDeviceCollectionin$cmDeviceCollections) { $htCM.Add($cmDeviceCollection.CollectionID, $cmDeviceCollection.Name) }#Add CM user collections to hash tableforeach ($cmUserCollectionin$cmUserCollections) { $htCM.Add($cmUserCollection.CollectionID, $cmUserCollection.Name) }#Put SM collections in hash tableforeach ($smCollectionin$smCollections) { $htSM.Add($smCollection.CollectionID, $smCollection.Name) }#Find collection to addforeach ($cmCollin$htCM.GetEnumerator()) {if (!($htSM.ContainsKey($cmColl.Key))) { $htToUpdate.Add($cmColl.Key, $cmColl.Value) } }#Find collections to removeforeach ($smCollin$htSM.GetEnumerator()) {if (!($htCM.ContainsKey($smColl.Key))) { $htToUpdate.Add($smColl.Key, 'REMOVE') } }#Update collectionsforeach ($collectionin$htToUpdate.GetEnumerator()) {if ($collection.Value -eq 'REMOVE') { DeleteCollection -collectionID $collection.Key }else { CreateCollection -collectionID $collection.Key -collectionName $collection.Value } } LogIt -message ("Leaving UpdateCollections()") -component "UpdateCollections()"-type 4 }function CreateCollection {param($collectionID, $collectionName)#Populate Collection$ht=@{}$ht.Add('Name', $collectionName)$ht.Add('DisplayName', $collectionName)$ht.Add('MemberCount', 0)$ht.Add('CollectionID', $collectionID)$ht.Add('CollID', (GetNextCollID))#Add Collection Instance$collection= GetSCClass -name 'Microsoft.SystemCenter.ConfigurationManager.CollectionInfo' try { New-SCClassInstance -Class $collection-Property $ht-ErrorAction Stop} catch { LogIt -message ("Cannot create new SM Collection: "+$collectionName+"|"+$collectionID) -component "CreateCollection()"-type 3 } LogIt -message ("SM Collection Created: "+$collectionName+"|"+$collectionID) -component "CreateCollection()"-type 1 }function DeleteCollection {param($collectionID)$collectionDefinition= GetSCClass -name 'Microsoft.SystemCenter.ConfigurationManager.CollectionInfo' try { Remove-SCClassInstance -Instance (GetSCClassInstance -class $collectionDefinition-filter ('CollectionID -eq {0}' -f$collectionID)) -ErrorAction Stop } catch { LogIt -message ("Cannot delete SM Collection: "+$collectionID) -component "DeleteCollection()"-type 3 } LogIt -message ("SM Collection Deleted: "+$collectionID) -component "DeleteCollection()"-type 1 }function UpdateCollectionMembers {param($fullUpdate, $collection, $cmDeviceCollections, $cmUserCollections)if ($collection) { UpdateSingleCollection -collectionID (GetCollectionID -collectionName $collection) }else {if ($fullUpdate) { FullUpdate -smCollections (GetSMCollections) -cmDeviceCollections $cmDeviceCollections-cmUserCollections $cmUserCollections }else { DeltaUpdate -smCollections (GetSMCollections) -cmDeviceCollections $cmDeviceCollections-cmUserCollections $cmUserCollections } } }function UpdateSingleCollection {param($collectionID) LogIt -message "Entering UpdateSingleCollection()"-component "UpdateSingleCollection()"-type 4$htToUpdate=@{ $collectionID=$collectionID } LogIt -message "Starting Single Collection Sync"-component "UpdateSingleCollection()"-type 1 SyncCollections -collectionList $htToUpdate LogIt -message "Done with Single Collection Sync"-component "UpdateSingleCollection()"-type 1 }function DeltaUpdate {param ($cmDeviceCollections, $cmUserCollections, $smCollections) LogIt -message "Entering DeltaUpdate()"-component "DeltaUpdate()"-type 4$htCM=@{}$htSM=@{}$htToUpdate=@{}#Put CM device collections in hash tableforeach ($cmDeviceCollectionin$cmDeviceCollections) { $htCM.Add($cmDeviceCollection.CollectionID, $cmDeviceCollection.MemberCount) }#Add CM user collections to hash tableforeach ($cmUserCollectionin$cmUserCollections) { $htCM.Add($cmUserCollection.CollectionID.ToString(), $cmUserCollection.MemberCount) }#Put SM collections in hash tableforeach ($smCollectionin$smCollections) { $htSM.Add($smCollection.CollectionID.ToString(), $smCollection.MemberCount) }#Find collections with mismatched MemberCountforeach ($collectionin$htCM.GetEnumerator()) {if ($collection.Value -ne ($htSM.Get_Item($collection.Key)).Value) {$htToUpdate.Add($collection.Key, $collection.Key) } }#Update collections LogIt -message "Starting Delta Sync"-component "DeltaUpdate()"-type 1 SyncCollections -collectionList $htToUpdate LogIt -message "Done with Delta Sync"-component "DeltaUpdate()"-type 1 }function FullUpdate {param ($cmDeviceCollections, $cmUserCollections, $smCollections) LogIt -message "Entering FullUpdate()"-component "FullUpdate()"-type 4$htCM=@{}#Put CM device collections in hash tableforeach ($cmDeviceCollectionin$cmDeviceCollections) { $htCM.Add($cmDeviceCollection.CollectionID, $cmDeviceCollection.CollectionID) }#Add CM user collections to hash tableforeach ($cmUserCollectionin$cmUserCollections) { $htCM.Add($cmUserCollection.CollectionID, $cmUserCollection.CollectionID) }#Update collections LogIt -message "Starting Full Sync"-component "FullUpdate()"-type 1 SyncCollections -collectionList $htCM LogIt -message "Done with Full Sync"-component "FullUpdate()"-type 1 }function UpdateMemberCounts { LogIt -message ("Updating SM Collection Member Counts") -component "UpdateMemberCounts()"-type 1foreach ($collectionin (GetSMCollections)) { try { $count=$collection.GetRelatedObjectsWhereSource((GetSCRelationship -name 'Microsoft.SystemCenter.ConfigurationManager.CollectionHasConfigItem')).Count } catch { LogIt -message ("Cannot get related objects for "+$collection.CollectionID) -component "UpdateMemberCounts()"-type 3 }$collection.MemberCount =$count try { Update-SCClassInstance -Instance $collection-ErrorAction Stop } catch { LogIt -message ("Cannot update SM collection member count for collection: "+$collection.CollectionID +" to member count of: "+$count) -component "UpdateMemberCounts()"-type 3 } LogIt -message ("Updated member count for collection "+$collection.CollectionID +" to count of "+$count) -component "UpdateMemberCounts()"-type 4 } LogIt -message ("Done Updating SM Collection Member Counts") -component "UpdateMemberCounts()"-type 1 }function SyncCollections {param($collectionList) LogIt -message ("Entering SyncCollections()") -component "SyncCollections()"-type 4#Get SM Definitions$collectionDefinition= GetSCClass -Name 'Microsoft.SystemCenter.ConfigurationManager.CollectionInfo'$relationshipDefinition= GetSCRelationship -name 'Microsoft.SystemCenter.ConfigurationManager.CollectionHasConfigItem'$deviceDefinition= GetSCClass -name 'Microsoft.Windows.Computer'$userDefinition= GetSCClass -name 'Microsoft.AD.User'#Put all SM devices in hash table$htAllSMDevices=@{}$deviceInstances= GetSCClassInstance -class $deviceDefinitionforeach ($deviceInstancein$deviceInstances) { $htAllSMDevices.Add($deviceInstance.NetbiosComputerName.ToUpper() +"."+$deviceInstance.NetbiosDomainName.ToUpper(), $deviceInstance.NetbiosComputerName +"."+$deviceInstance.NetbiosDomainName) }#Put all SM users in hash table$htAllSMUsers=@{}$userInstances= GetSCClassInstance -class $userDefinitionforeach ($userInstancein$userInstances) { $htAllSMUsers.Add($userInstance.Domain.ToUpper() +"\"+$userInstance.UserName.ToUpper(), $userInstance.Domain +"\"+$userInstance.UserName ) }#Loop through collections that have changedforeach ($collectionin$collectionList.GetEnumerator()) {if ((GetCollectionType -collectionID $collection.Key) -eq 'DEVICE') {#Put CM collection devices in hash table$htCMCollectionDevices=@{}$cmDevices= Get-CMDevice -CollectionId ((Get-CMDeviceCollection -CollectionId $collection.Key).CollectionID)foreach ($cmDevicein$cmDevices) { $htCMCollectionDevices.Add(($cmDevice.Name +"."+$cmDevice.Domain).ToUpper(), $cmDevice.Name +"."+$cmDevice.Domain) }#Put SM collection devices in hash table$htSMCollectionDevices=@{}$smCollectionInstance= GetSCClassInstance -class $collectionDefinition-filter ('CollectionID -eq"{0}"' -f$collection.Key)$smRelationships= GetSCRelationshipInstance -sourceInstance $smCollectionInstanceforeach ($smRelationshipin$smRelationships) { if (!($smRelationship.IsDeleted)) {$key= ("{0}.{1}"-f$smRelationship.TargetObject.Values[2].ToString().ToUpper(), $smRelationship.TargetObject.Values[3]).ToUpper()$value= ("{0}.{1}"-f$smRelationship.TargetObject.Values[2], $smRelationship.TargetObject.Values[3])$htSMCollectionDevices.Add($key, $value) } }#See what members need to be added to SM collectionforeach ($cmCollectionDevicein$htCMCollectionDevices.GetEnumerator()) {if (!($htSMCollectionDevices.Contains($cmCollectionDevice.Key))) {if ($htAllSMDevices.Contains($cmCollectionDevice.Key)) {$filter= 'NetbiosComputerName -eq"{0}"' -f$cmCollectionDevice.Value.ToString().Split(".")[0]$smDevice= GetSCClassInstance -class $deviceDefinition-filter$filterif ($smDevice.NetbiosDomainName -eq$cmCollectionDevice.Value.ToString().Split(".")[1]) { AddDeviceToCollection -relationship $relationshipDefinition-collection (GetSMCollection($collection.Key)) -device $smDevice } }else { LogIt -message ($cmCollectionDevice.Key.ToString().Split(".")[0] +" was not found in SM and cannot be added to SM collection: "+$collection.Key) -component "SyncCollections()"-type 2 } } }#See what members need to be removed from SM collectionforeach ($smCollectionDevicein$htSMCollectionDevices.GetEnumerator()) {if (!($htCMCollectionDevices.Contains($smCollectionDevice.Key))) {$filter= 'NetbiosComputerName -eq"{0}"' -f$smCollectionDevice.Value.ToString().Split(".")[0]$smDevice= GetSCClassInstance -class $deviceDefinition-filter$filterif ($smDevice.NetbiosDomainName -eq$smCollectionDevice.Value.ToString().Split(".")[1]) { RemoveDeviceFromCollection -relationship (GetSCRelationshipInstance -targetInstance $smDevice) -collection (GetSMCollection($collection.Key)) -device $smDevice } } } }else {#Put CM collection users in hash table$htCMCollectionUsers=@{}$cmUsers= Get-CMUser -CollectionId ((Get-CMUserCollection -CollectionId $collection.Key).CollectionID)foreach ($cmUserin$cmUsers) { $htCMCollectionUsers.Add($cmUser.SMSID.ToUpper(), $cmUser.SMSID) }#Put SM collection users in hash table$htSMCollectionUsers=@{}$smCollectionInstance= GetSCClassInstance -class $collectionDefinition-filter ('CollectionID -eq"{0}"' -f$collection.Key)$smRelationships= GetSCRelationshipInstance -sourceInstance $smCollectionInstanceforeach ($smRelationshipin$smRelationships) { if (!($smRelationship.IsDeleted)) {$key= ("{0}\{1}"-f$smRelationship.TargetObject.Values[6].ToString().ToUpper(), $smRelationship.TargetObject.Values[7]).ToUpper()$value= ("{0}\{1}"-f$smRelationship.TargetObject.Values[6], $smRelationship.TargetObject.Values[7])$htSMCollectionUsers.Add($key, $value) } }#See what members need to be added to SM collectionforeach ($cmCollectionUserin$htCMCollectionUsers.GetEnumerator()) {if (!($htSMCollectionUsers.Contains($cmCollectionUser.Key))) {if ($htAllSMUsers.Contains($cmCollectionUser.Key)) {$filter= 'UserName -eq"{0}"' -f$cmCollectionUser.Value.ToString().Split("\")[1]$smUser= GetSCClassInstance -class $userDefinition-filter$filter AddUserToCollection -relationship $relationshipDefinition-collection (GetSMCollection($collection.Key)) -user $smUser }else { LogIt -message ($cmCollectionUser.Key.ToString().Split(".")[0] +" was not found in SM and cannot be added to SM collection: "+$collection.Key) -component "SyncCollections()"-type 2 } } }#See what members need to be removed from SM collectionforeach ($smCollectionUserin$htSMCollectionUsers.GetEnumerator()) {if (!($htCMCollectionUsers.Contains($smCollectionUser.Key))) {$filter= 'UserName -eq"{0}"' -f$smCollectionUser.Value.ToString().Split("\")[1]$smUser= GetSCClassInstance -class $UserDefinition-filter$filter RemoveUserFromCollection -relationship (GetSCRelationshipInstance -targetInstance $smUser) -collection (GetSMCollection($collection.Key)) -user $smUser } } } } LogIt -message ("Leaving SyncCollections()") -component "SyncCollections()"-type 4 }function AddDeviceToCollection {param ($relationship, $collection, $device) NewSCDeviceRelationshipInstance -relationshipClass $relationship-source $collection-target $device }function RemoveDeviceFromCollection {param ($relationship, $collection, $device)foreach ($relin$relationship) {if ((!($rel.IsDeleted)) -and ($rel.SourceObject.Values[2].ToString() -eq$collection.CollectionID.ToString())) { RemoveSCDeviceRelationshipInstance -relationship $rel-collection $collection-device $device } } }function AddUserToCollection {param ($relationship, $collection, $user) NewSCUserRelationshipInstance -relationshipClass $relationship-source $collection-target $user }function RemoveUserFromCollection {param ($relationship, $collection, $user)foreach ($relin$relationship) {if ((!($rel.IsDeleted)) -and ($rel.SourceObject.Values[2].ToString() -eq$collection.CollectionID.ToString())) { RemoveSCUserRelationshipInstance -relationship $rel-collection $collection-user $user } } }#Main$Version="1.0" [bool]$FullUpdate= [System.Convert]::ToBoolean($FullUpdate) [bool]$Global:Verbose= [System.Convert]::ToBoolean($VerboseLogging)$Global:LogFile= Join-Path (GetScriptDirectory) 'Logs\SyncCollections.log'$Global:ScriptName= 'SyncCollections.ps1'$Global:LogBuffer= ''$Global:ScriptStatus= 'Success' LogIt -message ("Sync Collections Script v{0}"-f$Version) -type 1-component "Main()"#Connect to CM and SM$CM= GetCMSiteConnection -siteCode $CMSiteCode-siteServer $CMSiteServer$SM= GetSMManagementGroupConnection -computerName $SMManagementServer#Get Collections from CM and SM$CMDeviceCollections= GetCMDeviceCollections$CMUserCollections= GetCMUserCollections$SMCollections= GetSMCollections#Get CollIDs in SM$Global:CollIDs= GetCollIDs -smCollections $SMCollections#Update Collections in SMUpdateCollections -smCollections $SMCollections-cmDeviceCollections $CMDeviceCollections-cmUserCollections $CMUserCollections#Update Collection Members in SMUpdateCollectionMembers -fullUpdate $FullUpdate-collection $Collection-cmDeviceCollections $CMDeviceCollections-cmUserCollections $CMUserCollections#Update Collection Member Counts in SMUpdateMemberCounts#Log Result$Ret=$Global:ScriptStatus LogIt -message ("Script Complete, Result: {0}"-f$Ret) -component "Main()"-type 1#Create SR Log if neededCreateServiceRequestLog -serviceRequest $ServiceRequest-srLogPath (GetScriptDirectory)
Import the Configuration Manager Collections Management Pack into Service Manager
This Management Pack contains a Type Projection (aka Combination Class) that allows the members of a collection to be viewable when selecting a collection in the Service Manager Self-Service Portal.
- Open the Service Manager Console
- Select Administration
- Right-Click Management Packs and select Import
- Select the Custom.Example.DataCenter.Automation.Collections.mp management pack and choose Open, Import, and OK
<?xml version="1.0" encoding="utf-8"?><ManagementPack SchemaVersion="2.0" ContentReadable="true" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Manifest><Identity><ID>Custom.Example.DataCenter.Automation.Collections</ID><Version>1.0.0.0</Version></Identity><Name>DataCenter Automation: Configuration Manager Collections</Name><References><Reference Alias="CM"><ID>Microsoft.SystemCenter.ConfigurationManager</ID><Version>7.5.3079.0</Version><PublicKeyToken>31bf3856ad364e35</PublicKeyToken></Reference><Reference Alias="System"><ID>System.Library</ID><Version>7.5.8501.0</Version><PublicKeyToken>31bf3856ad364e35</PublicKeyToken></Reference></References></Manifest><TypeDefinitions><EntityTypes><TypeProjections><TypeProjection ID="Custom.Example.DataCenter.Automation.Collections.CollectionProjection" Accessibility="Public" Type="CM!Microsoft.SystemCenter.ConfigurationManager.CollectionInfo"><Component Path="$Target/Path[Relationship='CM!Microsoft.SystemCenter.ConfigurationManager.CollectionHasConfigItem']$" Alias="CollectionProjection"/></TypeProjection></TypeProjections></EntityTypes></TypeDefinitions><LanguagePacks><LanguagePack ID="ENU" IsDefault="true"><DisplayStrings><DisplayString ElementID="Custom.Example.DataCenter.Automation.Collections"><Name>DataCenter Automation: Configuration Manager Collections</Name><Description>Management Pack for bringing collection information from SCCM into SM</Description></DisplayString><DisplayString ElementID="Custom.Example.DataCenter.Automation.Collections.CollectionProjection"><Name>Collection Projection</Name></DisplayString></DisplayStrings><KnowledgeArticles></KnowledgeArticles></LanguagePack></LanguagePacks></ManagementPack>
Create the Sync Collection (On-Demand) Runbook
This Runbook will launch a PowerShell script when it is triggered via the Service Manager Self-Service Portal.
- Open the Orchestrator Runbook Designer
- Create a new runbook
- Drag the "Runbook Control\Initialize Data" activity into the new runbook
- Configure two parameters on the "Initialize Data" activity and click Finish
- ServiceRequest (data type: String)
- CollectionName (data type: String)
- Drag the "System\Run Program" activity into the new runbook
- Link the two activities
- Configure the Security of "System\Run Program" to use the service account
- Configure three settings under the "Details" section of the "Run Program" activity and click finish
- Program path: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
- Parameters: -File <local sharepath>\Automation\SyncCollections.ps1 -CMSiteCode CAS -CMSiteServer sccm2012r2cas.contoso.com -SMManagementServer sm2012r2.contoso.com -VerboseLogging false -FullUpdate false -Collection "{CollectionName from "Initialize Data"}" -ServiceRequest {ServiceRequest from "Initialize Data"}
- Working Folder: <local sharepath>\Automation
- Note, the data between the curly braces are Published Data from the Data Bus. This is obtained by right-clicking on the white space and selecting the appropriate variable. You can also use Orchestrator variables configured under Global Settings for items such as CMSiteCode, CMSiteServer, and SMManagementServer.
- Check In the Runbook
- The Runbook should look like this:
Create the Sync Collections (Full - Scheduled) Runbook
This Runbook will launch a PowerShell script when it is triggered via a schedule. This will catch Collections and Collection modifications that have occurred outside of the Service Manager Self-Service Portal.
- Open the Orchestrator Runbook Designer
- Create a new runbook
- Drag the "Scheduling\Monitor Date/Time" activity into the new runbook
- Configure one setting under Details of the Monitor Date/Time activity and click Finish
- Every: 1 hour
- Drag the "System\Run Program" activity into the new runbook
- Link the two activities
- Configure the Security of "System\Run Program" to use the service account
- Configure three settings under the "Details" section of the "Run Program" activity and click finish
- Program path: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
- Parameters: -File <sharepath>\Automation\SyncCollections.ps1 -CMSiteCode CAS -CMSiteServer sccm2012r2cas.contoso.com -SMManagementServer sm2012r2.contoso.com -VerboseLogging false -FullUpdate true
- Working Folder: <sharepath>\Automation
- You can also use Orchestrator variables configured under Global Settings for items such as CMSiteCode, CMSiteServer, and SMManagementServer.
- Check in and Start this runbook.
- The Runbook should look like this:
Summary
The on-demand runbook will be used in a future post for synchronizing Configuration Manager collections with Service Manager, including collection members and member count. The scheduled runbook will start working on the schedule to synchronize collections, collection members, and member counts every hour thereby picking up changes made outside of the Service Manager Portal.
Continue to the 4th post in this series: Sync Configuration Manager Client and Operations Manager Agent State in Service Manager