Uncategorized

Basic Patch Management Setup Script

May 18, 2020

Update 6/9/2020: I got caught speeding. It appears I managed to copy identical queries for multiple collections, used an incorrect parameter for one of the cmdlets, and forgot to include the deploy condition. I have repaired those mistakes so it should be good to go. Apologies to anyone that was having issues I’d also like to give a big thank you to @Tof006 from the Windows Admin Discord for catching these issues, I owe you a beverage of your choice.

Not looking forward to building the various folders, collections, SUGs, and ADRs? No worries, I have you covered. Below is a script to set it all up. As this is not a script you’d be running silently, I was not shy about using write-host, although I did die inside a little each time I did.

Run the script with three parameters (for most environments). You need to provide:
1. the base path to save updates in, a subfolder will be created for each deployment package
2. The FQDN of a DP to distribute the content. You could easily modify the script to take a DP group or an array of DP FQDNs if needed.
3. Deploy preference. If you set it to $TRUE, the SUGs will get deployed and the ADRs will be enabled. Set it to $FASLE and the SUGs are created, but not deployed and the ADRs are created in a disabled state.

For those with disconnected environments that need to download the updates from WSUS instead of the internet, you will provide an additional two parameters:
4. WSUS server FQDN
5. WSUS group to create and approve updates for to start download

# Microsoft provides programming examples for illustration only, 
# without warranty either expressed or implied, including, but not 
# limited to, the implied warranties of merchantability and/or 
# fitness for a particular purpose. 
#
# This sample assumes that you are familiar with the programming 
# language being demonstrated and the tools used to create and debug 
# procedures. Microsoft support professionals can help explain the 
# functionality of a particular procedure, but they will not modify 
# these examples to provide added functionality or construct 
# procedures to meet your specific needs. If you have limited 
# programming experience, you may want to contact a Microsoft 
# Certified Partner or the Microsoft fee-based consulting line at 
# (800) 936-5200. 
#
# For more information about Microsoft Certified Partners, please 
# visit the following Microsoft Web site:
# https://partner.microsoft.com/global/30000104

Param(
    [Parameter(Mandatory=$True)][string]$basepath,
    [Parameter(Mandatory=$True)][string]$DP,
    [Parameter(Mandatory=$True)][string]$Deploy,
    [Parameter()][string]$WSUSServer,
    [Parameter()][string]$WSUSgroup
)

$start = Get-Date

