WSUS on Windows Server 2012 Core from scratch

I don’t know anything about WSUS as I’m more a ConfigMgr guy but I wanted to evaluate a few things about WSUS a.k.a Update Services on Windows Server 2012:

  • Can it run on a Core version?
  • Can it be managed on Core version?

The short answer is YES, it can run on a Core version as it’s a built-in role. YES, it can be managed on Core version but you should rather stick to Microsoft piece of advice “install it on core, manage it from a Windows 8 box with RSAT” (Remote Server Administration Tools). If you don’t have a Windows 8, you can also switch the server to the Minimal shell configuration that will allow you to launch MMC based snap-ins.

So here’s my scenario. I want to build a VM running a Core version of Windows server 2012, install Updates Services, configure it and push updates to Windows 7 with Office 2010 computers.

  • Step1: Provision a new VM on the Hyper-V 2012 host
  • New-VM -Name "CO2" -MemoryStartupBytes 2GB -NewVHDPath CO2.vhdx -BootDevice CD -NewVHDSizeBytes 50GB -SwitchName Prod            
    # Check the controller            
    Get-VM -Name CO2 | Get-VMHardDiskDrive            
    # Check the type of HDD created            
    Get-VM -Name CO2 | Get-VMHardDiskDrive | Get-VHD            
    # Configure the BIOS            
    Get-VM -Name CO2 | Set-VMBios -EnableNumLock            
    # Increase the number of processors            
    Get-VM -Name CO2 | Set-VM -ProcessorCount 2            
    # Configure Dynamic memory            
    Get-VM -Name CO2 | Set-VMMemory -DynamicMemoryEnabled:$true -MaximumBytes 4096MB -MinimumBytes 786MB            
    # Check that I have DVD drive attached to the VM            
    Get-VM -Name CO2 | Get-VMDvdDrive            
    # Load a bootable ISO of WS 2012            
    Set-VMDvdDrive -Path D:\Downloads\SW_DVD5_Win_Svr_Std_and_DataCtr_2012_64Bit_English_Core_MLF_X18-27588.ISO -VMName CO2            
    # Configure VM starup/stop options            
    Get-vm -VMName CO2 | set-VM -AutomaticStartAction Nothing -AutomaticStopAction Shutdown            
    # Start the VM            
    Start-VM -Name CO2

  • Step2: Install Windows 2012 from the loaded DVD drive
  • Step 3: Perform some basic post-installation tasks
  • # Check the current IP configuration (no DHCP available)            
    Get-NetAdapter | ? Status -eq "Up" | Get-NetIPAddress | ft IPAddress            
    Get-NetAdapter | ? Status -eq "Up" | Get-NetIPAddress -AddressFamily IPV4            
    # As the IPAddress is APIPA and as I have only 1 adapter I can do:            
    Get-NetApater | ? Status -eq "Up" | New-NetIPAddress -IPAddress -DefaultGateway -PrefixLength 24            
    # Set the DNS server            
    Get-NetAdapter | ? Status -eq "Up" | Set-DnsClientServerAddress -ServerAddresses,            
    # Set a proxy            
    netsh --% winhttp set proxy "my.corp.proxy.fqdn:7070"            
    netsh --% winhttp show proxy            
    # Update the powershell help            
    Update-Help -Force -UICulture en-us
    # Enable remote desktop            
    Enable-NetFirewallRule -DisplayGroup "Remote Desktop"            
    Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 0            
    # Get the current time zone            
    tzutil --% /g            
    # List all time zones            
    tzutil --% /l            
    # Set it            
    tzutil --% /s "Romance Standard Time"            
    # Verify            
    tzutil --% /g            
    # Change the date time            
    Get-Date -Date "01/26/2013 06:29:00 PM" | Set-Date
  • Step 4: Upgrade the VM integration tools
  • On the Hypervisor do:

    # Check the version of Integration services            
    Get-VM | ? State -eq "Running" | Select Name,State,Integrationservicesversion | ft -AutoSize            
    # Upgrade it to  6.2.9200.16433            
    Get-VM -Name CO2 | Get-VMDvdDrive | Set-VMDvdDrive -Path C:\Windows\System32\vmguest.iso            

    Inside the VM do:

    & D:\support\amd64\setup.exe

  • Step5: Configure Windows Udpate
  • # Configure regional and language options: control intl.cpl            
    Show-ControlPanelItem -Name Region            
    # Configure the timezone, date, or time: control timedate.cpl            
    Show-ControlPanelItem -Name "Date and Time"            
    # Disable Automatic Updates            
    New-ItemProperty -Path  'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' -Name AUOptions -Type DWORD -Value 1            
    # Copy/paste function and invoke            

    Get-WUSettings can be found on

  • Step 6: Opt-in to Microsoft Update and install a subset a selected updates
  • # Opt-in to MU            
    $UpdateSvc = New-Object -ComObject Microsoft.Update.ServiceManager            
    $UpdateSvc.QueryServiceRegistration("7971f918-a847-4430-9279-4a52d1efe18d") | fl *            
    $UpdateSvc.Services | ? Name -eq "Microsoft Update"            
    # Scan for missing updates            
    $Session = New-Object -ComObject Microsoft.Update.Session            
    $Searcher = $Session.CreateUpdateSearcher()            
    $Criteria = "IsInstalled=0 and DeploymentAction='Installation' or IsPresent=1 and DeploymentAction='Uninstallation' or IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1"            
    $SearchResult = $Searcher.Search($Criteria)            
    # Display missing updates            
    $SearchResult.Updates | ft Title,AutoSelectOnWebSites            
    # Prepare to download            
    $updatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl            
    $downloader = $Session.CreateUpdateDownloader()            
    $updatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl            
    # Select items to be downloaded and installed            
    $SearchResult.Updates | Select-item -By Title,AutoSelectOnWebSites -Multi | % {$updatesToDownload.Add($_)}            

    # Download            
    $downloader.Updates = $updatesToDownload            
    $downresults = $downloader.Download()            
    # Install            
    $updatesToDownload | ? { -not($_.isInstalled) -and $_.isDownloaded} | % {$updatesToInstall.Add($_)}            
    $installer = $Session.CreateUpdateInstaller()            
    $installer.Updates = $updatesToInstall            
    $installationResult = $installer.Install()            
    # Show the result            

    As I run a Core version, there isn’t the built-in cmdlet Out-GridView with its nice PassThru parameter to allow me to select the udpates I want. If I try to use Out-GridView, I end up with the following error message:
    Out-GridView : To use the Out-GridView, install the Windows PowerShell ISE feature from Server Manager and restart this application.
    (Could not load file or assembly ‘Microsoft.PowerShell.GraphicalHost, Version=,Culture=neutral, PublicKeyToken=31bf3856ad364e35’ or one of its dependencies. The system cannot find the file specified.)

    The ISE feature is “removed” on Core version, i.e. “Disabled with Payload removed”. If you want to add back the ISE to be able to use Out-GridView, you can follow the steps on this page

    As you may have noticed, I didn’t need to use Out-GridView as Rob Campbell published an awsome Select-Item function on this page that does perfectly the job:

  • Step7: Add a second disk, initiliaze, partition and format it:
  • In the Hypervisor do:

    # Create a new dynamic VHDX disk            
    New-VHD -Dynamic -Path D:\VM\HDD\CO2_DATA.VHDX -SizeBytes 20GB            
    # Attach it as a iSCSI disk to the VM to store data            
    Get-VM -Name CO2 | Add-VMHardDiskDrive -ControllerType SCSI -Path D:\VM\HDD\CO2_DATA.vhdx            

    In the VM do:

    # Initialize the second harddisk (iSCSI)            
    Get-Disk | ft -AutoSize            

    Get-Disk -Number 1 | Initialize-Disk            
    Get-Disk -Number 1 | ft -AutoSize

    # Check volume letters            
    Get-Volume | ft -AutoSize                        

    # Change the CD drive letter            
    Set-CDRomLetter -NextAvailableLetter -Verbose            

    # Create a new primary partition            
    Get-Disk -Number 1 | New-Partition -UseMaximumSize -AssignDriveLetter   

    Get-Volume | ? { $_.DriveType -eq "Fixed" -and -not($_.FileSystem)}            
    # Format it            
    Get-Partition -DriveLetter D |  Format-Volume -FileSystem NTFS

    The Set-CDRomLetter cmdlet I used above isn’t a built-in cmdlet. It can be obtained on this page:

  • Step8: Change the computer name and join the domain
  • Add-Computer -DomainName -Server -Credential (Get-Credential) -NewName CO2            

    After joining the domain and rebooting the computer, I log on using a privileged account, change its user locale, logoff/on, review installed security updates and check firewall rules.

    Get-Command -Module International            
    Set-Culture en-GB            
    # Logoff            
    Get-NetFirewallRule | ? Enabled | ft Name,DisplayName,Action

    For the update history, see the comment in the following post:

  • Step9: Install the Update Services feature and it dependencies
  • You should not use the -IncludeAllSubFeature as the WSUS database subfeature are at the same level and you have to choose what kind of database you want:
    1. Feature WID Database has a conflict with the features Database.
    2. Feature Database has a conflict with the features WID Database.

    Instead do:

    Add-WindowsFeature -Name UpdateServices  -Restart:$false -IncludeManagementTools:$true

    2 warnings are issued but the 2nd is the most important:
    WARNING: Windows automatic updating is not enabled. To ensure that your newly-installed role or feature is automatically updated, turn on Windows Update.
    WARNING: Additional configuration may be required. Review the article Managing WSUS Using PowerShell at TechNet Library
    ( for more information on the recommended steps to perform WSUS installation using PowerShell.

  • Step10: Perform Update Services’ post-installation task
  • # Create a folder where WSUS will download its content            
    New-Item D:\WSUS.Content -Type Directory            
    & 'C:\Program Files\Update Services\Tools\WsusUtil.exe' --% postinstall CONTENT_DIR=D:\WSUS.Content            

  • Step11: Review installed features, test the WSUS and examin BPA results
  • Get-WindowsFeature | ? Installed

    If you didn’t perform the post-installation you’ll have the following:

    When it works you get:

    Launch a BPA analysis on Update Services and review the results:

    Get-BpaModel | ? Name -match "Update Services" | Invoke-BpaModel            
    Get-BpaModel | ? Name -match "Update Services" |                        
    Get-BpaResult | ? Resolution |                         
    fl -p ResultNumber,Severity,Category,Title,Problem,Resolution            

    Add some miscellaneous features that I will use later on

    # Add other features            
    Add-WindowsFeature -Name GPMC -Restart:$false -Verbose            
    Add-WindowsFeature -Name RSAT-AD-PowerShell -Restart:$false -Verbose

  • Step12: Configure WSUS
  • Currently, I have done all the steps mentioned on Getting started with WSUS in the Windows Server 8 beta: Installing the WSUS role using the new Server Manager

    We will now focus on configuring WSUS. Before continuing, here are some related good articles:

    # List all built-in cmdlets associated with WSUS            
    Get-Command -Module UpdateServices

    No comment. Humm, actually I’ve one. As you’ll see below, I don’t do an extensive use of these cmdlets except Get-WSUSserver as some of these cmdlets return a custom type of object instead of the raw .Net object.

    # View the configuration options            
    # Choose the upstream server            
    Set-WsusServerSynchronization -SyncFromMU            
    # Set the Proxy            
    $config = (Get-WsusServer).GetConfiguration()            
    $config.GetContentFromMU = $true            
    $config.UseProxy = $true            
    $config.ProxyName = "my.corp.proxy.fqdn"            
    $config.ProxyServerPort = 7070            
    # Choose Languages            
    $config.AllUpdateLanguagesEnabled = $false            
    $config.AllUpdateLanguagesDssEnabled = $false            
    # View enabled Languages            
    # Begin initial synchronization            
    $subscription = (Get-WsusServer).GetSubscription()            
    While ($subscription.GetSynchronizationStatus() -ne 'NotProcessing') {            
        Write-Host "waiting on sync"            
        Start-Sleep -Seconds 5            
    # View all products            
    (Get-WsusServer).GetUpdateCategories() | ft Title,Id            
    # Choose Products            
    $subscription = (Get-WsusServer).GetSubscription()            
    $products = (Get-WsusServer).GetUpdateCategories() | ? {$_.Id -in @(            
        '84f5f325-30d7-41c4-81d1-87a0e6535b66', # Office 2010            
        'bfe5b177-a086-47a0-b102-097e4fa1f807'  # Windows 7            
    $coll = New-Object -TypeName Microsoft.UpdateServices.Administration.UpdateCategoryCollection            
    $products | % { $coll.Add($_) }            
    # View all classifications            
    Get-WsusClassification | % {'{0}, # {1}' -f $_.Classification.Id,$_.Classification.Title }            
    # As the Title is based on             
    # Choose Classifications            
    $subscription = (Get-WsusServer).GetSubscription()            
    $classifications = (Get-WsusServer).GetUpdateClassifications() | ? {$_.Id -in @(            
        'e6cf1350-c01b-414d-a61f-263d14d133b4', # 'Critical Updates',            
        '0fa1201d-4330-4fa8-8ae9-b877473b6441', # "Security Updates",            
        '68c5b0a3-d1a6-4553-ae49-01d3a7827828'  # "Service Packs"            
    $coll = New-Object -TypeName Microsoft.UpdateServices.Administration.UpdateClassificationCollection            
    $classifications  | % { $coll.Add($_) }            
    # Check selected classifications            
    # Start a sync            
    # View it progress            
    # Wait for sync            
    $subscription = (Get-WsusServer).GetSubscription()            
    while ($subscription.GetSynchronizationProgress().ProcessedItems -ne $subscription.GetSynchronizationProgress().TotalItems) {            
        Write-Progress -PercentComplete (            
        ) -Activity "Sync"             
    # Make sure the default automatic approval rule is not enabled            
    # View synchronization schedule options            
    # Get Configuration state            
    # Get WSUS status            
  • Step13: Approve specific updates for a custom target group
  • Source:

    There are two default computer groups: All Computers and Unassigned Computers. By default, when each client computer first contacts the WSUS server, the server adds that client computer to both of these groups.

    You can create as many custom computer groups as you need to manage updates in your organization. As a best practice, create at least one computer group to test updates before you deploy them to other computers in your organization.

    # Create a new computer group            
    (Get-WsusServer).CreateComputerTargetGroup("Windows 7 x64")            

    # List all groups            
    # Find any update that has 'Windows 7' string mentioned            
    $allW7updates = (Get-WsusServer).SearchUpdates("Windows 7")            
    # Show only Windows 7 x64 security updates            
    $allW7updates |             
        ? { (-not($_.IsSuperseded)) -and ($_.Title -match "x64") -and ($_.UpdateClassificationTitle -eq "Security Updates")} |            
        ft Title,State,KnowledgebaseArticles,SecurityBulletins            
    # Define the group to which I will assign updates for install            
    $targetgroup = (Get-WsusServer).GetComputerTargetGroups() | ? Name -eq "Windows 7 x64"            
    # Approve all Windows 7 security udpates except those for .Net 4            
    $allW7updates | ? { (-not($_.IsSuperseded)) -and ($_.Title -match "x64") -and ($_.UpdateClassificationTitle -eq "Security Updates")} |            
    ? {$_.Title -notmatch ".NET Framework 4"} | ForEach-Object -Process {            
    # Look for Windows 7 SP1            
    (Get-WsusServer).SearchUpdates("Windows 7 Service Pack 1 for x64")            
    # Approve Windows 7 SP1            
    (Get-WsusServer).SearchUpdates("Windows 7 Service Pack 1 for x64") | ForEach-Object -Process {            
    # Approve all security udpates for Office 2010 (32bit)            
    (Get-WsusServer).SearchUpdates("Office 2010") |             
    ? { (-not($_.IsSuperseded)) -and ($_.Title -match "32-bit") -and ($_.UpdateClassificationTitle -eq "Security Updates")} |            
     ForEach-Object -Process {            
    # View Office 2010 SP1 32bit            
    (Get-WsusServer).SearchUpdates("Office 2010") | ? UpdateClassificationTitle -eq "Service Packs" | ? Title -eq "Service Pack 1 for Microsoft Office 2010 (KB2510690) 32-bit Edition"            
    # Approve Office 2010 SP1            
    (Get-WsusServer).SearchUpdates("Office 2010") |             
    ? UpdateClassificationTitle -eq "Service Packs" |             
    ? Title -eq "Service Pack 1 for Microsoft Office 2010 (KB2510690) 32-bit Edition" |             
    ForEach-Object -Process {            
    # View other critical updates!            
    (Get-WsusServer).SearchUpdates("Windows 7") |            
    ? UpdateClassificationTitle -eq "Critical Updates" |            
    ft Title            
    # Approve other critical updates!            
    (Get-WsusServer).SearchUpdates("Windows 7") |            
    ? UpdateClassificationTitle -eq "Critical Updates" | ? Title -in @(            
    'Update for Windows 7 for x64-based Systems (KB2749655)',            
    'Update for Windows 7 for x64-based Systems (KB2718704)') |             
    ForEach-Object -Process {            

  • Step14: Configure a Group policy for client computers

  • As you see above, I chose to disable automatic updates.
    This has 2 major consequences:

    • I won’t get the selfupdate of the WUA (Windows Update Agent). The latest version 7.6.7600.256 won’t be installed automatically on client computers.
    • Non interactive scan will fail. In other words, the following well-known command cannot be used.
      %systemroot%\system32\wuauclt.exe /detectnow

    To workaround the second issue, client computers need to perform an interactive scan. When they perfom it the first time, computers got registered automatically in the default built-in ‘Unassigned computers’ group.

    As I have just configured a new computer group policy and loaded the Group Policy Management (GPMC) feature, I can make it apply immediately on all client computers like this:

    Get-ADComputer -Filter 'SamAccountName -LIKE "PC*"' |             
    ForEach-Object -Process {            
        Invoke-GPUpdate -RandomDelayInMinutes 0 -Force -Target Computer -Computer "$($_.Name).my.corp.fqdn"            
  • Step15: Assign computers to their group and make them apply udpates
  • As I said previously, to see computers being registered in the default groups on the WSUS server, client computers should perform an interactive scan.
    Once they do, they can be assigned to their custom target group so that they can install the specific updates I selected for installation.

    # Assign all unassigned computers to their group 'Windows 7 x64' group	            
    (Get-WsusServer).GetComputerTargetGroup('b73ca6ed-5727-47f3-84de-015e03f6a88a').GetComputerTargets() |            
     ForEach-Object -Process {            
        ((Get-WsusServer).GetComputerTargetGroups() | ? Name -Match "Windows 7").AddComputerTarget($_)            

    To force computers perform an interactive scan, register for the first time on the WSUS server and install all missing udpates, I execute the following script on all clients.

    #Requires -Version 2.0
    Function Install-WSUSUpdates {
    Begin {
        $UpdateSvc = New-Object -ComObject Microsoft.Update.ServiceManager
        # Make sure we have a WSUS by default
        if (-not($UpdateSvc.Services | ? { $_.ServiceID -eq '3da21691-e39d-4da6-8a4b-b43877bcb1b7'}).IsDefaultAUService) {
            Write-Warning -Message "WSUS server service not detected"
    Process {
        # Create a session
        $Session = New-Object -ComObject Microsoft.Update.Session
        # Search for updates
        $Searcher = $Session.CreateUpdateSearcher()
        $Criteria = "IsInstalled=0 and DeploymentAction='Installation' or IsPresent=1 and DeploymentAction='Uninstallation' or IsInstalled=1 and DeploymentAction='Installation' and RebootRequired=1 or IsInstalled=0 and DeploymentAction='Uninstallation' and RebootRequired=1"
        Write-Verbose -Message "Searching for updates"
        $SearchResult = $Searcher.Search($Criteria)
        if ($SearchResult.ResultCode -eq 2) {
            # Show us on console what was found
            $SearchResult.Updates | ft Title,AutoSelectOnWebSites
            # Create collections
            $updatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl
            $downloader = $Session.CreateUpdateDownloader()
            $updatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl
            # Download all detected updates
            $SearchResult.Updates | % {$updatesToDownload.Add($_)}
            $downloader.Updates = $updatesToDownload
            Write-Verbose -Message "Downloading Updates"
            $downresults = $downloader.Download()
            if ($downresults.ResultCode -eq 2) {
                Write-Verbose -Message 'Successfully downloaded'
                $updatesToDownload | Where { -not($_.isInstalled) -and $_.isDownloaded} | % {$updatesToInstall.Add($_)}
                # Install updates
                $installer = $Session.CreateUpdateInstaller()
                $installer.Updates = $updatesToInstall
                Write-Verbose -Message "Installing Updates"
                $installationResult = $installer.Install()
                if ($installationResult) {
            } else {
                Write-Warning -Message "Failed to downlad udpates"
        } else {
            Write-Warning -Message "Failed to search for updates"
    End {}
    # Call our function in verbose mode
    Install-WSUSUpdates -Verbose

    Voilà 😎
    You are of course free to choose the best way to launch this script. I’m using scheduled tasks in my environment.
    The above script can also be modified to install only Windows 7 SP1 as some .Net updates make it fail with the following error:
    Installation Failure: Windows failed to install the following update with error 0x80070643: Windows 7 Service Pack 1 for x64-based Systems (KB976932).

10 thoughts on “WSUS on Windows Server 2012 Core from scratch

  1. Pingback: Installing and Configuring WSUS with Powershell | smsagent

  2. First of all Emin, wow, thanks very much for sharing your expertise. When I tried to use your code, there was an error trying to use the code in Step 6 which goes:
    “# Select items to be downloaded and installed
    $SearchResult.Updates | Select-item -By Title,AutoSelectOnWebSites -Multi | % {$updatesToDownload.Add($_)} ”

    There error returned was: Exception calling “Add” with “1” argument(s): “Specified cast is not valid.”
    At line:2 char:71
    + $SearchResult.Updates | Select-object Title,AutoSelectOnWebSites | % {$updatesTo …

    Do you know why this is?

    • Hi,
      If you do


      You’ll see that the collection excepts IUpdate objects to be added.
      But when you do

      $SearchResult.Updates | Select-object Title,AutoSelectOnWebSites | select -First 1 | gm

      When you used the Select-Object cmdlet, you actually somehow converted the IUpdate objects to Selected.System.Management.Automation.PSCustomObject types of objects.
      This type of object isn’t accepted by the Add method, that’s why you get the cast error message.
      In my post, I used the Select-Item that you can get from
      Another method that leaves the object intact and let you choose what to add to the download collection is the Out-GridView
      This is also discussed in the blog post a bit later and the following would work:

      $SearchResult.Updates | Out-GridView -PassThru | % {$updatesToDownload.Add($_)} 

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.