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

Using the Service Manager Self-Service Portal for Common Tasks in Configuration Manager, Operations Manager, and Azure

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

  1. Give the account admin rights to Service Manager
  2. Give the account admin rights to Configuration Manager
  3. Give the account admin rights to Operations Manager

Create a share to store scripts and logs

  1. 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.
  2. In the share, create a folder called "Automation" and give the service account access to it.
  3. Copy SyncCollections.ps1 into the Automation Folder
  4. 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.
  5. 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 ) #Functions function 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 1 if ($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 1 return $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 4 return $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 4 return $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 4 return $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 4 return $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 4 return $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 4 return $cmUserCollections } function GetSMCollections { $smCollectionDefinition = GetSCClass -name 'Microsoft.SystemCenter.ConfigurationManager.CollectionInfo' $smCollections = GetSCClassInstance -class $smCollectionDefinition LogIt -message ("Retrieved SM Collections") -component "GetSMCollections()" -type 4 return $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 4 return $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 $collectionID if ($collection) { $collectionType = 'DEVICE' } else { $collection = GetCMUserCollection -collectionID $collectionID if ($collection) { $collectionType = 'USER' } else { $collectionType = 'NEW' } } LogIt -message ("Retrieved CM Collection: " + $collectionID + " of type: " + $collectionType) -component "GetCollectionType()" -type 4 return $collectionType } function GetCollectionTypeByName { param($collectionName) $collection = GetCMDeviceCollectionByName -collectionName $collectionName if ($collection) { $collectionType = 'DEVICE' } else { $collection = GetCMUserCollectionByName -collectionName $collectionName if ($collection) { $collectionType = 'USER' } else { $collectionType = 'NEW' } } LogIt -message ("Retrieved CM Collection: " + $collectionName + " of type: " + $collectionType) -component "GetCollectionTypeByName()" -type 4 return $collectionType } function GetCollectionID { param($collectionName) $type = GetCollectionTypeByName -collectionName $collectionName if ($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 ($smCollection in $smCollections) { try {$collIDs.Add($smCollection.CollID, $smCollection.CollID)} catch {} } LogIt -message ("Retrieved SM Collection Ids") -component "GetCollIDs()" -type 4 return $collIDs } function GetNextCollID { $collIDs = $Global:CollIDs.GetEnumerator() | Sort-Object Name foreach ($id in $collIDs) { $i = $id.Key } $i++ $Global:CollIDs.Add($i, $i) LogIt -message ("Retrieved Next Collection ID: " + $i) -component "GetNextCollID()" -type 4 return $i } function UpdateCollections { param ($cmDeviceCollections, $cmUserCollections, $smCollections) LogIt -message ("Entering UpdateCollections()") -component "UpdateCollections()" -type 4 $htCM = @{} $htSM = @{} $htToUpdate = @{} #Put CM device collections in hash table foreach ($cmDeviceCollection in $cmDeviceCollections) { $htCM.Add($cmDeviceCollection.CollectionID, $cmDeviceCollection.Name) } #Add CM user collections to hash table foreach ($cmUserCollection in $cmUserCollections) { $htCM.Add($cmUserCollection.CollectionID, $cmUserCollection.Name) } #Put SM collections in hash table foreach ($smCollection in $smCollections) { $htSM.Add($smCollection.CollectionID, $smCollection.Name) } #Find collection to add foreach ($cmColl in $htCM.GetEnumerator()) { if (!($htSM.ContainsKey($cmColl.Key))) { $htToUpdate.Add($cmColl.Key, $cmColl.Value) } } #Find collections to remove foreach ($smColl in $htSM.GetEnumerator()) { if (!($htCM.ContainsKey($smColl.Key))) { $htToUpdate.Add($smColl.Key, 'REMOVE') } } #Update collections foreach ($collection in $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 table foreach ($cmDeviceCollection in $cmDeviceCollections) { $htCM.Add($cmDeviceCollection.CollectionID, $cmDeviceCollection.MemberCount) } #Add CM user collections to hash table foreach ($cmUserCollection in $cmUserCollections) { $htCM.Add($cmUserCollection.CollectionID.ToString(), $cmUserCollection.MemberCount) } #Put SM collections in hash table foreach ($smCollection in $smCollections) { $htSM.Add($smCollection.CollectionID.ToString(), $smCollection.MemberCount) } #Find collections with mismatched MemberCount foreach ($collection in $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 table foreach ($cmDeviceCollection in $cmDeviceCollections) { $htCM.Add($cmDeviceCollection.CollectionID, $cmDeviceCollection.CollectionID) } #Add CM user collections to hash table foreach ($cmUserCollection in $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 1 foreach ($collection in (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 $deviceDefinition foreach ($deviceInstance in $deviceInstances) { $htAllSMDevices.Add($deviceInstance.NetbiosComputerName.ToUpper() + "." + $deviceInstance.NetbiosDomainName.ToUpper(), $deviceInstance.NetbiosComputerName + "." + $deviceInstance.NetbiosDomainName) } #Put all SM users in hash table $htAllSMUsers = @{} $userInstances = GetSCClassInstance -class $userDefinition foreach ($userInstance in $userInstances) { $htAllSMUsers.Add($userInstance.Domain.ToUpper() + "\" + $userInstance.UserName.ToUpper(), $userInstance.Domain + "\" + $userInstance.UserName ) } #Loop through collections that have changed foreach ($collection in $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 ($cmDevice in $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 $smCollectionInstance foreach ($smRelationship in $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 collection foreach ($cmCollectionDevice in $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 $filter if ($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 collection foreach ($smCollectionDevice in $htSMCollectionDevices.GetEnumerator()) { if (!($htCMCollectionDevices.Contains($smCollectionDevice.Key))) { $filter = 'NetbiosComputerName -eq "{0}"' -f $smCollectionDevice.Value.ToString().Split(".")[0] $smDevice = GetSCClassInstance -class $deviceDefinition -filter $filter if ($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 ($cmUser in $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 $smCollectionInstance foreach ($smRelationship in $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 collection foreach ($cmCollectionUser in $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 collection foreach ($smCollectionUser in $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 ($rel in $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 ($rel in $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 SM UpdateCollections -smCollections $SMCollections -cmDeviceCollections $CMDeviceCollections -cmUserCollections $CMUserCollections #Update Collection Members in SM UpdateCollectionMembers -fullUpdate $FullUpdate -collection $Collection -cmDeviceCollections $CMDeviceCollections -cmUserCollections $CMUserCollections #Update Collection Member Counts in SM UpdateMemberCounts #Log Result $Ret = $Global:ScriptStatus LogIt -message ("Script Complete, Result: {0}" -f $Ret) -component "Main()" -type 1 #Create SR Log if needed CreateServiceRequestLog -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.

  1. Open the Service Manager Console
  2. Select Administration
  3. Right-Click Management Packs and select Import
  4. 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.

  1. Open the Orchestrator Runbook Designer
  2. Create a new runbook
  3. Drag the "Runbook Control\Initialize Data" activity into the new runbook
  4. Configure two parameters on the "Initialize Data" activity and click Finish
    • ServiceRequest (data type: String)
    • CollectionName (data type: String)
  5. Drag the "System\Run Program" activity into the new runbook
  6. Link the two activities
  7. Configure the Security of "System\Run Program" to use the service account
  8. 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.
  9. Check In the Runbook
  10. The Runbook should look like this:

clip_image001

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.

  1. Open the Orchestrator Runbook Designer
  2. Create a new runbook
  3. Drag the "Scheduling\Monitor Date/Time" activity into the new runbook
  4. Configure one setting under Details of the Monitor Date/Time activity and click Finish
    • Every: 1 hour
  5. Drag the "System\Run Program" activity into the new runbook
  6. Link the two activities
  7. Configure the Security of "System\Run Program" to use the service account
  8. 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.
  9. Check in and Start this runbook.
  10. The Runbook should look like this:

clip_image002

 

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