logo-powershellCreate 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

Leave a Reply

Your email address will not be published. Required fields are marked *