function New-PFEPersistentSUG{
    Param(
        [Parameter(Mandatory=$True)][string]$Product,
        [Parameter(Mandatory=$True)][string]$ProdColl,
        [Parameter(Mandatory=$True)][string]$DP,
        [Parameter(Mandatory=$True)][bool]$deploy,
        [Parameter()][bool]$offline
    )
    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""

    $updates = Get-CMSoftwareUpdate -Category (Get-CMSoftwareUpdateCategory -Name $Product -Fast) -IsSuperseded $FALSE -IsExpired $FALSE -fast | ?{($_.NumMissing -gt 0 -or $_.NumPresent -gt 0) -and $_.LocalizedCategoryInstanceNames -notcontains "Upgrades"} | select -ExpandProperty localizeddisplayname
    $SUGNAME = ("$($Product -replace ' ','' -replace ',','')Persistent").split('`.') -join " "
    New-CMSoftwareUpdateGroup -Name $SUGNAME | Out-Null
    $i=0
    ForEach ($Update in $updates){
        $i++
        write-progress -Activity "Creating Software Update Group" -Status "Progress:" -CurrentOperation "Adding $Update" -PercentComplete ($i/$updates.Count*100)
        try{Add-CMSoftwareUpdateToGroup -SoftwareUpdateGroupName $SUGNAME -SoftwareUpdateName $update}
        catch{$err+=$update}
    }

    write-output "Starting download of $($SUGNAME) SUG"
    if($offline){
        $SUGupdates = Get-CMSoftwareUpdate -UpdateGroupName $SUGNAME -Fast
        ForEach($SUGupdate in $SUGupdates){
            $WSUSUpdate = $wsus.GetUpdate([GUID]($SUGupdate.CI_UniqueID))
            if($WSUSUpdate.State -ne "Ready" -or $WSUSUpdate.State -ne "NotReady"){
                if($WSUSUpdate.HasLicenseAgreement){$WSUSUpdate.AcceptLicenseAgreement() | out-null}
                $WSUSUpdate.ApproveForOptionalInstall($group) | out-null
            }
        }
        
        while($SUGupdates.count -gt 0){
            write-host "Waiting for $($SUGupdates.count) updates to download in WSUS for $SUGNAME SUG" -ForegroundColor Yellow
            start-sleep -Seconds 60
            ForEach($SUGupdate in $SUGupdates){
                $WSUSUpdate = $wsus.GetUpdate([GUID]($SUGupdate.CI_UniqueID))
                if($WSUSUpdate.State -eq "Ready"){
                    write-host "Download of `"$($SUGupdate.LocalizedDisplayName)`" COMPLETE in WSUS" -ForegroundColor Green
                    $SUGupdates = $SUGupdates | Where-Object{$_ -ne $SUGupdate}
                }
            }
        }

        Save-CMSoftwareUpdate -SoftwareUpdateGroupName $SUGName -DeploymentPackageName $pkgname -location $WSUSContent
    }
    else{Save-CMSoftwareUpdate -SoftwareUpdateGroupName $SUGName -DeploymentPackageName $pkgname}
    Start-CMContentDistribution -DistributionPointName $DP -DeploymentPackageName $pkgname

    if($Deploy){
        New-CMSoftwareUpdateDeployment `
        -SoftwareUpdateGroupName $SUGName `
        -CollectionName $ProdColl `
        -DeploymentName $SUGName `
        -SendWakeupPacket $TRUE `
        -AvailableDateTime (Get-Date) `
        -DeadlineDateTime (Get-Date) `
        -DownloadFromMicrosoftUpdate $TRUE `
        -AcceptEula | out-null
    }
}

function New-PFEADR{
    Param(
        [Parameter(Mandatory=$True)][string]$Product,
        [Parameter(Mandatory=$True)][string]$PilotColl,
        [Parameter(Mandatory=$True)][string]$UATColl,
        [Parameter(Mandatory=$True)][string]$ProdColl,
        [Parameter(Mandatory=$True)][bool]$deploy,
        [Parameter()][bool]$offline
    )
    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""

    $Win10builds = @{
    '10240' = '1507'
    '10586' = '1511'
    '14393' = '1607'
    '15063' = '1703'
    '16299' = '1709'
    '17134' = '1803'
    '17763' = '1809'
    }

    $win101903builds = @{
    '18362' = '1903'
    '18363' = '1909'
    '19041' = '2004'
    }

    $title = @('-preview','-security only')

    if($Product -like "Windows 10*" -and $Product -notlike "*1903*" -and $Product -notlike "*LTSB*"){
        $Win10installedClass = Get-CimInstance -Namespace $namespace SMS_Windows10Dashboard | Where-object $_.Branch -eq 0 | Select Build, NumClients
        ForEach($win10ver in $Win10installedClass){
            $build = $Win10builds.item($win10ver.Build.split('.')[-1])
            if($build){$title += $build}
        }
    }
    elseif($Product -like "*1903*"){
        $Win10installedClass = Get-CimInstance -Namespace $namespace SMS_Windows10Dashboard | Where-object $_.Branch -eq 0 | Select Build, NumClients
        ForEach($win10ver in $Win10installedClass){
            $build = $win101903builds.item($win10ver.Build.split('.')[-1])
            if($build){$title += $build}
        }
    }
    elseif($Product -like "*LTSB*"){
        $Win10installedClass = Get-CimInstance -Namespace $namespace SMS_Windows10Dashboard | Where-object $_.Branch -eq 0 | Select Build, NumClients
        ForEach($win10ver in $Win10installedClass){
            $build = $win101903builds.item($win10ver.Build.split('.')[-1])
            if($build){$title += $build}
        }
    }

    if($offline){$Enable = $FALSE} else {$Enable = $TRUE}
    $ADRName = $Product + " Monthly"

    $ADRSched = New-CMSchedule -Start (Get-date -Year 2020 -Month 05 -day 09 -Hour 01 -Minute 00 -Second 00) -WeekOrder 2 -DayOfWeek Tuesday -OffsetDay 1

    if($offline){
    New-CMSoftwareUpdateAutoDeploymentRule `
    -Name $ADRName `
    -CollectionName $PilotColl `
    -AddToExistingSoftwareUpdateGroup $FALSE `
    -EnabledAfterCreate $Enable `
    -Enable $Deploy `
    -SendWakeupPacket $TRUE `
    -DeployWithoutLicense $TRUE `
    -DateReleasedOrRevised Last2Days `
    -Superseded $FALSE `
    -Title $title `
    -Product $Product `
    -Architecture X64 `
    -RunType RunTheRuleOnSchedule `
    -Schedule $ADRSched `
    -AvailableImmediately $TRUE `
    -DeadlineImmediately $TRUE `
    -UserNotification DisplaySoftwareCenterOnly `
    -SoftDeadlineEnabled $TRUE `
    -RequirePostRebootFullScan $TRUE | Out-Null
    }
    else{
    New-CMSoftwareUpdateAutoDeploymentRule `
    -Name $ADRName `
    -CollectionName $PilotColl `
    -AddToExistingSoftwareUpdateGroup $FALSE `
    -EnabledAfterCreate $Enable `
    -Enable $Deploy `
    -SendWakeupPacket $TRUE `
    -DeployWithoutLicense $TRUE `
    -DateReleasedOrRevised Last2Days `
    -Superseded $FALSE `
    -Title $title `
    -Product $Product `
    -Architecture X64 `
    -RunType RunTheRuleOnSchedule `
    -Schedule $ADRSched `
    -AvailableImmediately $TRUE `
    -DeadlineImmediately $TRUE `
    -UserNotification DisplaySoftwareCenterOnly `
    -DownloadFromMicrosoftUpdate $TRUE `
    -DeploymentPackageName $pkgname `
    -DownloadFromInternet $TRUE `
    -SoftDeadlineEnabled $TRUE `
    -RequirePostRebootFullScan $TRUE | Out-Null
    }

    New-CMAutoDeploymentRuleDeployment `
    -Name $ADRName `
    -CollectionName $UATColl `
    -EnableDeployment $Enable `
    -SendWakeupPacket $TRUE `
    -AvailableTime 2 `
    -AvailableTimeUnit Days `
    -DeadlineImmediately $TRUE `
    -UserNotification DisplaySoftwareCenterOnly `
    -AllowDownloadFromMicrosoftUpdate $TRUE `
    -SoftDeadlineEnabled $TRUE `
    -RequirePostRebootFullScan $TRUE | Out-Null

    New-CMAutoDeploymentRuleDeployment `
    -Name $ADRName `
    -CollectionName $ProdColl `
    -EnableDeployment $FALSE `
    -SendWakeupPacket $TRUE `
    -AvailableTime 7 `
    -AvailableTimeUnit Days `
    -DeadlineTime 7 `
    -DeadlineTimeUnit Days `
    -UserNotification DisplaySoftwareCenterOnly `
    -AllowDownloadFromMicrosoftUpdate $TRUE `
    -SoftDeadlineEnabled $TRUE `
    -RequirePostRebootFullScan $TRUE | Out-Null
}


write-output "Loading CM module"
if((Get-Module ConfigurationManager) -eq $null) {Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"}

$psdrive = Get-PSDrive -PSProvider CMSite
$sitecode = $psdrive.SiteCode
$provider = $psdrive.Connection.NamedValueDictionary["ProviderLocation"].Machine
$namespace = "root\sms\site_"+$sitecode

Set-Location "$(($psdrive).Name):\"

if($WSUSServer -and $WSUSgroup){$offline = $TRUE; write-host "Offline mode detected." -ForegroundColor Yellow} else{$offline = $FALSE}
$title = @('-preview','-security only')

$OSinCM = get-ciminstance -namespace $namespace -classname SMS_GH_System_OPERATING_SYSTEM | select -ExpandProperty Caption -Unique
ForEach($OS in $OSinCM){
    if($OS -like "*Windows 8.1*"){$Win81 = $TRUE}
    if($OS -like "*Windows 10*"){$win10 = $TRUE;$Win1903 = $TRUE}
    if($OS -like "*LTSB" -or $OS -like "*LTSC"){$Win10LTSB = $TRUE}
    if($OS -like "*Windows Server 2012 R2*"){$Server2012R2 = $TRUE}
    if($OS -like "*Windows Server 2016*"){$Server2016 = $TRUE}
    if($OS -like "*Windows Server 2019*"){$Server2019 = $TRUE}
}

write-output "Creating folders"
$collroot = "$(($psdrive).Name):\DeviceCollection"
$PatchingFolder = $collroot + "\Patching"
$TestingFolder = $PatchingFolder + "\Testing Deployments"
$ProductionFolder = $PatchingFolder + "\Production Deployments"
$MXWindowFolder = $PatchingFolder + "\Maintenenace Windows"

if(!(test-path $PatchingFolder)){New-Item -Name "Patching" -Path $collroot}
if(!(test-path $TestingFolder)){New-Item -Name "Testing Deployments" -Path $PatchingFolder}
if(!(test-path $ProductionFolder)){New-Item -Name "Production Deployments" -Path $PatchingFolder}
if(!(test-path $MXWindowFolder)){New-Item -Name "Maintenenace Windows" -Path $PatchingFolder}

$DailySchedule = New-CMSchedule -Start (Get-date -Year 2020 -Month 01 -day 01 -Hour 02 -Minute 00 -Second 00) -RecurInterval Days -RecurCount 1
$wallschedule = New-CMSchedule -Start (Get-date -Year 2020 -Month 01 -day 01 -Hour 00 -Minute 00 -Second 00) -End (Get-date -Year 2020 -Month 01 -day 01 -Hour 04 -Minute 00 -Second 00) -Nonrecurring
$Sat2200schedule = New-CMSchedule -Start (Get-date -Year 2020 -Month 05 -day 09 -Hour 22 -Minute 00 -Second 00) -End (Get-date -Year 2020 -Month 05 -day 10 -Hour 02 -Minute 00 -Second 00) -DayOfWeek Saturday
$Sun0200schedule = New-CMSchedule -Start (Get-date -Year 2020 -Month 05 -day 10 -Hour 02 -Minute 00 -Second 00) -End (Get-date -Year 2020 -Month 05 -day 10 -Hour 06 -Minute 00 -Second 00) -DayOfWeek Sunday
$PersistentSched = New-CMSchedule -Start (Get-date -Year 2020 -Month 01 -day 01 -Hour 02 -Minute 00 -Second 00) -Nonrecurring

write-output "Creating collections"
$AllServersColl = New-CMDeviceCollection -Name "PFE - All Servers" -LimitingCollectionName "All Systems" -RefreshType Periodic -RefreshSchedule $DailySchedule 
$AllServersQuery = "select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceId = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.ProductType != 1"
Add-CMDeviceCollectionQueryMembershipRule -collection $AllServersColl -RuleName "All Servers" -QueryExpression $AllServersQuery
New-CMMaintenanceWindow -InputObject $AllServersColl -IsEnabled $TRUE -Schedule $wallschedule -Name "MX Wall" -ApplyTo Any | out-null
Move-CMObject -InputObject $AllServersColl -FolderPath $PatchingFolder

$AllWKSColl = New-CMDeviceCollection -Name "PFE - All Workstations" -LimitingCollectionName "All Systems" -RefreshType Periodic -RefreshSchedule $DailySchedule
$AllWKSQuery = "select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceId = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.ProductType = 1"
Add-CMDeviceCollectionQueryMembershipRule -collection $AllWKSColl -RuleName "All Workstations" -QueryExpression $AllWKSQuery
Move-CMObject -InputObject $AllWKSColl -FolderPath $PatchingFolder

$NoMXnColl = New-CMDeviceCollection -Name "PFE - Servers with no MX Window" -LimitingCollectionName "All Systems" -RefreshType Periodic -RefreshSchedule $DailySchedule
Move-CMObject -InputObject $AllWKSColl -FolderPath $PatchingFolder

$exclusionColl = New-CMDeviceCollection -Name "PFE - Server Exclusions" -LimitingCollectionName "All Systems" -RefreshType Periodic -RefreshSchedule $DailySchedule
Move-CMObject -InputObject $exclusionColl -FolderPath $PatchingFolder
Add-CMDeviceCollectionExcludeMembershipRule -CollectionId $NoMXnColl.CollectionID -ExcludeCollectionId $exclusionColl.CollectionID | out-null

$PilotColl = New-CMDeviceCollection -Name "PFE - Pilot" -LimitingCollectionName "All Systems" -RefreshType Periodic -RefreshSchedule $DailySchedule
Move-CMObject -InputObject $PilotColl -FolderPath $TestingFolder

$UATColl = New-CMDeviceCollection -Name "PFE - UAT" -LimitingCollectionName "All Systems" -RefreshType Periodic -RefreshSchedule $DailySchedule
Move-CMObject -InputObject $UATColl -FolderPath $TestingFolder

$Sunday0200Coll = New-CMDeviceCollection -Name "PFE - Sunday 0200-0600" -LimitingCollectionName "All Systems" -RefreshType Periodic -RefreshSchedule $DailySchedule 
New-CMMaintenanceWindow -InputObject $Sunday0200Coll -IsEnabled $TRUE -Schedule $Sun0200schedule -Name "Sun0200" -ApplyTo Any | out-null
Move-CMObject -InputObject $Sunday0200Coll -FolderPath $MXWindowFolder
Add-CMDeviceCollectionExcludeMembershipRule -CollectionId $Sunday0200Coll.CollectionID -ExcludeCollectionId $exclusionColl.CollectionID | out-null
Add-CMDeviceCollectionExcludeMembershipRule -CollectionId $NoMXnColl.CollectionID -ExcludeCollectionId $Sunday0200Coll.CollectionID | out-null

$Sat2200Coll = New-CMDeviceCollection -Name "PFE - Sat 2200-0200" -LimitingCollectionName "All Systems" -RefreshType Periodic -RefreshSchedule $DailySchedule 
New-CMMaintenanceWindow -InputObject $Sat2200Coll -IsEnabled $TRUE -Schedule $Sat2200schedule -Name "Sat2200" -ApplyTo Any | out-null
Move-CMObject -InputObject $Sat2200Coll -FolderPath $MXWindowFolder
Add-CMDeviceCollectionExcludeMembershipRule -CollectionId $Sat2200Coll.CollectionID -ExcludeCollectionId $exclusionColl.CollectionID | out-null
Add-CMDeviceCollectionExcludeMembershipRule -CollectionId $NoMXnColl.CollectionID -ExcludeCollectionId $Sat2200Coll.CollectionID | out-null

if($offline){
    [reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | Out-Null
    $wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($WSUSServer,$False, 8530)
    $group = $wsus.GetComputerTargetGroups() | where {$_.Name -eq $WSUSgroup}
    $WSUSContent = "\\$($WSUSServer)\WSUSContent"
}

if($Win81){
    $Product = "Windows 8.1"

    $Coll = New-CMDeviceCollection -Name "PFE - $($product)" -LimitingCollectionId $AllWKSColl.CollectionID
    $Query = "select SMS_R_System.ResourceId, SMS_R_System.ResourceType, SMS_R_System.Name, SMS_R_System.SMSUniqueIdentifier, SMS_R_System.ResourceDomainORWorkgroup, SMS_R_System.Client from  SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.Caption = `"Microsoft Windows 8.1 Enterprise`""
    Add-CMDeviceCollectionQueryMembershipRule -collection $Coll -RuleName $product -QueryExpression $Query
    Move-CMObject -InputObject $Coll -FolderPath $ProductionFolder

    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""
    New-CMSoftwareUpdateDeploymentPackage -Name $pkgname -Path "$($basepath)$($pkgname)" | out-null

    write-output "Creating $($product) persistent SUG"
    New-PFEPersistentSUG -Product $product -ProdColl $Coll.Name -DP $DP -offline $offline -deploy $deploy

    write-output "Creating $($product) ADR"
    New-PFEADR -Product $product -PilotColl $PilotColl.Name -UATColl $UATColl.Name -ProdColl $Coll.Name -deploy $FALSE -offline $offline
}

if($win10){
    $product = "Windows 10"

    $Coll = New-CMDeviceCollection -Name "PFE - $($product)" -LimitingCollectionId $AllWKSColl.CollectionID
    $Query = "select SMS_R_System.ResourceId, SMS_R_System.ResourceType, SMS_R_System.Name, SMS_R_System.SMSUniqueIdentifier, SMS_R_System.ResourceDomainORWorkgroup, SMS_R_System.Client from  SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.Caption = `"Microsoft Windows 10 Enterprise`" and SMS_G_System_OPERATING_SYSTEM.BuildNumber < `"18362`""
    Add-CMDeviceCollectionQueryMembershipRule -collection $Coll -RuleName $product -QueryExpression $Query
    Move-CMObject -InputObject $Coll -FolderPath $ProductionFolder

    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""
    New-CMSoftwareUpdateDeploymentPackage -Name $pkgname -Path "$($basepath)$($pkgname)" | out-null

    write-output "Creating $($product) persistent SUG"
    New-PFEPersistentSUG -Product $product -ProdColl $Coll.Name -DP $DP -offline $offline -deploy $deploy

    write-output "Creating $($product) ADR"
    New-PFEADR -Product $product -PilotColl $PilotColl.Name -UATColl $UATColl.Name -ProdColl $Coll.Name -deploy $FALSE -offline $offline
    
}

if($Win1903){
    $product = "Windows 10, version 1903 and later"

    $Coll = New-CMDeviceCollection -Name "PFE - $($product)" -LimitingCollectionId $AllWKSColl.CollectionID
    $Query = "select SMS_R_System.ResourceId, SMS_R_System.ResourceType, SMS_R_System.Name, SMS_R_System.SMSUniqueIdentifier, SMS_R_System.ResourceDomainORWorkgroup, SMS_R_System.Client from  SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.Caption = `"Microsoft Windows 10 Enterprise`" and SMS_G_System_OPERATING_SYSTEM.BuildNumber >= `"18362`""
    Add-CMDeviceCollectionQueryMembershipRule -collection $Coll -RuleName $product -QueryExpression $Query
    Move-CMObject -InputObject $Coll -FolderPath $ProductionFolder

    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""
    New-CMSoftwareUpdateDeploymentPackage -Name $pkgname -Path "$($basepath)$($pkgname)" | out-null

    write-output "Creating $($product) persistent SUG"
    New-PFEPersistentSUG -Product $product -ProdColl $Coll.Name -DP $DP -offline $offline -deploy $deploy

    write-output "Creating $($product) ADR"
    New-PFEADR -Product $product -PilotColl $PilotColl.Name -UATColl $UATColl.Name -ProdColl $Coll.Name -deploy $FALSE -offline $offline
}

if($Win10LTSB){
    $product = "Windows 10 LTSB"

    $Coll = New-CMDeviceCollection -Name "PFE - $($product)" -LimitingCollectionId $AllWKSColl.CollectionID
    $Query = "select SMS_R_System.ResourceId, SMS_R_System.ResourceType, SMS_R_System.Name, SMS_R_System.SMSUniqueIdentifier, SMS_R_System.ResourceDomainORWorkgroup, SMS_R_System.Client from  SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.Caption = `"Microsoft Windows 10 Enterprise 2016 LTSB`""
    Add-CMDeviceCollectionQueryMembershipRule -collection $Coll -RuleName $product -QueryExpression $Query
    Move-CMObject -InputObject $Coll -FolderPath $ProductionFolder

    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""
    New-CMSoftwareUpdateDeploymentPackage -Name $pkgname -Path "$($basepath)$($pkgname)" | out-null

    write-output "Creating $($product) persistent SUG"
    New-PFEPersistentSUG -Product $product -ProdColl $Coll.Name -DP $DP -offline $offline -deploy $deploy

    write-output "Creating $($product) ADR"
    New-PFEADR -Product $product -PilotColl $PilotColl.Name -UATColl $UATColl.Name -ProdColl $Coll.Name -deploy $FALSE -offline $offline
}

if($Server2012R2){
    $product = "Windows Server 2012 R2"

    $Coll = New-CMDeviceCollection -Name "PFE - $($product)" -LimitingCollectionId $AllServersColl.CollectionID
    $Query = "select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.Caption = `"Microsoft Windows Server 2012 R2 Standard`""
    Add-CMDeviceCollectionQueryMembershipRule -collection $Coll -RuleName $product -QueryExpression $Query
    Move-CMObject -InputObject $Coll -FolderPath $ProductionFolder

    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""
    New-CMSoftwareUpdateDeploymentPackage -Name $pkgname -Path "$($basepath)$($pkgname)" | out-null

    write-output "Creating $($product) persistent SUG"
    New-PFEPersistentSUG -Product $product -ProdColl $Coll.Name -DP $DP -offline $offline -deploy $deploy

    write-output "Creating $($product) ADR"
    New-PFEADR -Product $product -PilotColl $PilotColl.Name -UATColl $UATColl.Name -ProdColl $Coll.Name -deploy $FALSE -offline $offline
}

if($Server2016){
    $product = "Windows Server 2016"

    $Coll = New-CMDeviceCollection -Name "PFE - $($product)" -LimitingCollectionId $AllServersColl.CollectionID
    $Query = "select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.Caption = `"Microsoft Windows Server 2016 Standard`""
    Add-CMDeviceCollectionQueryMembershipRule -collection $Coll -RuleName $product -QueryExpression $Query
    Move-CMObject -InputObject $Coll -FolderPath $ProductionFolder

    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""
    New-CMSoftwareUpdateDeploymentPackage -Name $pkgname -Path "$($basepath)$($pkgname)" | out-null

    write-output "Creating $($product) persistent SUG"
    New-PFEPersistentSUG -Product $product -ProdColl $Coll.Name -DP $DP -offline $offline -deploy $deploy

    write-output "Creating $($product) ADR"
    New-PFEADR -Product $product -PilotColl $PilotColl.Name -UATColl $UATColl.Name -ProdColl $Coll.Name -deploy $FALSE -offline $offline

}

if($Server2019){
    $product = "Windows Server 2019"

    $Coll = New-CMDeviceCollection -Name "PFE - $($product)" -LimitingCollectionId $AllServersColl.CollectionID
    $Query = "select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId where SMS_G_System_OPERATING_SYSTEM.Caption = `"Microsoft Windows Server 2019 Standard`""
    Add-CMDeviceCollectionQueryMembershipRule -collection $Coll -RuleName $product -QueryExpression $Query
    Move-CMObject -InputObject $Coll -FolderPath $ProductionFolder

    $pkgname = ("$($Product -replace ' ','' -replace ',','')updates").split('`.') -join ""
    New-CMSoftwareUpdateDeploymentPackage -Name $pkgname -Path "$($basepath)$($pkgname)" | out-null

    write-output "Creating $($product) persistent SUG"
    New-PFEPersistentSUG -Product $product -ProdColl $Coll.Name -DP $DP -offline $offline -deploy $deploy

    write-output "Creating $($product) ADR"
    New-PFEADR -Product $product -PilotColl $PilotColl.Name -UATColl $UATColl.Name -ProdColl $Coll.Name -deploy $FALSE -offline $offline
}

$end = Get-Date

$elapsed = New-TimeSpan -Start $start -end $end

write-host "Script took $($elapsed.Minutes) minutes to complete" -ForegroundColor Cyan