Dynamic Provisioning with VMM: Proxy, Windows Updates, and Scripts
In our environment, there are two things that are critical to success of an environment that is dynamically built from scratch – Updates & Internet connectivity. This might seem odd since most would believe that we would utilize WSUS & the Software Update Points in Configuration Manager to do our patching and truth be known we do. However, non-compliant servers with specific hot-fixes are not allowed to come on the network and to avoid big delays we do not push the ConfigMgr clients to our servers “immediately.” Thus, we depend on Windows Update connectivity for our servers and also proxies as we can’t get to the internet without them.
In today’s post, I’m going to share a settings configuration we use in our unattend.xml to ensure that our automated scripts effectively reach the internet and secondly the script itself. The script is shared as-is and credit goes to Ben Shy & Michael Schmidt on my team for the actual building of the script though I’m consuming it in my design of dynamic provisioning.
Setting Proxy Settings to work during Dynamic Provisioning Servers
In some situations, you will have the following settings in your unattend.xml file though upon completion you will not have any proxy settings set. This is very troublesome and I couldn’t locate much of any data or information regarding how to correct this on the internet and had to use internal resources to troubleshoot (Thanks Eric!). For the longest time, I had the following settings in my unattend XML:
Code Snippet
<settings pass="oobeSystem">
<component name="Microsoft-Windows-IE-ClientNetworkProtocolImplementation" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="https://schemas.microsoft.com/WMIConfig/2002/State">
<HKLMProxyEnable>true</HKLMProxyEnable>
<HKLMProxyServer>itgproxy</HKLMProxyServer>
</component>
</settings>
This is all that is discussed in the WAIK unattended help file and thus should just work, right? Wrong! Unfortunately, there was a little unknown (to me) key called ProxySettingsPerUser that needed to get set though this wasn’t outlined anywhere in the documentation. I can’t, unfortunately, provide you a lot of insight as to what is so important other than what is covered in TechNet. It basically changes the Windows behavior where proxy settings are per machine rather than per user. However, I had no idea this was needed nor that I could include it in my unattend.xml file. However, with a little digging, some help, and testing we found that it was absolutely possible to include this in the WAIK’s unattend.xml and it would be honored. Thus, my working XML is the following that sets my proxy settings so that scripts running on the server but not as a user would honor the proxy settings:
Code Snippet
- <settings pass="oobeSystem">
- <component name="Microsoft-Windows-IE-ClientNetworkProtocolImplementation" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="https://schemas.microsoft.com/WMIConfig/2002/State">
- <POLICYProxySettingsPerUser>0</POLICYProxySettingsPerUser>
- <HKLMProxyEnable>true</HKLMProxyEnable>
- <HKLMProxyServer>OurProxy</HKLMProxyServer>
- </component>
- </settings>
After testing, the script ran successfully and completely thus ensuring that our virtual machines were compliant quickly after they hit the network.
Scripting Critical Updates for Windows Server from Windows Update
In this section, we will focus on the script itself that does the work of connecting to Windows update, providing the appropriate client-based information, and installing the updates. The first thing to note that is critical is the error message received when you do not have a viable connection to the internet.
Using Windows Update vs. Microsoft Update: That is the question…
A great number of folks don’t quite know the reason that Microsoft has two viable update engines, Windows (WU) and Microsoft update (MU). The former focuses only on the Windows platform and will only serve the core Windows operating system binaries. The latter, though, is an opt-in service, that offers the ability for servers to get updated and patched that are not only running Windows but additional Microsoft software such as Office, etc. In order for the Microsoft update engine to work, the Windows update agent client is replaced with the Microsoft update agent in Windows Vista & above. For older operating systems, a different ActiveX control is installed in Internet Explorer.
For our environment, we wanted to ensure that we downloaded only critical updates from Windows Update at the time of server creation. In order to do this, we utilized the following sample script:
Code Snippet
'================================================================================
' Microsoft Update
'================================================================================
' Authors: BENSHY/OTHERS/MSDN
' Comments/Cleanup: MICHS 5.14.09
'
'================================================================================
'#############################
' Create Session
'#############################
wscript.echo "-------------------------- Create Session --------------------------"
Set UpdateSession = CreateObject("Microsoft.Update.Session")
Set UpdateSearcher = UpdateSession.CreateUpdateSearcher()
'#############################
' Register Microsoft Update
'#############################
RegisterMu
updateSearcher.ServerSelection = 2
updateSearcher.ServiceID = "7971f918-a847-4430-9279-4a52d1efe18d"
'#############################
' Create List of Updates to Download
'#############################
wscript.echo "-------------------------- Create List --------------------------"
Set UpdatesToDownload = CreateObject("Microsoft.Update.UpdateColl")
'#############################
' Search for Updates
'#############################
wscript.echo "-------------------------- Search for Updates ---------------Start: " & Now()
Set SearchResult = UpdateSearcher.Search("Isinstalled=0")
wscript.echo "---------------------------Search for Updates Complete-------End: " & Now()
'#############################
' Quit if No Updates Found
'#############################
If SearchResult.Updates.Count = 0 Then
WScript.Quit
WScript.Echo "No updates found."
End If
Dim strSpacer
For I = 0 To SearchResult.Updates.Count-1
Set Update = SearchResult.Updates.Item(I)
' formatting helper
If I < 10 Then
strSpacer = " "
Else
strSpacer = ""
End If
' write to console
WScript.Echo "[" & strSpacer & I & "] Found Update, Marking For Download: " & update.Title
UpdatesToDownload.Add(Update)
Next
'#############################
' Download Updates
'#############################
wscript.echo "-------------------------- Downloading Updates ----------------------Start: " & Now()
Set Downloader = UpdateSession.CreateUpdateDownloader()
Downloader.Updates = UpdatesToDownload
On Error Resume Next
Downloader.Download()
If Err.number <> 0 Then
Wscript.Echo "An error occurred in Downloader.Download() of updates"
Wscript.Echo "Number: " & err.number
Wscript.Echo "Description: " & err.Description
Wscript.Quit (Err.number)
End If
On Error Goto 0
wscript.echo "-------------------------- Downloading Complete ----------------------End: " & Now()
'#############################
' Create List of Updates to Install
'#############################
wscript.echo "-------------------------- Create List of Updates to Install --------------------------"
Set UpdatesToInstall = CreateObject("Microsoft.Update.UpdateColl")
For I = 0 To SearchResult.Updates.Count-1
set Update = SearchResult.Updates.Item(I)
If Update.IsDownloaded = true Then
UpdatesToInstall.Add(Update)
WScript.Echo "Marking update for install: [" & Update.Title & "]"
End If
Next
'#############################
' Install Updates
'#############################
wscript.echo "-------------------------- Installing Updates --------------------------"
Set Installer = UpdateSession.CreateUpdateInstaller()
Installer.Updates = UpdatesToInstall
Set InstallationResult = Installer.Install()
wscript.Echo "Installation Result: " & InstallationResult.ResultCode
wscript.Echo "Reboot Required: " & InstallationResult.RebootRequired
wscript.Echo "Listing of updates installed and individual installation results:"
For I = 0 to UpdatesToInstall.Count - 1
WScript.Echo I + 1 & "> " & _
UpdatesToInstall.Item(I).Title & ": " & TranslateMuCode(InstallationResult.GetUpdateResult(i).ResultCode)
Next
'#############################
' Quit
'#############################
WScript.Quit
'================================================================================
' Translate Microsoft Update Installation Results
'================================================================================
Function TranslateMuCode(theCode)
TranslateMuCode = "[" & theCode & "] "
if (theCode = 0) Then TranslateMuCode = TranslateMuCode & "Not Started"
if (theCode = 1) Then TranslateMuCode = TranslateMuCode & "In Progress"
if (theCode = 2) Then TranslateMuCode = TranslateMuCode & "Succeeded"
if (theCode = 3) Then TranslateMuCode = TranslateMuCode & "Succeeded with Errors"
if (theCode = 4) Then TranslateMuCode = TranslateMuCode & "Failed"
if (theCode = 5) Then TranslateMuCode = TranslateMuCode & "Aborted"
End Function
'================================================================================
' Register Microsoft Update (if never registered)
'================================================================================
Function RegisterMu
Dim fso
Dim file
Dim WshShell
Dim updateService
Dim updateServiceManager
found = false
Set fso = CreateObject("Scripting.FileSystemObject")
Set WshShell = WScript.CreateObject ("WScript.Shell")
Set updateServiceManager = CreateObject("Microsoft.Update.ServiceManager")
Set updateService = updateServiceManager.Services
If err <> 0 Then
WScript.Echo "CreateObject(Microsoft.Update.ServiceManager) failed with error 0x" & Hex(err.Number) & err.Description
WScript.Quit(2)
End If
For I=0 to updateService.Count - 1
Set item = updateService.Item(i)
If item.ServiceID = "7971f918-a847-4430-9279-4a52d1efe18d" Then
found = true
End IF
Next
IF found = false Then
updateServiceManager.AddService2 "7971f918-a847-4430-9279-4a52d1efe18d", 2, ""
If err <> 0 Then
WScript.Echo "updateServiceManager.AddService() failed with error 0x" & Hex(err.Number) & err.Description
Else
WScript.Echo "MU is registered with WU Agent"
End IF
End IF
END Function
Using Microsoft Update instead of Windows Update
In the above script, the updateSearcher.ServerSelection option can be modified to instead use Microsoft update. This depends on your scenario and your goal. For our environment, we didn’t need to focus on Microsoft update rather just Windows update. For example, if i set server selection to 1 then I would get a ton of Microsoft updates including items like Windows Live Essentials (ouch!) and other bogus updates. I corrected this behavior changing the value for ServerSelection to 2 which uses Windows Update and only gets Critical Updates.
Summary
In this post, I shared how I modified our unattend.xml file that is used with our provisioning of new virtual machines to ensure that the servers not only were ready to serve clients but also they were correctly patched. This is very important for the security of the network and your sanity. In order to do this, I used a unknown setting in my unattend.xml file that is called PolicyProxySettingsPerUser and set this value to 0 that forced the proxy settings per machine.
Lastly, I shared a sample script managed and maintained by my team for patching clients using MU and I altered it to use WU instead.
Enjoy!
-Chris