Deploying Windows Defender updates with WSUS

Context:

I’ve been recently alerted by a Nessus report that Windows Defender wasn’t up-to-date. Nessus actually raised the following two alerts about the Malware Protection Engine in various products:

The Nessus database references the issues on these pages:

And both Nessus reports link to http://support.microsoft.com/kb/2510781, which says:

Note Windows Defender may be disabled when Microsoft Security Essentials (MSE) or Forefront Endpoint Protection (FEP) is being installed. This is by design, as MSE and FEP are functional supersets of Windows Defender. The currently active product will receive engine and definition updates accordingly.

In other words, just deploying the latest Definition Update for whatever installed Malware Protection Engine would fix these two Nessus alerts 😀

Hands-on! Update the WSUS server configuration

Note that the following will run on a WSUS server built-in Windows 2012 R2.

# View what are the currently selected products
(Get-WsusServer).GetSubscription().GetUpdateCategories() | 
Format-Table Title,Id -AutoSize

NB: One cannot just add new products or classifications because there’s no method for this purpose 😦

# Select the products I want and update the WSUS config
$subscription = (Get-WsusServer).GetSubscription()
$products = (Get-WsusServer).GetUpdateCategories() | Where {
    $_.Id -in @(
        'bfe5b177-a086-47a0-b102-097e4fa1f807', # Windows 7
        '6407468e-edc7-4ecd-8c32-521f64cee65e', # Windows 8.1
        '8c3fcc84-7410-4a95-8b89-a166a0190486'  # Windows Defender
    )
}
$coll = New-Object -TypeName Microsoft.UpdateServices.Administration.UpdateCategoryCollection
$products | foreach { $coll.Add($_) }
$subscription.SetUpdateCategories($coll)
$subscription.Save()
# View what are the currently selected classifications
(Get-WsusServer).GetSubscription().GetUpdateCategories() |
Format-Table Title,Id -AutoSize
# Select the classifications I want and update the WSUS config
$subscription = (Get-WsusServer).GetSubscription()
$classifications = (Get-WsusServer).GetUpdateClassifications() | Where {
    $_.Id -in @(
        'e6cf1350-c01b-414d-a61f-263d14d133b4', # Critical Updates
        '0fa1201d-4330-4fa8-8ae9-b877473b6441', # Security Updates
        '68c5b0a3-d1a6-4553-ae49-01d3a7827828', # Service Packs
        'e0789628-ce08-4437-be74-2495b842f43b'  # Definition Updates 
    )
}
$coll = New-Object -TypeName Microsoft.UpdateServices.Administration.UpdateClassificationCollection
$classifications  | foreach { $coll.Add($_) }
$subscription.SetUpdateClassifications($coll)
$subscription.Save()

Deploy the Definition updates

First the WSUS needs to be re-synced from its upstream source (Microsoft Update, in my case)

(Get-WsusServer).GetSubscription().StartSynchronization()

Whatever the speed of your ISP link, go and get a coffee or your favorite beverage.
When you’re back, check the progress of the sync.

(Get-WsusServer).GetSubscription().GetSynchronizationStatus()   

…or if it finished, check it’s status

(Get-WsusServer).GetSubscription().GetLastSynchronizationInfo()

There are two methods to deploy definition updates either by using an ADR (Automatic Deployment Rule) or manually. The ADR method is documented on this page KB919772

But let’s do it manually as I don’t like ADR 😛

Let’s figure out what needs to be deployed:

(Get-WsusServer).SearchUpdates("Defender") | 
Where { 
    $_.PublicationState -ne 'Expired' -and
    -not($_.isSuperseded) } | 
Sort-Object CreationDate | 
Select -Last 2 | 
Format-Table Title,is* -AutoSize

Humm.. there are two different KB. KB915597 is for Windows 7/2008R2 and KB2267602 (that leads to a Oops!! page currently) for Windows 8.1/2012R2

# Select my target group: Windows 7 computers
$targetgroup = (Get-WsusServer).GetComputerTargetGroups() | 
Where Name -eq "Windows 7 x64"

# Make sure nothing is approved
# or mark as 'notapproved' any previously approved Defender updates
# from last month
(Get-WsusServer).SearchUpdates("Defender") | 
Where isApproved | ForEach-Object -Process {            
  $_.Approve(            
[Microsoft.UpdateServices.Administration.UpdateApprovalAction]::NotApproved,            
   $targetgroup            
  )            
 }

