Public/New-WsusObjects.ps1

function New-WsusObjects {
    <#
        .SYNOPSIS
            Creates Windows Server Update Services (WSUS) objects and delegations.
 
        .DESCRIPTION
            Creates the WSUS objects used to manage this organisation by following the defined
            Delegation Model. Key features:
            - Creates WSUS-specific security groups
            - Configures WSUS update permissions
            - Delegates WSUS administration rights
            - Supports the three-tier administration model
 
        .PARAMETER ConfigXMLFile
            [String] Full path to the configuration XML file.
            The XML file must contain required naming conventions and OU structure.
 
        .EXAMPLE
            New-WsusObjects -ConfigXMLFile 'C:\PsScripts\Config.xml'
 
            Creates WSUS objects using the specified configuration file.
 
        .EXAMPLE
            New-WsusObjects -ConfigXMLFile 'C:\PsScripts\Config.xml' -Verbose
 
            Creates WSUS objects with verbose output.
 
        .EXAMPLE
            New-WsusObjects -ConfigXMLFile 'C:\PsScripts\Config.xml' -WhatIf
 
            Shows what would happen when creating WSUS objects.
 
        .INPUTS
            [System.String]
            You can pipe the path to the XML configuration file to this function.
 
        .OUTPUTS
            [void]
            This function does not return any output.
 
        .NOTES
            Used Functions:
                Name ║ Module/Namespace
                ═══════════════════════════════════════╬══════════════════════════════
                Get-FunctionDisplay ║ EguibarIT
                New-AdDelegatedGroup ║ EguibarIT
                Import-MyModule ║ EguibarIT
                Write-Verbose ║ Microsoft.PowerShell.Utility
                Write-Error ║ Microsoft.PowerShell.Utility
 
        .NOTES
            Version: 1.2
            DateModified: 21/Sep/2025
            LastModifiedBy: Vicente Rodriguez Eguibar
                            vicente@eguibar.com
                            Eguibar IT
                            http://www.eguibarit.com
 
        .LINK
            https://github.com/vreguibar/EguibarIT/blob/main/Public/New-WsusObjects.ps1
 
        .COMPONENT
            Active Directory
 
        .ROLE
            Infrastructure Administration
 
        .FUNCTIONALITY
            WSUS Object Management
    #>

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Intentional plural: function creates multiple WSUS-related objects')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([void])]

    param ( )

    begin {
        Set-StrictMode -Version Latest

        # Initialize logging
        if ($null -ne $Variables -and
            $null -ne $Variables.Header) {

            $txt = ($Variables.Header -f
                (Get-Date).ToString('dd/MMM/yyyy'),
                $MyInvocation.Mycommand,
                (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False)
            )
            Write-Verbose -Message $txt
        } #end if

        ##############################
        # Module imports

        Import-MyModule 'ActiveDirectory' -Verbose:$false

        ##############################
        # Variables Definition

        [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)

        #Get the OS Installation Type
        $RegPath = 'HKLM:Software\Microsoft\Windows NT\CurrentVersion'
        $OsInstalationType = Get-ItemProperty -Path $RegPath | Select-Object -ExpandProperty InstallationType

    } # end begin

    process {
        if ($PSCmdlet.ShouldProcess('Active Directory', 'Configure WSUS and create related AD objects')) {

            # Check if AD module is installed
            if (-not((Get-WindowsFeature -Name RSAT-AD-PowerShell).Installed)) {
                Install-WindowsFeature -Name RSAT-AD-PowerShell -IncludeAllSubFeature
            }

            # Check if feature is installed, if not then proceed to install it.
            if (-not((Get-WindowsFeature -Name UpdateServices-Services).Installed)) {

                Install-WindowsFeature -Name UpdateServices, UpdateServices-Services, UpdateServices-WidDB -Restart

            }

            if ($OsInstalationType -ne 'Server Core') {
                Install-WindowsFeature -Name UpdateServices-RSAT -IncludeAllSubFeature
            }

            # Configure Download Location

            #Create WSUS folder
            # Create Folder where to store all Delegation Model scripts & files
            $WsusFolder = ('{0}\WSUS\' -f $env:SystemDrive)

            if (-not(Test-Path $WsusFolder)) {
                New-Item -ItemType Directory -Force -Path $WsusFolder
            }

            # Create a new Windows Script Shell
            $sh = New-Object -ComObject 'Wscript.Shell'

            [String]$cmd = '"C:\Program Files\Update Services\Tools\WsusUtil.exe" PostInstall CONTENT_DIR=C:\WSUS'
            $sh.Run($cmd, 1, 'true')

            # Download Microsoft System CLR Types for SQL Server 2014
            #$URL = 'https://download.microsoft.com/download/1/3/0/13089488-91FC-4E22-AD68-5BE58BD5C014/ENU/x64/SQLSysClrTypes.msi'

            # Download Microsoft System CLR Types for SQL Server 2012
            $URL = 'http://download.microsoft.com/download/F/E/D/FEDB200F-DE2A-46D8-B661-D019DFE9D470/ENU/x64/SQLSysClrTypes.msi'
            $Splat = @{
                Source        = $URL
                Destination   = $env:TEMP
                Priority      = 'High'
                TransferType  = 'Download'
                RetryInterval = 60
                RetryTimeout  = 180
                ErrorVariable = 'err'
            }
            Start-BitsTransfer @Splat
            if ($err) {
                Write-Error -Message 'Microsoft Microsoft System CLR Types for SQL Server 2014 could not be downloaded!. Please download and install it manually to use WSUS Reports.'
            }

            # Download MICROSOFT� REPORT VIEWER 2015 RUNTIME
            #$URL = 'https://download.microsoft.com/download/A/1/2/A129F694-233C-4C7C-860F-F73139CF2E01/ENU/x86/ReportViewer.msi'

            # Download MICROSOFT� REPORT VIEWER 2012 RUNTIME
            $URL = 'https://download.microsoft.com/download/F/B/7/FB728406-A1EE-4AB5-9C56-74EB8BDDF2FF/ReportViewer.msi'
            Start-BitsTransfer @Splat
            if ($err) {
                Write-Error -Message 'Microsoft REPORT VIEWER 2015 RUNTIME could not be downloaded!. Please download and install it manually to use WSUS Reports.'
            }



            # Install Microsoft System CLR Types for SQL Server 2014
            $Arguments = '/i "{0}\SQLSysClrTypes.msi" /qn /quiet /norestart' -f $env:TEMP
            $setup = Start-Process -FilePath 'msiexec.exe' -Verb RunAs -ArgumentList $Arguments -Wait -PassThru -Verbose
            $setup.WaitForExit()
            if ($setup.exitcode -eq 0) {
                Write-Verbose -Message 'Microsoft System CLR Types for SQL Server 2017 Successfully installed'
            } else {
                Write-Error -Message 'Microsoft System CLR Types for SQL Server 2017 did not install correctly. Please download and install it manually to use WSUS Reports.'
            }


            # Install REPORT VIEWER 2015 RUNTIME
            $Arguments = '/i "{0}\ReportViewer.msi" /qn /quiet /norestart' -f $env:TEMP
            $setup = Start-Process -FilePath 'msiexec.exe' -Verb RunAs -ArgumentList $Arguments -Wait -PassThru -Verbose
            $setup.WaitForExit()
            if ($setup.exitcode -eq 0) {
                Write-Verbose -Message 'Microsoft REPORT VIEWER 2015 RUNTIME Successfully installed'
            } else {
                Write-Error -Message 'Microsoft REPORT VIEWER 2015 RUNTIME did not install correctly. Please download and install it manually to use WSUS Reports.'
            }




            # Cannot be imported in the begin section due features installation
            Import-MyModule -Name 'WebAdministration' -Verbose:$false





            # Set Application Pool Maximum Private memory
            #Clear-ItemProperty IIS:\AppPools\WsusPool -Name Recycling.periodicRestart.privatememory
            #[int64] $PrivMemMax = 4GB
            #[int64] $PrivMemMax = 8GB
            #[int64] $PrivMemMax = 0
            # Set-ItemProperty -Path IIS:\AppPools\WsusPool -Name Recycling.periodicRestart.privateMemory -Value $PrivMemMax
            Set-ItemProperty -Path IIS:\AppPools\WsusPool -Name Recycling.periodicRestart.privateMemory -Value 4294967295

            # ( C:\Program Files\Update Services\WebServices\ClientWebService\web.config ) for WSUS: Replace <httpRuntime maxRequestLength="4096" /> with <httpRuntime maxRequestLength="204800" executionTimeout="7200"/>

            <#
        This one are failing
        Set-WebConfiguration -Filter "/system.applicationHost/applicationPools/add[@name='WsusPool']/recycling/periodicRestart/@privateMemory" -Value 0
        Set-WebConfiguration -Filter "/system.applicationHost/applicationPools/add[@name='WsusPool']/processModel/@maxProcesses" -Value 0
        #>


            # Other "Unexpected error" hacks
            Set-ItemProperty -Path IIS:\AppPools\WsusPool -Name queueLength -Value 25000
            Set-ItemProperty -Path IIS:\AppPools\WsusPool -Name cpu.resetInterval -Value '00.00:15:00'
            Set-ItemProperty -Path IIS:\AppPools\WsusPool -Name failure.loadBalancerCapabilities -Value 'TcpLevel'
            Set-ItemProperty -Path IIS:\AppPools\WsusPool -Name failure.rapidFailProtectionInterval -Value '00.00:30:00'
            Set-ItemProperty -Path IIS:\AppPools\WsusPool -Name failure.rapidFailProtectionMaxCrashes -Value 60
            Set-ItemProperty -Path IIS:\AppPools\WsusPool -Name ProcessModel.MaxProcesses -Value 0


            # Get WSUS Server Object
            $wsus = Get-WSUSServer

            # Connect to WSUS server configuration
            $wsusConfig = $wsus.GetConfiguration()

            ### Remove WSUS configuration pop-up when opening WSUS Management Console
            $wsusConfig.OobeInitialized = $true
            $wsusConfig.Save()


            #Check WSUS services. Mark those as automatic
            Set-Service WSusCertServer -StartupType Automatic
            Set-Service WsusService -StartupType Automatic
            Set-Service wuauserv -StartupType Automatic

            #Start Services
            Start-Service WSusCertServer, WsusService, wuauserv -Verbose


            # Get a new certificate from CA1 using WebServerV2 template
            $Splat = @{
                Template          = 'WebServerV2'
                DnsName           = ('{0}.{1}' -f $env:COMPUTERNAME, $env:USERDNSDOMAIN).ToLower()
                Url               = 'ldap:'
                CertStoreLocation = 'cert:\LocalMachine\My'
                SubjectName       = ('CN={0}' -f $env:COMPUTERNAME).ToLower()
            }
            $WsusCert = Get-Certificate @Splat

            # Get the binding as object
            $bind = Get-WebBinding -Name 'WSUS Administration' -Protocol https

            # Merge the 2 objects
            $bind.AddSslCertificate($WsusCert.Certificate.Thumbprint, 'My')

            # Set all corresponding virtual directories to use SSL
            $Splat = @{
                PSPath = 'MACHINE/WEBROOT/APPHOST'
                Filter = 'system.webServer/Security/access'
                Name   = 'sslFlags'
                Value  = 'Ssl'
            }
            Set-WebConfigurationProperty @Splat -Location 'WSUS Administration/ApiRemoting30'
            Set-WebConfigurationProperty @Splat -Location 'WSUS Administration/ClientWebService'
            Set-WebConfigurationProperty @Splat -Location 'WSUS Administration/DSSAuthWebService'
            Set-WebConfigurationProperty @Splat -Location 'WSUS Administration/ServerSyncWebService'
            Set-WebConfigurationProperty @Splat -Location 'WSUS Administration/SimpleAuthWebService'

            # Final SSL configuration
            [String]$cmd = '"C:\Program Files\Update Services\Tools\WsusUtil.exe" configuressl {0}' -f ('{0}.{1}' -f $env:COMPUTERNAME, $env:USERDNSDOMAIN).ToLower()
            $sh.Run($cmd, 1, 'true')



            # Get WSUS Server Object
            $wsus = Get-WSUSServer
            # Refresh WSUS server configuration
            $wsusConfig = $wsus.GetConfiguration()

            # Set to download updates from Microsoft Updates
            Set-WsusServerSynchronization -SyncFromMU

            # Set Update Languages to English and save configuration settings
            $wsusConfig.AllUpdateLanguagesEnabled = $false
            $wsusConfig.SetEnabledUpdateLanguages('en')
            $wsusConfig.GetContentFromMU = $True
            $wsusConfig.AutoApproveWsusInfrastructureUpdates = $True
            $wsusConfig.AutoRefreshUpdateApprovals = $True
            $wsusConfig.AutoRefreshUpdateApprovalsDeclineExpired = $True
            $wsusConfig.HostBinariesOnMicrosoftUpdate = $True
            $wsusConfig.BitsDownloadPriorityForeground = $True
            $wsusConfig.MaxSimultaneousFileDownloads = 1000
            $wsusConfig.Save()

            # Get WSUS Subscription and perform initial synchronization to get latest categories
            $subscription = $wsus.GetSubscription()
            $subscription.StartSynchronizationForCategoryOnly()

            while ($subscription.GetSynchronizationProgress().ProcessedItems -ne $subscription.GetSynchronizationProgress().TotalItems) {
                Write-Progress -PercentComplete ( $subscription.GetSynchronizationProgress().ProcessedItems * 100 / ($subscription.GetSynchronizationProgress().TotalItems) ) -Activity 'WSUS Sync Progress'
            }


            # Disable all previously selected products
            Get-WsusProduct | Set-WsusProduct -Disable

            # Configure the Platforms that we want WSUS to receive updates
            Get-WsusProduct | Where-Object {
                $_.Product.Title -in (
                    'Active Directory',
                    'Developer Tools, Runtimes, and Redistributables',
                    'Forefront Client Security',
                    'Forefront Identity Manager 2010 R2',
                    'Forefront Identity Manager 2010',
                    'Forefront Protection Category',
                    'Forefront Server Security Category',
                    'Forefront Threat Management Gateway, Definition Updates for HTTP Malware Inspection',
                    'Forefront TMG MBE',
                    'Forefront TMG',
                    'Forefront',
                    'Microsoft Advanced Threat Analytics',
                    'Microsoft BitLocker Administration and Monitoring v1',
                    'Microsoft BitLocker Administration and Monitoring',
                    'Microsoft Edge',
                    'Microsoft Security Essentials',
                    'MS Security Essentials',
                    'Report Viewer 2005',
                    'Report Viewer 2008',
                    'Report Viewer 2010',
                    'Security Essentials',
                    'Visual Studio 2015',
                    'Visual Studio 2017',
                    'Windows 10, version 1809 and later, Upgrade & Servicing Drivers',
                    'Windows 10',
                    'Windows 11',
                    'Windows Admin Center',
                    'Windows Defender',
                    'Windows Dictionary Updates',
                    'Windows Server 2016 and Later Servicing Drivers',
                    'Windows Server 2016',
                    'Windows Server 2019 and later, Servicing Drivers',
                    'Windows Server 2019 and later, Upgrade & Servicing Drivers',
                    'Windows Server 2019',
                    'Windows Server Drivers',
                    'Windows Server Solutions Best Practices Analyzer 1.0',
                    'Windows Server, version 1903 and later'
                )
            } | Set-WsusProduct



            # Configure the Classifications
            Write-Output 'Setting WSUS Classifications'
            Get-WsusClassification | Where-Object {
                $_.Classification.Title -in (
                    'Critical Updates',
                    'Definition Updates',
                    'Feature Packs',
                    'Security Updates',
                    'Service Packs',
                    'Tools',
                    'Update Rollups',
                    'Updates',
                    'Upgrade')
            } | Set-WsusClassification



            # Configure Default Approval Rule
            [void][reflection.assembly]::LoadWithPartialName('Microsoft.UpdateServices.Administration')

            $rule = $wsus.GetInstallApprovalRules() | Where-Object { $_.Name -eq 'Default Automatic Approval Rule' }

            $class = $wsus.GetUpdateClassifications() | Where-Object { $_.Title -in (
                    'Critical Updates',
                    'Definition Updates',
                    'Feature Packs',
                    'Security Updates',
                    'Service Packs',
                    'Tools',
                    'Update Rollups',
                    'Updates',
                    'Upgrade')
            }

            $class_coll = New-Object Microsoft.UpdateServices.Administration.UpdateClassificationCollection

            $class_coll.AddRange($class)
            $rule.SetUpdateClassifications($class_coll)
            $rule.Enabled = $True
            $rule.Save()


            # Configure Synchronizations
            Write-Output 'Enabling WSUS Automatic Synchronisation'
            $subscription.SynchronizeAutomatically = $true

            # Set synchronization scheduled for midnight each night
            $subscription.SynchronizeAutomaticallyTimeOfDay = (New-TimeSpan -Hours 0)
            $subscription.NumberOfSynchronizationsPerDay = 3
            $subscription.Save()

            # Kick off a synchronization
            $subscription.StartSynchronization()


            ### Create computer target group
            $wsus.CreateComputerTargetGroup('DCs')
            $wsus.CreateComputerTargetGroup('PAWs')
            $wsus.CreateComputerTargetGroup('Infrastructure Servers')
            $wsus.CreateComputerTargetGroup('Tier1')
            $wsus.CreateComputerTargetGroup('Tier2')

        } #end if ShouldProcess
    } # end process

    end {
        $txt = ($Variables.Footer -f $MyInvocation.InvocationName,
            'creating Wsus objects and Delegations.'
        )
    } # end end
} # end function New-WsusObjects