Update
——————————————————————————
A small update on this topic. If you do not want your customers to face error 0x800F0906 when they try to install .NET Fraemwork 3.5 on Windows Server 2012 or 2012 R2 I would suggest you to install and disable .net FRamework 3.5 feature with DISM prior syspreping your images. The following commands should do the work:
For Windows Server 2012:
dism /image:”D:\2012″ /enable-feature /featurename:NetFx3 /All /Source:G:\sources\sxs
dism /image:”D:\2012″ /disable-feature /featurename:NetFx3
where D:\2012 is your mounted 2012 VHD and G:\sources\sxs is the installation files on your Windows Server 2012 setup DVD.
For Windows Server 2012 R2
dism /image:”D:\2012r2″ /enable-feature /featurename:NetFx3 /All /Source:G:\sources\sxs
dism /image:”D:\2012r2″ /disable-feature /featurename:NetFx3
where D:\2012r2 is your mounted 2012 R2 VHD and G:\sources\sxs is the installation files on your Windows Server 2012 R2 setup DVD.
After executing the commands you can commit the image. That way when your customers try to install .NET Framework 3.5 will not recieved the error and do not have to uinstall KB2966828: MS14-046: Description of the security update for the .NET Framework 3.5 on Windows 8.1 and Windows Server 2012 R2: August 12, 2014 or KB2966827: MS14-046: MS14-046: Description of the security update for the .NET Framework 3.5 on Windows 8 and Windows Server 2012: August 12, 2014.
—————————————————————————–
When you go to Microsoft Azure and try to create a Windows Server virtual machine you will see that Microsoft displays a couple of images with different dates:
As you can see you can choose a different patch level.
And when you create virtual machine in Azure and logon on it to see installed updates you will see that most of them are installed on one particular date:
This got me thinking on how Azure makes them own Windows Server images? And of course is there a way to do that on-premise?
So you’ve probably figured out already how Azure probably do it:
- Create base image for every OS version without none or some updates.
-
Make a copy of a base image and update it with the latest updates.
-
Publish the updated image to the portal.
But let’s go into detail about the three main OS versions (Windows Server 2012 R2, Windows Server 2012, Windows Server 2008 R2 SP1) and how to create these images with some System Center magic.
Let’s first start with:
Creating base image for Windows Server 2008 R2 SP1
First you need to grab Windows Server 2008 R2 SP1 iso from MSDN or Volume license. As Azure is using Datacenter Edition for all their server versions I will use the same.
Than you can easily convert the iso to VHDX with the following script:
.\Convert-WindowsImage.ps1 -SourcePath “D:\en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso” -VHDFormat VHDX -Edition “ServerDataCenter” -SizeBytes 120GB -RemoteDesktopEnable -VHDPath D:\WS2008R2SP1Base.vhdx -VHDType Dynamic
If you want to use other editions or different disk size you can change the parameters to whatever makes sense in your case.
After that it is good to install IE11 in advance. That could happen by mounting the VHDX file with dism:
dism /mount-image /imagefile:”D:\WS2008R2SP1Base.vhdx” /mountdir:D:\2008r2 /index:1
Than you apply IE11 prerequisites:
dism /Image:”D:\2008r2″ /add-package /Packagepath:”D:\Windows6.1-KB2729094-v2-x64.msu“
dism /Image:”D:\2008r2″ /add-package /Packagepath:”D:\Windows6.1-KB2726535-x64.msu“
dism /Image:”D:\2008r2″ /add-package /Packagepath:”D:\Windows6.1-KB2670838-x64.msu“
dism /Image:”D:\2008r2″ /add-package /Packagepath:”D:\Windows6.1-KB2834140-v2-x64.msu“
dism /Image:”D:\2008r2″ /add-package /Packagepath:”D:\Windows6.1-KB2786081-x64.msu“
And now you can install IE11 itself:
dism /Image:”D:\VHD\2008″ /add-package /Packagepath:”D:\IE11-Windows6.1-KB2841134-x64.cab“
As you will probably use this image on Hyper-V server 2012 or 2012 R2 it is good to install the latest Integration Service. They can be found by mounting iso file located in “C:\Windows\System32\vmguest.iso” on Windows 8.1 or Windows Server 2012 R2:
dism /Image:”D:\2008r2″ /add-package /Packagepath:”E:\support\amd64″
After applying this last update you can save the image by committing the changes:
dism /unmount-image /mountdir:”D:\2008r2″ /commit
If you want too apply some additional configurations to that base image like firewalls rules and etc. you need to create a VM from that image. Install the OS. Make the changes you want to the VM and sysprep it with the following command:
.\Sysprep.exe /generalize /shutdown /oobe
After the sysprep command your Windows Server 2008 R2 SP1 base image is ready.
Creating base image for Windows Server 2012
The steps for Windows Server 2012 base image are similar. Download your iso from MSDN or Volume License.
Convert the iso:
.\Convert-WindowsImage.ps1 -SourcePath “D:\en_windows_server_2012_x64_dvd_915478.iso” -VHDFormat VHDX -Edition “ServerDataCenter” -SizeBytes 120GB -RemoteDesktopEnable -VHDPath D:\WS2012Base.vhdx -VHDType Dynamic
Mount the vhd with dism:
dism /mount-image /imagefile:”D:\WS2012Base.vhdx” /mountdir:D:\2012 /index:1
There is no IE11 for Windows Server 2012 but there is one important update that you need to apply in advance:
dism /Image:”D:\2012″ /add-package /Packagepath:”D:\Windows8-RT-KB2871777-x64.msu“
Through several tests I’ve found that this update is needed for future proper updating of this base image.
Apply the latest Hyper-V Integration services if needed:
dism /Image:”D:\2012″ /add-package /Packagepath:”E:\support\amd64″
Commit the changes:
dism /unmount-image /mountdir:”D:\2012″ /commit
If also you need to apply some configuration changes to this image you need to start it as a virtual machine, make the changes and sysprep it:
.\Sysprep.exe /generalize /shutdown /oobe
And now your Windows Server 2012 base image is also ready.
Creating base image for Windows Server 2012 R2 Update
This one is the most easy one as you just need to convert it:
.\Convert-WindowsImage.ps1 -SourcePath “D:\en_windows_server_2012_r2_with_update_x64_dvd_4065220.iso” -VHDFormat VHDX -Edition “ServerDataCenter” -SizeBytes 120GB -RemoteDesktopEnable -VHDPath D:\WS2008R2SP1Base.vhdx -VHDType Dynamic
You do not need to mount it dism as there are no updates that you need to add and the the latest Integration services are already there.
For additional configurations you have to do the same steps as the other two.
Prerequisites
Now that we have our base images let’s on the solution how to have new updated image every month. I will start with the prerequisites. Later on when you look at how the whole solution works you may find other ways to do it in your environment if you do not have some of them.
We will need the following servers:
- WSUS
- VMM
- SCSMA
The WSUS server is needed so we can grab all Windows Updates directly from the WSUS Content share. But when you have WSUS server connected to VMM the updates will be downloaded on the WSUS content share after you create Update Baselines in VMM, add updates to these baselines and assign at least one server in VMM to these baselines. So let’s create 3 empty Update baselines in VMM:
- WS2012R2
- WS2012
- WS2008R2
Do not add updates to them but assign at least one server in VMM to them. We will update these baselines later with SMA Runbook.
On the VMM server on C:\ drive you can create three folders:
- C:\ovpWS2012R2
- C:\ovpWS2012
- C:\ovpWS2008R2
We will use these folders to mount the different images on them with DISM.
Next create a share on a server. For example named Base. I create such share on my VMM Library server. On that share I copy all the base images we’ve created earlier.
The last part of the prerequisites puzzle is Service Management Automation.
Let’s start first by creating some assets in my new favorite automation solution.
Create Connection asset named VMMConnection and for type VirtualMachineManager. For credentials use service account that has Administrator rights on your VMM server. That account should also have full share and NTFS permissions on the Base share that you’ve created earlier. And for computer name you should use the FQDN of your VMM server.
Next you need to create Variable asset of Type String. For name enter WSUSServer and for value the FQDN of your WSUS server.
The last asset you need to create is also variable. For name use VMMLibraryServer and for value the FQDN of your VMM Library server.
Now that we have our SMA assets create 5 empty SMA Runbooks:
- Update-VMMBaslines
- Update-BaseImageWS2012R2
- Update-BaseImageWS2012
- Update-BaseImageWS2008R2
- Set-VHDProductKey
In SMA you can open Update-VMMBaselines for edit. Remove the empty workflow and copy the following runbook directly:
workflow Update-VMMBaselines
{
Connection to access VMM server.
$VmmConnection = Get-AutomationConnection -Name ‘VmmConnection’
$VmmServerName = $VmmConnection.ComputerName
# Create a PSCredential from the ‘Username’ and ‘Password’ fields within
‘VmmConnection’ because this is the form of authentication that an
inlinescript accepts.
$SecurePassword = ConvertTo-SecureString -AsPlainText -String $VmmConnection.Password -Force
$VmmCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $VmmConnection.Username, $SecurePassword
inlinescript {
# Import VMM module.
Import-Module virtualmachinemanager
Connect to VMM server.
Get-SCVMMServer -ComputerName $Using:VmmServerName
Import-Module VirtualMachineManager
Get-SCUpdateserver | Start-SCUpdateServerSynchronization
#Windows Server 2008 R2 Update Baseline
$ContosoBaseline2008R2 = Get-SCBaseline | where { $_.Name -eq “WS2008R2” }
$baseline2008R2 = Get-SCBaseline -ID $ContosoBaseline2008R2.ID
$addedUpdateList2008R2 = @()
$SCVMMJobGUID = [System.Guid]::NewGuid()
$ContosoLatestUpdates2008R2 = Get-SCUpdate | where { ($.UpdateClassification -eq “Security Updates” -or $.UpdateClassification -eq “Critical Updates” -or $.UpdateClassification -eq “Updates”-or $.UpdateClassification -eq “Update Rollups”) -and ($.Products -eq “Windows Server 2008 R2” -or $.Products -eq “Windows Server 2003, Datacenter Edition, Windows Server 2003, Windows Server 2008, Windows Server 2008 R2” -or $.Products -eq “Windows 7, Windows Server 2008 R2” -or $.Products -eq “Windows Vista, Windows Server 2008, Windows 7, Windows Server 2008 R2” -or $.Products -eq “Windows 7, Windows Embedded Standard 7, Windows Server 2008 R2” -or $.Products -eq “Windows Server 2003, Datacenter Edition, Windows Server 2003, Windows Vista, Windows XP x64 Edition, Windows Server 2008, Windows 7, Windows Server 2008 R2” -or $.Products -eq “Windows Server 2003, Datacenter Edition, Windows Server 2003, Windows XP, Windows Vista, Windows XP x64 Edition, Windows Server 2008, Windows 7, Windows Server 2008 R2” -or $.Products -eq “Windows Server 2003, Datacenter Edition, Windows Server 2008, Windows Server 2003, Windows Server 2008 R2” -or $.Products -eq “Windows Vista, Windows Server 2003, Datacenter Edition, Windows Server 2008, Windows 7, Windows Server 2003, Windows Server 2008 R2”) -and $.IsExpired -eq $false -and $_.CreationDate -gt ‘2/16/2011 10:00’}
# Compare existing updates with new one
Compare-Object -ReferenceObject $ContosoBaseline2008R2.Updates -DifferenceObject $ContosoLatestUpdates2008R2 -IncludeEqual | % {
if($.SideIndicator -eq ‘=>’) { $addedUpdateList2008R2 += Get-SCUpdate -ID $.inputobject.id }
}
Set-SCBaseline -Baseline $baseline2008R2 -Name $ContosoBaseline2008R2.Name.ToString() -RunAsynchronously -AddUpdates $addedUpdateList2008R2 -JobGroup $SCVMMJobGUID.ToString() -StartNow
#Windows Server 2012 Update Baseline
$ContosoBaseline2012 = Get-SCBaseline | where { $_.Name -eq “WS2012” }
$baseline2012 = Get-SCBaseline -ID $ContosoBaseline2012.ID
$addedUpdateList2012 = @()
$SCVMMJobGUID = [System.Guid]::NewGuid()
$ContosoLatestUpdates2012 = Get-SCUpdate | where { ($.UpdateClassification -eq “Security Updates” -or $.UpdateClassification -eq “Critical Updates” -or $.UpdateClassification -eq “Updates”-or $.UpdateClassification -eq “Update Rollups”) -and ($.Products -eq “Windows Server 2012” -or $.Products -eq “Windows 8, Windows Server 2012”) -and $_.IsExpired -eq $false}
# Compare existing updates with new one
Compare-Object -ReferenceObject $ContosoBaseline2012.Updates -DifferenceObject $ContosoLatestUpdates2012 -IncludeEqual | % {
if($.SideIndicator -eq ‘=>’) { $addedUpdateList2012 += Get-SCUpdate -ID $.inputobject.id }
}
Set-SCBaseline -Baseline $baseline2012 -Name $ContosoBaseline2012.Name.ToString() -RunAsynchronously -AddUpdates $addedUpdateList2012 -JobGroup $SCVMMJobGUID.ToString() -StartNow
#Windows Server 2012 R2 Update Baseline
$ContosoBaseline2012R2 = Get-SCBaseline | where { $_.Name -eq “WS2012R2” }
$baseline2012R2 = Get-SCBaseline -ID $ContosoBaseline2012R2.ID
$addedUpdateList2012R2 = @()
$SCVMMJobGUID = [System.Guid]::NewGuid()
$ContosoLatestUpdates2012R2 = Get-SCUpdate | where { ($.UpdateClassification -eq “Security Updates” -or $.UpdateClassification -eq “Critical Updates” -or $.UpdateClassification -eq “Updates”-or $.UpdateClassification -eq “Update Rollups”) -and ($.Products -eq “Windows Server 2012 R2” -or $.Products -eq “Windows 8.1, Windows Server 2012 R2”) -and $_.IsExpired -eq $false}
# Compare existing updates with new one
Compare-Object -ReferenceObject $ContosoBaseline2012R2.Updates -DifferenceObject $ContosoLatestUpdates2012R2 -IncludeEqual | % {
if($.SideIndicator -eq ‘=>’) { $addedUpdateList2012R2 += Get-SCUpdate -ID $.inputobject.id }
}
Set-SCBaseline -Baseline $baseline2012R2 -Name $ContosoBaseline2012R2.Name.ToString() -RunAsynchronously -AddUpdates $addedUpdateList2012R2 -JobGroup $SCVMMJobGUID.ToString() -StartNow
}-PSComputerName $VmmServerName -PSCredential $VmmCredential
}
I’ve took some parts of this script and made some changes to adopt it for my needs. Thank you Markus Lassfolk.
The script basically connects to VMM, Synchronizes the updates in VMM with the WSUS server and adds updates to the three baselines we’ve created earlier. The script is made in a way to add all the updates available for every corresponding OS version including .net Framework updates. Of course you can modify it whatever suits your needs.
After you import the runbook, save it and run it for first time you may need to wait some time until all added updates are downloaded on your WSUS server. Remember that download will be initiate only if you have at least one server assigned to your VMM baselines.
Next you can open for edit Update-BaseImageWS2012R2 SMA Runbook. Delete the contents in it and copy the following SMA runbook in it directly:
<#
Version 1.0
.SYNOPSIS
Update WS2012R image
#>
workflow Update-BaseImageWS2012R2
{
Connection to access VMM server.
$VmmConnection = Get-AutomationConnection -Name ‘VmmConnection’
$VmmServerName = $VmmConnection.ComputerName
# Create a PSCredential from the ‘Username’ and ‘Password’ fields within
‘VmmConnection’ because this is the form of authentication that an
inlinescript accepts.
$SecurePassword = ConvertTo-SecureString -AsPlainText -String $VmmConnection.Password -Force
$VmmCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $VmmConnection.Username, $SecurePassword
#Connection to access WSUS Server
$WSUS = Get-AutomationVariable -Name ‘WSUSSERVER’
#Connection to access VMM Library Server
$VMMLirbary = Get-AutomationVariable -Name ‘VMMLibraryServer’
inlinescript {
# Import VMM module.
Import-Module virtualmachinemanager
Connect to VMM server.
Get-SCVMMServer -ComputerName $Using:VmmServerName
#Get all available Update at WSUS
$Updatelistcab = get-childitem -Path “\$using:wsus\wsuscontent” -include *.cab -recurse -File
$Updatelistmsu = get-childitem -Path “\$using:wsus\wsuscontent” -include *.msu –recurse -File
#Path for mounting
$OVPPath=”C:\ovp2012R2″
#Get Path to Base Image
$VHDGOLDPath=”\$using:VMMLirbary\Base\WS2012R2VLBase.vhdx”
#Get Path to Updated Image
$VHDPath=”\$using:VMMLirbary\Base\WS2012R2Updated.vhdx”
#Check if Updated VHD exists and delete it
$VHDexists=Test-Path $VHDPath
If ($VHDexists) {
Remove-Item $VHDPath
}
#Try to copy Base VHD
try {
Copy-Item $VHDGOLDPath $VHDPath
}
Catch {
Write-Output “GODL VHD cannot be copied”
}
#Mount Image and try to updated from WSUS updates
try{
Mount-WindowsImage -ImagePath “$VHDPath” -Path “$OVPPath” -Index 1
}
catch {
Write-Output “Cannot mount VHD”
}
Foreach ($Updatecab in $Updatelistcab)
{
$UpdateReady=get-windowspackage -PackagePath $Updatecab -Path “$OVPPath”
If ($UpdateReady.PackageState -eq “installed”)
{Write-Output $UpdateReady.PackageName “is already installed”}
elseif ($updateReady.Applicable -eq “true”)
{Add-WindowsPackage -PackagePath $Updatecab.Directory -Path “$OVPPath”}
}
Foreach ($Updatemsu in $Updatelistmsu)
{
add-windowspackage -PackagePath $Updatemsu.Directory -Path “$OVPPath”
}
#Try Dismount and save VHD
Try {
Dismount-WindowsImage -Path “$OVPPath” -save
}
catch {
Write-Output “Cannot Dismount and save VHD”
}
}-PSComputerName $VmmServerName -PSCredential $VmmCredential
}
The runbook will take the WS 2012 R2 base image make a copy of it in the same folder with other name, mount the copied image on a folder on the VMM server and will start updating. Updating is done by taking all available updates on the WSUS content share and trying to apply them one by one. When it is done changes are committed. When running this runbook you may see a lot of errors and warnings but this is normal as many of the updates that are tried to be applied are not for this OS version and are just rejected. This solution for updating is taken from Building Clouds blog and modified for our needs. Depending on your environment this runbook can run for a couple of days even.
I will not post the Runbooks for the other two images as they are basically the same with a few modifications on names.
The last runbook Set-VHDProductKey is kind of optional. If you are deploying Windows Azure Pack VM Roles you might want to embed product keys into your updated VHDs:
<#
Version 1.0
.SYNOPSIS
Set Product Keys to VHDs
#>
workflow Set-VHDProductKey
{
Connection to access VMM server.
$VmmConnection = Get-AutomationConnection -Name ‘VmmConnection’
$VmmServerName = $VmmConnection.ComputerName
# Create a PSCredential from the ‘Username’ and ‘Password’ fields within
‘VmmConnection’ because this is the form of authentication that an
inlinescript accepts.
$SecurePassword = ConvertTo-SecureString -AsPlainText -String $VmmConnection.Password -Force
$VmmCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $VmmConnection.Username, $SecurePassword
#Connection to access WSUS Server
$WSUS = Get-AutomationVariable -Name ‘WSUSSERVER’
#Connection to access VMM Library Server
$VMMLirbary = Get-AutomationVariable -Name ‘VMMLibraryServer’
inlinescript {
# Import VMM module.
Import-Module virtualmachinemanager
Connect to VMM server.
Get-SCVMMServer -ComputerName $Using:VmmServerName
#LibraryName
$tVMMLibraryName=”Library”
#Set KMS Key for WS 2008 R2 Datacenter
$VHD2008R2=Get-SCVirtualHardDisk | where -Property Location -eq “\$using:VMMLirbary\$VMMLibraryName\WS2008R2.vhdx”
Set-SCVirtualHardDisk -VirtualHardDisk $VHD2008R2 -ProductKey “74YFP-3QFB3-KQT8W-PMXWJ-7M648”
#Set KMS Key for WS 2012 Datacenter
$VHD2012=Get-SCVirtualHardDisk | where -Property Location -eq “\$using:VMMLirbary\$VMMLibraryName\WS2012.vhdx”
Set-SCVirtualHardDisk -VirtualHardDisk $tVHD2012 -ProductKey “48HP8-DN98B-MYWDG-T2DCC-8W83P”
#Set Autmoatic Virtual Machine Activation Key for WS 2012 R2 Datacenter
$VHD2012R2=Get-SCVirtualHardDisk | where -Property Location -eq “\$using:VMMLirbary\$VMMLibraryName\WS2012R2.vhdx”
Set-SCVirtualHardDisk -VirtualHardDisk $VHD2012R2 -ProductKey “Y4TGP-NPTV9-HTC2H-7MGQ3-DV4TW”
}-PSComputerName $VmmServerName -PSCredential $VmmCredential
}
You might want to change some values in it depending on where you store your VHDs.
Let’s look at the the whole process of this solution:
The step of copying the updated images to your VMM library is manual but of course you can make that automatic. In fact you can make the whole solution automatic. There are certainly many ways to do that solution like using Orchestrator instead of SMA, mounting the images on your SMA servers and etc.
Hope this solution will be workable and useful solution for you.