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
# 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
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
# 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/
# 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
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/
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/
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.
# 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
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
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:
- Step 3: Configure WSUS
- Deploy Windows Server Update Services in Your Organization
- Installing WSUS on Windows Server “8” Beta using PowerShell
# 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()
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) }
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" }
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).
To install WSUS you can also read this article from Boe Prox on the Hey Scripting Guy’s blog http://blogs.technet.com/b/heyscriptingguy/archive/2013/04/15/installing-wsus-on-windows-server-2012.aspx
Pingback: Installing and Configuring WSUS with Powershell | smsagent
This is excelent article. Bravo ! Keep it up good work ! Best regards, Nenad
Thx 😀
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,
Thanks.
If you do
You’ll see that the collection excepts IUpdate objects to be added.
But when you do
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 http://gallery.technet.microsoft.com/scriptcenter/Select-Item-V-10-beta-5e579fa6
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:
Many thanks Emin, it was the select-item function that I was missing.
Hi Emin, I forgot to mention my running environment is Windows Server 2012 R2.
Many thanks
Hi,
No problem, I’m also running on Windows 2012 R2 now 🙂
Reblogged this on Surviving Within IT and commented:
Core is always the way to do it.
Pingback: WSUS on Windows Server 2012 Core from scratch | Yogesh