So, last friday I was about to patch templates in different systems, and this time it needed some special care for Spectre/Meltdown patches and workarounds. Figured I may as well finally start scripting this procedure as it needed to be repeated on several templates. Most of this is done with Invoke-VMScript and the use of different powershell modules, and utilize some pre-standardisation of our templates (same DVD drive letter, same user/password, script in c:\install etc.).
In broad terms, the script (in it's current revision) does the following:
- Checks for powered on VMs in folders named "Templates" that has guestID matching "windows"
- Adds registry value so January security patches gets installed (No AV in base image, except Windows Defender)
- Adds NuGet package provider
- Installs PSwindowsupdate PS Module
- Downloads all windows updates (filtered if needed)
- Install all Windows Updates and reboots the machine
- Runs DISM with some parameters to ensure a small image
- Maps the ISO for VMware Tools and runs the installation silently, then dismounts the CD
- Shutting down the VM when complete
.. everything is logged to c:\temp\<name of VM>.txt
I have been trying to figure out why some VMs work, and some don't. Often the failure is due to problems with powershell, and I'm guessing that is because the machine is not logged on (or have some intrusive patch installed).
The errors I get are these:
Join-Path : Cannot bind argument to parameter 'Path' because it is null. At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:47 char:77 + ... osoft.PowerShell.Management\Join-Path -Path $env:LOCALAPPDATA -ChildP ... + ~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidData: (:) [Join-Path], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCommand The variable '$script:PSGetAppLocalPath' cannot be retrieved because it has not been set. At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:48 char:86 + ... werShell.Management\Join-Path -Path $script:PSGetAppLocalPath -ChildP ... + ~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (script:PSGetAppLocalPath:String) [], RuntimeException + FullyQualifiedErrorId : VariableIsUndefined The variable '$script:PSGetAppLocalPath' cannot be retrieved because it has not been set. At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:51 char:81 + ... werShell.Management\Join-Path -Path $script:PSGetAppLocalPath -ChildP ... + ~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (script:PSGetAppLocalPath:String) [], RuntimeException + FullyQualifiedErrorId : VariableIsUndefined WARNING: The property 'Values' cannot be found on this object. Verify that the property exists. WARNING: The property 'Keys' cannot be found on this object. Verify that the property exists. WARNING: The variable '$script:PSGetModuleSourcesFilePath' cannot be retrieved because it has not been set. PackageManagement\Install-Package : No match was found for the specified search criteria and module name 'PSWindowsUpdate'. Try Get-PSRepository to see all available registered module repositories. At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:1772 char:21 + ... $null = PackageManagement\Install-Package @PSBoundParameters + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (Microsoft.Power....InstallPackage:InstallPackage) [Install-Package], Exception + FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage
So far, it seems to be working if the console is logged on, or if the OS is 2008 R2.
Here's the script (it has no error handling or much intelligense yet, but it does the job..)
$guestuser = "administrator" $guestpass = "password" $skipupdates = "KB4033369,KB4033342" $toolspath = "[Isofiles] VMware/VMware Tools/10.2.0/vmtools/windows.iso" if( $Host -and $Host.UI -and $Host.UI.RawUI ) { $rawUI = $Host.UI.RawUI $oldSize = $rawUI.BufferSize $typeName = $oldSize.GetType( ).FullName $newSize = New-Object $typeName (120, $oldSize.Height) $rawUI.BufferSize = $newSize } Clear-Host foreach ($vm in (Get-Folder Templates | Get-VM | Sort-Object Name | Where-Object {$_.PowerState -eq "PoweredOn" -and $_.guestid -match "windows" })) { Write-Host Processing: $vm.name $file = "c:\temp\$($vm.name).txt" $now = get-date add-content $file "Actions started on $now" Write-host " - Adding registry values for Spectre/Meltdown patches" add-content $file "Adding registry values for Spectre/Meltdown patches" $output = Invoke-VMScript -vm $vm -ScriptText "reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat /v cadca5fe-87d3-4b96-b7fb-a231484277cc /t REG_DWORD /d 0 /f" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType bat add-content $file $output Write-host " - Installing NuGet package provider" add-content $file "Installing NuGet package provider" $output = Invoke-VMScript -vm $vm -ScriptText "Install-PackageProvider nuget -force" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType PowerShell add-content $file $output Write-host " - Installing PSWindowsUpdate module" add-content $file "Installing PSWindowsUpdate module" $output = Invoke-VMScript -vm $vm -ScriptText "Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; Import-Module PowerShellGet; Start-Sleep -Seconds 10; Install-Module -Name PSWindowsUpdate -force -scope AllUsers" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType PowerShell add-content $file $output if ($skipupdates -eq "") { Write-host " - Downloading all Windows Updates" add-content $file "Downloading all Windows Updates" $output = Invoke-VMScript -vm $vm -ScriptText "Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; Import-Module PSWindowsUpdate; Start-Sleep -seconds 10; Get-WindowsUpdate -Download -Verbose -AcceptAll" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType PowerShell add-content $file $output Write-host " - Installing all Windows Updates" add-content $file "Installing all Windows Updates" $output = Invoke-VMScript -vm $vm -ScriptText "Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; Import-Module PSWindowsUpdate; Start-Sleep -seconds 10; Get-WindowsUpdate -MicrosoftUpdate -AcceptAll -Install -AutoReboot -Verbose" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType PowerShell add-content $file $output } else { Write-host " - Downloading all Windows Updates, except $skipupdates" add-content $file "Downloading all Windows Updates, except $skipupdates" $output = Invoke-VMScript -vm $vm -ScriptText "Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; Import-Module PSWindowsUpdate; Start-Sleep -seconds 10; Get-WindowsUpdate -Download -Verbose -AcceptAll -NotKBArticleID $skipupdates" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType PowerShell add-content $file $output Write-host " - Installing all Windows Updates, except $skipupdates" add-content $file "Installing all Windows Updates, except $skipupdates" $output = Invoke-VMScript -vm $vm -ScriptText "Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass; Import-Module PSWindowsUpdate; Start-Sleep -seconds 10; Get-WindowsUpdate -MicrosoftUpdate -AcceptAll -Install -AutoReboot -NotKBArticleID $skipupdates -Verbose" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType PowerShell add-content $file $output } Write-Host " - Waiting 180 seconds for reboot to complete" Start-Sleep -Seconds 180 Write-host " - Running cleanup script" add-content $file "Running cleanup script" $output = Invoke-VMScript -ToolsWaitSecs 360 -vm $vm -ScriptText "c:\install\cleanup.cmd" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType bat add-content $file $output Write-Host " - Upgrading VMware Tools" add-content $file "Upgrading VMware Tools" Get-CDDrive -vm $vm | Set-CDDrive -IsoPath $toolspath -Connected:$true -Confirm:$false | Out-Null Write-Host " - Waiting for ISO to be mounted" Start-Sleep -Seconds 15 $output = Invoke-VMScript -ToolsWaitSecs 360 -vm $vm -ScriptText "Z:\setup64.exe /s /v ""/qn REBOOT=Force ADDLOCAL=ALL REMOVE=Hgfs""" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType bat add-content $file $output #sleep added due to vmware tools upgrade trashing the connection Write-Host " - Waiting five minutes for VMware Tools installation to complete" Start-Sleep -Seconds 300 Write-Host " - Dismounting ISO file" Get-CDDrive -vm $vm | Set-CDDrive -NoMedia -Connected:$false -Confirm:$false | Out-Null Write-host " - Shutting down the VM" add-content $file "Shutting down the VM" $output = Invoke-VMScript -vm $vm -ScriptText "shutdown /s /f /t 10 /d p:0:0" -GuestUser $guestuser -GuestPassword $guestpass -ScriptType Bat add-content $file $output }
Have anyone else encountered problems with Invoke-VMScript and powershell?
(Note, I could prepare a BAT/CMD that does all the above, but this can be used for other purposes as well when automating customer patching later on)
Message was edited by: Andreas Cederlund Forgot to edit out password :-)