Sync Configuration Manager Collections in Service Manager
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
)
#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.
- 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="https://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
Comments
Anonymous
May 12, 2015
I starteed the powershell script on the SM Management Server and get the follow error. Can you tell me why? many thanks Cannot get path to CM console, will try default path: Cannot bind argument to parameter 'Path' because it is an empty string.Anonymous
May 12, 2015
It's looking for this environment variable: Env:SMS_ADMIN_UI_PATH. Make sure you're following the instructions in the post, including the prereqs.