Create dynamic SCCM collections
Purpose :
This project allows you to create dynamic SCCM collections with specific maintenance windows. This project has been created to optimize Windows update installation on a VDI infrastructure. There are some requirements to optimize these update installations :
- VDI (Virtual Desktop Environment) desktops
- All VDI computer account are located in the same Active Directory OU
- There are two datacenters with VMware ESX servers and these servers host the VDI stations.
- The datacenter1 hosts the VDI workstations with a hostname like “vdi-guest-A001”, “vdi-guest-A002”,…
- The datacenter2 hosts the VDI workstations with a hostname like “vdi-guest-B001”, “vdi-guest-B002”,…
To avoid a high resource consumption on the ESX servers with Windows update installation and VDI station reboots, the Powershell script will do the following actions :
- list all VDI stations located in the Active Directory OU
- from the list created above, create one VDI station list located in the datacenter1 (hostname like “vdi-guest-A001”, “vdi-guest-A002”,…) and create one another VDI station list located in the datacenter2 (hostname like “vdi-guest-B001”, “vdi-guest-B002”,…)
- create automatically VDI station pools consisting of 10 VDI stations located in datacenter1 and 10 VDI stations located in datacenter2
- create the SCCM collections with the pools created above. These collections will be the children of a parent collection. Create manually this parent collection and keep in mind the ID of this collection (eg. ABC0010C)
- associate different SCCM maintenance windows for each collection. The first maintenance window will occur every second sunday from 01:00AM to 02:00AM. The second maintenance window will occur one hour later from 02:00AM to 03:00AM… etc
Script :
function Connect-SCCMServer { [CmdletBinding()] PARAM ( [Parameter(Position=1)] $serverName, [Parameter(Position=2)] $siteCode ) # Clear the results from any previous execution Clear-Variable -name sccmServer -errorAction SilentlyContinue Clear-Variable -name sccmNamespace -errorAction SilentlyContinue Clear-Variable -name sccmSiteCode -errorAction SilentlyContinue Clear-Variable -name sccmConnection -errorAction SilentlyContinue # If the $serverName is not specified, use "." if ($serverName -eq $null -or $serverName -eq "") { $serverName = "." } # Get the pointer to the provider for the site code if ($siteCode -eq $null -or $siteCode -eq "") { Write-Verbose "Getting provider location for default site on server $serverName" $providerLocation = get-wmiobject -query "select * from SMS_ProviderLocation where ProviderForLocalSite = true" -namespace "root\sms" -computername $serverName -errorAction Stop } else { Write-Verbose "Getting provider location for site $siteName on server $serverName" $providerLocation = get-wmiobject -query "select * from SMS_ProviderLocation where SiteCode = '$siteCode'" -namespace "root\sms" -computername $serverName -errorAction Stop } # Split up the namespace path $parts = $providerLocation.NamespacePath -split "\\", 4 Write-Verbose "Provider is located on $($providerLocation.Machine) in namespace $($parts[3])" $global:sccmServer = $providerLocation.Machine $global:sccmNamespace = $parts[3] $global:sccmSiteCode = $providerLocation.SiteCode # Make sure we can get a connection $global:sccmConnection = [wmi]"${providerLocation.NamespacePath}" Write-Verbose "Successfully connected to the specified provider" } function Delete-SCCMCollection { param($Server = $Env:ComputerName, $Name, $Site, $ParentCollectionID) Get-WmiObject -computerName $Server -namespace Root\SMS\Site_$Site -class SMS_Collection | where {$_.Name -eq "$Name"} | Remove-WmiObject } function Create-SCCMCollection { param($Server = $Env:ComputerName, $Name, $Site, $ParentCollectionID) $ColClass = [WMIClass]"\\$Server\Root\SMS\Site_$($Site):SMS_Collection" $Col = $ColClass.PSBase.CreateInstance() $Col.Name = $Name $Col.OwnedByThisSite = $True $Col.Comment = $Name $Col.psbase $Col.psbase.Put() $NewCollectionID = (Get-WmiObject -computerName $Server -namespace Root\SMS\Site_$Site -class SMS_Collection | where {$_.Name -eq $Name}).CollectionID $RelClass = [WMIClass]"\\$Server\Root\SMS\Site_$($Site):SMS_CollectToSubCollect" $Rel = $RelClass.PSBase.CreateInstance() $Rel.ParentCollectionID = $ParentCollectionID $Rel.SubCollectionID = $NewCollectionID $Rel.psbase $Rel.psbase.Put() } Function Get-SCCMComputer { [CmdletBinding()] PARAM ( [Parameter(Position=1)] $filter ) Get-SCCMObject SMS_R_System $filter } function Get-SCCMObject { [CmdletBinding()] PARAM ( [Parameter(Position=1)] $class, [Parameter(Position=2)] $filter ) if ($filter -eq $null -or $filter -eq "") { get-wmiobject -class $class -computername $sccmServer -namespace $sccmNamespace } else { get-wmiobject -query "select * from $class where $filter" -computername $sccmServer -namespace $sccmNamespace } } function Add-SCCMCollectionRule { [CmdletBinding()] PARAM ( [Parameter(ValueFromPipelineByPropertyName=$true)] $collectionID, [Parameter(ValueFromPipeline=$true)] [String[]] $name, [Parameter()] $queryExpression, [Parameter()] $queryRuleName ) Process { # Get the specified collection (to make sure we have the lazy properties) $coll = [wmi]"\\$sccmServer\$($sccmNamespace):SMS_Collection.CollectionID='$collectionID'" # Build the new rule if ($queryExpression -ne $null) { # Create a query rule $ruleClass = [wmiclass]"\\$sccmServer\$($sccmNamespace):SMS_CollectionRuleQuery" $newRule = $ruleClass.CreateInstance() $newRule.RuleName = $queryRuleName $newRule.QueryExpression = $queryExpression $null = $coll.AddMembershipRule($newRule) } else { $ruleClass = [wmiclass]"\\$sccmServer\$($sccmNamespace):SMS_CollectionRuleDirect" # Find each computer foreach ($n in $name) { foreach ($computer in get-SCCMComputer -filter "Name = '$n'") { # See if the computer is already a member $found = $false if ($coll.CollectionRules -ne $null) { foreach ($member in $coll.CollectionRules) { if ($member.ResourceID -eq $computer.ResourceID) { $found = $true } } } if (-not $found) { Write-Host "Adding new rule for computer $n" $newRule = $ruleClass.CreateInstance() $newRule.RuleName = $n $newRule.ResourceClassName = "SMS_R_System" $newRule.ResourceID = $computer.ResourceID $null = $coll.AddMembershipRule($newRule) } else { Write-Host "Computer $n is already in the collection" } } } } } } function Calc_Schedule { param($index, $ConfigMgrServer, $site) $ConfigMgrNamespace = "root\sms\site_$site" $ConfigMgrProviderPath = "\\" + (Join-Path $ConfigMgrServer $ConfigMgrNamespace) # Create recurrent monthly evaluation schedule $AssignedSchedule = ([WmiClass]($ConfigMgrProviderPath + ":SMS_ST_RecurMonthlyByWeekday")).CreateInstance() $AssignedSchedule.StartTime = "20111028" + ("{0:00}" -f $index).tostring() + "0000.000000+***" $AssignedSchedule.Day = 1 #SUNDAY $AssignedSchedule.DayDuration = 0 $AssignedSchedule.HourDuration = 1 $AssignedSchedule.MinuteDuration = 0 $AssignedSchedule.weekorder = 2 # 2nd week, 0 for last week $ScheduleAsString = ([WmiClass]($ConfigMgrProviderPath + ":SMS_ScheduleMethods")).WriteToString($AssignedSchedule) return $ScheduleAsString.StringData } function Create-SCCMMaintenanceWindow { param($SCCM_SERVER, $Name, $SCCM_SITECODE, $COLLECTION_ID, $Schedule) #maintanance window info #read more about the values here # http://msdn.microsoft.com/en-us/library/cc143300.aspx $serviceWindowName = $Name $serviceWindowDescription = "Temporary window for software compliance runbook" $serviceWindowServiceWindowSchedules = $Schedule $serviceWindowIsEnabled = $true $serviceWindowServiceWindowType = 1 #create splatting package with wmi info. $SccmWmiInfo = @{ Namespace = "root\SMS\site_$SCCM_SITECODE" ComputerName = $SCCM_SERVER } $collsettings = Get-WmiObject @SccmWmiInfo -Query "Select * From SMS_CollectionSettings Where CollectionID = '$COLLECTION_ID'" if (!$collsettings) { $collsettings = ([WMIClass] "\\$SCCM_SERVER\root\SMS\site_$($SCCM_SITECODE):SMS_CollectionSettings").CreateInstance() $collsettings.CollectionID = $COLLECTION_ID $collsettings.Put() } #Get lazy properties $collsettings.Get(); #new service window $serviceWindow = ([WMIClass] "\\$SCCM_SERVER\root\SMS\site_$($SCCM_SITECODE):SMS_ServiceWindow").CreateInstance() $serviceWindow.Name = $serviceWindowName $serviceWindow.Description = $serviceWindowDescription $serviceWindow.ServiceWindowSchedules = $serviceWindowServiceWindowSchedules $serviceWindow.IsEnabled = $serviceWindowIsEnabled $serviceWindow.ServiceWindowType = $serviceWindowServiceWindowType $collsettings.ServiceWindows += $serviceWindow.psobject.baseobject $collsettings.Put() } Clear-Host $vdi_list = Get-ADComputer -SearchBase "OU=VDI,OU=Computers,DC=domain,DC=local" -Filter * -Properties Name |select name $pool_A_All = $vdi_list | where {$_.name -like "vdi-guest-A*"} | Get-Random -Count 9999 $pool_B_All = $vdi_list | where {$_.name -notlike "vdi-guest-A*"} | Get-Random -Count 9999 $pool_A_All.Count $pool_B_All.Count $pool_A_All $pool_B_All $vdi_count_except_IT = $pool_A_All.Count + $pool_B_All.Count $total_pool_number = $vdi_count_except_IT / 20 if ([int]("{0:#.000000}" -f $total_pool_number).split(".")[1] -gt 0) { $total_pool_number = [int]("{0:#.000000}" -f $total_pool_number).split(".")[0] + 1 $total_pool_number } else { $total_pool_number } Connect-SCCMServer -servername SccmServerHostname -siteCode ABC for ($i=1;$i -le $total_pool_number;$i++) { $inc = ("{0:00}" -f $i).tostring() $pool_name = "Pool_" + $inc Delete-SCCMCollection -Server "SccmServerHostname" -Name $pool_name -Site ABC -ParentCollectionID ABC0010C Create-SCCMCollection -Server "SccmServerHostname" -Name $pool_name -Site ABC -ParentCollectionID ABC0010C $pool_id = (Get-WmiObject -computerName SccmServerHostname -namespace Root\SMS\Site_ABC -class SMS_Collection | where {$_.Name -eq $pool_name}).CollectionID $pool_part_end = $i * 10 $pool_part_begin = $pool_part_end - 9 $pool_part_begin $pool_part_end foreach ($vdi.name in $pool_A_All[$pool_part_begin..$pool_part_end]) { Add-SCCMCollectionRule -collectionID $pool_id -name $vdi.name } foreach ($vdi.name in $pool_B_All[$pool_part_begin..$pool_part_end]) { Add-SCCMCollectionRule -collectionID $pool_id -name $vdi.name } $schedule_str = Calc_Schedule -index $i -site ABC create-SCCMMaintenanceWindow -SCCM_SERVER "SccmServerHostname" -Name $pool_name -SCCM_SITECODE "ABC" -COLLECTION_ID $pool_id -Schedule $schedule_str }
Create dynamic SCCM collections