Adding a PowerShell Windows Update Script to my VMware Customizations

In my environments, we typically convert the templates to Virtual Machines once a quarter so we can install Windows Updates.  This means, there could be a number of patches to install if you’re 2+ months out from the last time they were updated.  So I decided to use a PowerShell script that my coworker wrote to do Windows Updates and run it during the Run Once phase of the vCenter Customizations.

Typically, I have my customizations set to automatically login once.  I did this so I could deploy a VM and walk away.  When I came back, I could tell it was done when the VM was up and logged into Windows.  So I wanted a way to be able to tell things were done and the VM was ready after deploying the Windows Updates.  I decided that a TXT file being displayed on screen would be a great way to know things were done!

First, I modified the customization by changing the number of times to logon automatically to 2 on the ADMINISTRATOR PASSWORD tab and putting C:\SW\install_update.bat on the RUN ONCE tab.  I should also note that my templates are all configured to start on our build VLAN which runs DHCP.  This allows us to build and configure the VM and then change it to the destination VLAN when ready.

image

Next, I had to convert my template to a VM and boot it up, so I could copy the required files to a location on the C drive.  I created a folder called c:\SW and copied the files there.  There are 4 files I needed to copy.

1. install_update.bat – This is the file that the customization executes.
2. install_updates.ps1 – This is the PowerShell script that runs Windows Update.
3.  runonce.reg – This adds the txt file to launch at the end of the 2nd reboot.
4.  image.txt – This is the TXT file that displays when the VM has rebooted the 2nd time and is finished.

This is the process that happens when you deploy a VM after I made the changes.

Once you power up the VM, it boots and prepares to run sysprep.
image

Once it runs sysprep, the system reboots, sysprep will run and will reboot again.
image  image

As configured, Windows will Auto Login as the local administrator and the install_updates.bat file will run.  You’ll see the regedit command run first to add the display of the text file to the RUNONCE key in the registry.  Then the Windows update PowerShell Script will execute.  It will reboot upon completion, even if there are no pending updates.

image image

After the VM reboots, it will Auto Login for a 2nd time. After logging in, the TXT file staying the VM is finished deploying will be displayed.
image image

Once you see the TXT file, you can close Notepad and continue to perform the post-build configuration (like adding users, installing AV, etc.)

Here are the contents of each of the files:

INSTALL_UPDATE.BAT:


REM Run Once
regedit /s c:\SW\runonce.reg

REM Run Windows Update
powershell.exe -ExecutionPolicy Bypass -File "c:\sw\Install_updates.ps1" -patch 1

INSTALL_UPDATES.PS1:


$patch = 1
function write-log
{    param([string]$data)
$date = get-date -format "ddMMMyyyy @ hh:mm:ss tt"
write-host "$date $data"
Out-File -InputObject "$date $data" -FilePath $LoggingFile -Append
}
function Get-StatusValue($value)
{ switch -exact ($value)
{ 0   {"NotStarted"}
1   {"InProgress"}
2   {"Succeeded"}
3   {"SucceededWithErrors"}
4   {"Failed"}
5   {"Aborted"}}}
function patchserver
{    Param([string]$Option)
$needsReboot = $false
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
write-log " - Searching for Updates"
switch ($Option)
{ 0 { $SearchResult = $UpdateSearcher.Search("IsHidden=0 and IsInstalled=0")}
1 { $SearchResult = $UpdateSearcher.Search("IsAssigned=1 and IsHidden=0 and IsInstalled=0")}
default { $SearchResult = $UpdateSearcher.Search("IsHidden=0 and IsInstalled=0")}}
write-log " - Found [$($SearchResult.Updates.count)] Updates to Download and install"
$i=0
foreach($Update in $SearchResult.Updates)
{
$i++
$UpdatesCollection = New-Object -ComObject Microsoft.Update.UpdateColl
if ( $Update.EulaAccepted -eq 0 ) { $Update.AcceptEula() }
$UpdatesCollection.Add($Update) | out-null
write-log " + Downloading [$i of $($SearchResult.Updates.count)] $($Update.Title)"
$UpdatesDownloader = $UpdateSession.CreateUpdateDownloader()
$UpdatesDownloader.Updates = $UpdatesCollection
$DownloadResult = $UpdatesDownloader.Download()
$Message = "   - Download {0}" -f (Get-StatusValue $DownloadResult.ResultCode)
write-log $message
write-log "   - Installing Update"
$UpdatesInstaller = $UpdateSession.CreateUpdateInstaller()
$UpdatesInstaller.Updates = $UpdatesCollection
$InstallResult = $UpdatesInstaller.Install()
$Message = "   - Install {0}" -f (Get-StatusValue $DownloadResult.ResultCode)
write-log $message
write-log
}}
New-Item -Path C:\Logs -ItemType "directory" -Force > $null
$LoggingFile = "C:\Logs\Updates_" + (Get-Date -f "MMddyyyy_hh-mm-ss_tt") + ".txt"
del $LoggingFile -Confirm:$false -ErrorAction SilentlyContinue
$CurrentTime = Get-Date
write-log "STARTING: $CurrentTime"
switch ($patch)
{
0 {
write-log "########################"
}
1 {
write-log "################################"
write-log "###  INSTALLING ALL UPDATES  ###"
write-log "################################"
patchserver 0
Restart-Computer -Force
}
2 {
write-log "############################"
write-log "###  INSTALLING UPDATES  ###"
write-log "############################"

patchserver 1
Restart-Computer -Force
}
default {
write-log "Must specify an valid option -- exiting"
return -1
}}

RUNONCE.REG:


Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce]
"Image"="notepad c:\\SW\\image.txt"

IMAGE.TXT:


IMAGE DEPLOYMENT COMPLETE

 

UPDATE 3/22/16:

Another way to display that the image is done is to display a popup box…
template_complete

Delete image.tx, modify the runonce.reg (below), and add image.ps1 (also below)…

RUNONCE.REG:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce]
"Image"="powershell.exe -ExecutioinPolicy Bypass -File \"c:\\sw\\image.ps1\""

IMAGE.PS1:

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.MessageBox]::Show("TEMPLATE DEPLOYMENT COMPLETE.")

Ben Liebowitz, VCP, vExpert
NJ VMUG Leader

Share This:

Leave a Reply

Your email address will not be published. Required fields are marked *

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