# Approve the latest definition update for Windows 7
(Get-WsusServer).SearchUpdates("Defender") | 
Where { 
    $_.PublicationState -ne 'Expired' -and  
    -not($_.isSuperseded)  -and
    ($_.Title -match "915597")
} |
Sort-Object CreationDate | 
Select -Last 1 | foreach {
 $_.Approve(            
[Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,            
  $targetgroup            
 )
}

PowerShell and WSUS rocks! No doubt 😎

Advertisements

WSUS 2012: viewing superseded and superseding updates

I’ve shown over the last few months how Powershell can help manage a WSUS built-in a Windows Server 2012:

  • Problem

Now that Office 2010 SP2 has been released and that the catalog of my WSUS has been updated, I cannot filter anymore superseded Office 2010 updates with the following code:

# View superseded updates that were previously approved            
(Get-WsusServer).SearchUpdates("2010") | Where {            
 ($_.IsSuperseded) -and            
 ($_.isApproved)             
} |             
ft Title,SecurityBulletins,IsApproved,IsSuperseded -AutoSize 

If I run this, I got more than I expected:

I was just expecting bulletin MS12-34 being supersed by MS13-054 😦

It appears that the ‘IsSupersed’ property isn’t precise enough and that I need more information like the ones provided by the “Updates superseding this update” and the “Updates superseded by this update” properties that you can see in the above snapshot of the WSUS console.

  • Solution

The above search returns a collection of Microsoft.UpdateServices.Internal.BaseApi.Update objects.

Each object has many properties by default but not the aboves juicy ones (“Updates superseding this update”) I’m looking for.

That said, each object has also many methods that could help.

To get a collection of “Updates superseding this update”, the GetRelatedUpdates needs to be used according the following MSDN page on How to Get Update Details

How should I implement and use this method?

I can enumerate UpdateRelationship values and their meaning with this code:

[System.Enum]::GetValues([Microsoft.UpdateServices.Administration.UpdateRelationship]) | ForEach-Object -Process {
    '{0} -> {1}' -f ([Microsoft.UpdateServices.Administration.UpdateRelationship]::$_.Value__),$_
}

This step will just allow me to save some typing below…

Now the idea is to add on the fly a ‘UpdatesThatSupersedeThisUpdate’ property to each Microsoft.UpdateServices.Internal.BaseApi.Update object returned by the search.
This property should be filled-in with the collection of Titles returned by the GetRelatedUpdates method.
Let’s have a look at the changes I made to achieve this:

# View superseded updates that were previously approved            
(Get-WsusServer).SearchUpdates("2010") | Where {            
 ($_.IsSuperseded) -and            
 ($_.isApproved)             
} | ForEach-Object -Process {
    $_ | Add-Member -MemberType ScriptProperty -Name UpdatesThatSupersedeThisUpdate -Value {
    ($this.GetRelatedUpdates(5)).Title
    } -Force -PassThru
} | ? UpdatesThatSupersedeThisUpdate -notmatch "Service\sPack\s2" |
 fl Title,SecurityBulletins,IsApproved,UpdatesThatSupersedeThisUpdate 

I’ve added a foreach loop that processes each update object and adds a collection of titles of updates that superseded this update by first calling the GetRelatedUpdates method and then using an implicit foreach to return only the title property. The Add-Member cmdlet allows me to create this script property on the fly.

As this new scriptproperty is a multiline string, the match operator can be used to filter it.

I got only MS12-034 as a result, perfect that’s exactly what I was looking for 😎
MS12-034 is superseded by SP2 but SP2 isn’t the only update that supersedes MS12-034.

Note also that I’m actually using a feature of the match comparison operator. When used on multilines, it doesn’t return true or false.

This is what you can read with

Get-help about_Comparison_Operators

If the input is a collection, the -Match and -NotMatch operators return
the matching members of that collection, but the operator does not
populate the $Matches variable.

Did I already say that Powershell rocks!? 😀

Quick follow-up on WSUS on Windows Server 2012 Core from scratch

I’ve been using WSUS from the Powershell console since I published my post on WSUS on Windows Server 2012 Core from scratch in February 2013.

I’d like to mention two mistakes I did:

  • I don’t really need to decline updates when they have approved. They can be also “not approved” which is their original state.
  • My filter for Office 2010 was a little bit too restrictive.

Let’s see how I manage my updates for Windows 7 and do not reproduce the above mistakes

  • Start a synchronization
  • # Check the date of the last sync (it's configured to sync manually)            
    (Get-WsusServer).GetSubscription().GetLastSynchronizationInfo()            
                
    # Initiate a sync            
    (Get-WsusServer).GetSubscription().StartSynchronization()            
                
    # View its progress            
    (Get-WsusServer).GetSubscription().GetSynchronizationStatus()            
                
    # Check that the last sync date is today and a success            
    (Get-WsusServer).GetSubscription().GetLastSynchronizationInfo()
  • Handle Windows 7 security updates
  • # Find any update that has 'Windows 7' string mentioned            
    $allW7updates = (Get-WsusServer).SearchUpdates("Windows 7")            
                
    # Select my target group of computers            
    $targetgroup = (Get-WsusServer).GetComputerTargetGroups() |             
    Where Name -eq "Windows 7 x64"            
                
    # View all the x64 updates for Windows 7            
    $allW7updates | Where { (-not($_.IsSuperseded)) -and            
     ($_.Title -match "x64") -and             
     ($_.UpdateClassificationTitle -eq "Security Updates") -and            
     (-not($_.isApproved)) -and            
     (-not($_.isDeclined))             
    } |             
    ft Title,State,KnowledgebaseArticles,SecurityBulletins -AutoSize            
                
    # Mark these required updates as 'to be installed' by client computers            
    $allW7updates | Where { (-not($_.IsSuperseded)) -and            
     ($_.Title -match "x64") -and            
     ($_.UpdateClassificationTitle -eq "Security Updates") -and            
     (-not($_.isApproved)) -and            
     (-not($_.isDeclined))} |            
    Where {            
     $_.Title -notmatch ".NET Framework 4"            
    } | ForEach-Object -Process {            
     $_.Approve(            
      [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,            
      $targetgroup            
     )            
    }            
                
    # View superseded updates that were previously approved            
    $allW7updates | Where {            
     ($_.IsSuperseded) -and            
     ($_.isApproved)            
    } | ft Title,SecurityBulletins            
                
    # Remove them by flagging them as 'not approved'            
    $allW7updates |             
     Where { ($_.IsSuperseded) -and ($_.isApproved) } |            
     ForEach-Object -Process {            
      $_.Approve(            
       [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::NotApproved,            
       $targetgroup            
      )            
     }
  • Handle Office 2010 updates
  • # View all Office 2010 (32bit) updates            
    (Get-WsusServer).SearchUpdates("2010") | Where {            
     (-not($_.IsSuperseded)) -and            
     ($_.Title -match "32-bit") -and            
     ($_.UpdateClassificationTitle -eq "Security Updates") -and            
     (-not($_.isApproved)) -and            
     (-not($_.isDeclined))             
    } | ft Title,SecurityBulletins,IsApproved,IsSuperseded -AutoSize            
                
    # Mark these required updates as 'to be installed' by client computers            
    (Get-WsusServer).SearchUpdates("2010") | Where {            
     (-not($_.IsSuperseded)) -and            
     ($_.Title -match "32-bit") -and            
     ($_.UpdateClassificationTitle -eq "Security Updates") -and            
     (-not($_.isApproved)) -and            
     (-not($_.isDeclined))             
    } | ForEach-Object -Process {            
     $_.Approve(            
      [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,            
      $targetgroup            
     )            
    }            
                
    # View superseded updates that were previously approved            
    (Get-WsusServer).SearchUpdates("2010") | Where {            
     ($_.IsSuperseded) -and            
     ($_.isApproved)             
    } |             
    ft Title,SecurityBulletins,IsApproved,IsSuperseded -AutoSize            
                
    # Remove them by flagging them as 'not approved'            
    (Get-WsusServer).SearchUpdates("2010") | Where {            
     ($_.IsSuperseded) -and             
     ($_.isApproved) } |             
    ForEach-Object -Process {            
     $_.Approve(            
      [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::NotApproved,            
      $targetgroup            
     )            
    }

    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 192.168.0.100 -DefaultGateway 192.168.0.1 -PrefixLength 24            
      
      # Set the DNS server            
      Get-NetAdapter | ? Status -eq "Up" | Set-DnsClientServerAddress -ServerAddresses 192.168.0.2,192.168.0.3            
                  
      # 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

      Get-WUSettings can be found on https://p0w3rsh3ll.wordpress.com/2013/01/09/get-windows-update-client-configuration/

    • 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.AddService2("7971f918-a847-4430-9279-4a52d1efe18d",7,"")            
      $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            
      $updatesToDownload.clear()            
      $downloader = $Session.CreateUpdateDownloader()            
      $updatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl            
      $updatesToInstall.clear()            
                  
      # 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            
      $installationResult

      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=3.0.0.0,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 https://p0w3rsh3ll.wordpress.com/2012/10/10/switch-from-windows-2012-core-to-gui-mode-hands-on/

      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: http://gallery.technet.microsoft.com/scriptcenter/Select-Item-V-10-beta-5e579fa6

    • 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: https://p0w3rsh3ll.wordpress.com/2012/06/18/changing-cdrom-drive-letter-on-windows-2012/

    • Step8: Change the computer name and join the domain
    • Add-Computer -DomainName my.corp.domain.name.fqdn -Server my.corp.DC.name.fqdn -Credential (Get-Credential) -NewName CO2            
                  
      Restart-computer

      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            
      Get-Culture            
      Set-Culture en-GB            
      # Logoff            
      Get-NetFirewallRule | ? Enabled | ft Name,DisplayName,Action
      

      For the update history, see the comment in the following post: https://p0w3rsh3ll.wordpress.com/2012/10/25/getting-windows-updates-installation-history/

    • 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
      (http://go.microsoft.com/fwlink/?LinkId=235499) 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            
                  
      # http://blogs.technet.com/b/sus/archive/2012/03/20/installing-wsus-on-windows-server-8-beta-using-powershell.aspx            
      & '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            
      (Get-WsusServer).GetConfiguration()            
                  
      # 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            
      $config.SetEnabledUpdateLanguages("en")            
      $config.Save()            
                  
      # View enabled Languages            
      (Get-WsusServer).GetConfiguration().GetEnabledUpdateLanguages()            
                  
      # Begin initial synchronization            
      $subscription = (Get-WsusServer).GetSubscription()            
      $subscription.StartSynchronizationForCategoryOnly()            
      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($_) }            
      $subscription.SetUpdateCategories($coll)            
      $subscription.Save()            
                  
      # View all classifications            
      Get-WsusClassification | % {'{0}, # {1}' -f $_.Classification.Id,$_.Classification.Title }            
      (Get-WsusServer).GetUpdateClassifications()            
                  
      # As the Title is based on             
      $host.CurrentCulture            
                  
      # 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($_) }            
      $subscription.SetUpdateClassifications($coll)            
      $subscription.Save()            
                  
      # Check selected classifications            
      (Get-WsusServer).GetSubscription().GetUpdateClassifications()            
                  
      # Start a sync            
      (Get-WsusServer).GetSubscription().StartSynchronization()            
                  
      # View it progress            
      (Get-WsusServer).GetSubscription().GetSynchronizationProgress()            
                  
      # Wait for sync            
      $subscription = (Get-WsusServer).GetSubscription()            
      while ($subscription.GetSynchronizationProgress().ProcessedItems -ne $subscription.GetSynchronizationProgress().TotalItems) {            
          Write-Progress -PercentComplete (            
          $subscription.GetSynchronizationProgress().ProcessedItems*100/($subscription.GetSynchronizationProgress().TotalItems)            
          ) -Activity "Sync"             
      }            
                  
      # Make sure the default automatic approval rule is not enabled            
      (Get-WsusServer).GetInstallApprovalRules()            
                  
      # View synchronization schedule options            
      (Get-WsusServer).GetSubscription()            
                  
      # Get Configuration state            
      (Get-WsusServer).GetConfiguration().GetUpdateServerConfigurationState()            
                  
      # Get WSUS status            
      (Get-WsusServer).GetStatus()
    • Step13: Approve specific updates for a custom target group
    • Source: http://technet.microsoft.com/en-us/library/hh852346.aspx

      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            
      (Get-WsusServer).GetComputerTargetGroups()            
                  
      # 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 {            
          $_.Approve([Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,$targetgroup)            
      }            
                  
      # 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([Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,$targetgroup)            
      }            
                  
      # 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 {            
          $_.Approve([Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,$targetgroup)            
      }            
                  
      # 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 {            
          $_.Approve([Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,$targetgroup)            
      }            
                  
      # 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 {            
          $_.Approve([Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install,$targetgroup)            
      }

    • 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 {
      [cmdletBinding()]
      param()
      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"
              break
          }
      } 
      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
              $updatesToDownload.clear()
              $downloader = $Session.CreateUpdateDownloader()
              $updatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl
              $updatesToInstall.clear()
      
              # 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) {
                      $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).