VMware Cloud Community
vin01
Expert
Expert
Jump to solution

windows templates patching with invoke-vmscript

Hello Community

I need few correction on below script. I have written this script to do windows updates on each template.

Process I have defined in the script is

1. I have created a dhcp vlan and mapped to all the hosts in vc.

2.Script will connect to a datacenter in vc and get all the windows templates with Template name,portgroup and vcname in $alltemplates var

3.Then its taken in a foreach loop $singletemplate and set each template to vm and assign the dhcp port group and poweron. (This will auto assign dhcp ip and connects to wus server which is already configured in templates.)

4. VM will wait for GuestOperationsReady to ready and checks if tools are outdated then it will update.

5.if tools are updated then it will wait for GuestOperationsReady eq true then do invoke in RunAsync mode to pass code in $script

6. All invoke tasks are kept $tasks+ array

7.when ever task state completed it will do Restart-VMGuest to apply patches.

8.Again it will wait till GuestOperationsReady -eq true.

9.then do Stop-VMGuest and writes Invoke-VMScript output which is in $task variable to $report

10.Then it will send email with status report

11.Again it will convert portgroup status of template vms to old one $alltemplates and set each vm back to template.

-------------

Tested the script on 30 templates and working as expected.

I required help in below points

1.Lot of while loops in the script so if any vm struck in a loop then the script will not completes for ever. So by looking the script in any line can we use alternate method or reduce while loops

2.For logfile i am using Start-Transcript but the problem is in the logfile  is it will write what ever displayed in powershell window so if we get error for a vm whose character doesn't fit in powershell window it will write as below. Any alternate to get the full windows output.

Sample log data from Start-Transcript logfile.

At C:\My Data\Working\windowsupdateprototypescript_updated.ps1:75 char:18

+ Update-Tools -VM $task.Result.VM.Name -NoReboot

+                  ~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : InvalidData: (:) [Update-Tools], ParameterBindingValidationException

    + FullyQualifiedErrorId : ParameterArgumentValidationError,VMware.VimAutomation.ViCore.Cmdlets.Commands.UpdateVmTo

   ols

W_Win10_64_MSDNNe...  PoweredOff 1        0.250

(No Fullname for vm (....) are represented so it hard to find if vmname)

Start-Transcript -Path "C:\My Data\Notepad++\logfile.txt" -NoClobber -Force -Confirm:$false

$script = @'

$finalresult=@()

$ErrorActionPreference = "SilentlyContinue"

If ($Error) {

$Error.Clear()

}

$Today = Get-Date

$UpdateCollection = New-Object -ComObject Microsoft.Update.UpdateColl

$Searcher = New-Object -ComObject Microsoft.Update.Searcher

$Session = New-Object -ComObject Microsoft.Update.Session

$Result = $Searcher.Search("IsInstalled=0 and Type='Software' and IsHidden=0")

If ($Result.Updates.Count -EQ 0) {

$finalresult+= "There are no applicable updates for this computer."

}

Else {

For ($Counter = 0; $Counter -LT $Result.Updates.Count; $Counter++) {

$DisplayCount = $Counter + 1

    $Update = $Result.Updates.Item($Counter)

$UpdateTitle = $Update.Title

}

$Counter = 0

$DisplayCount = 0

$Downloader = $Session.CreateUpdateDownloader()

$UpdatesList = $Result.Updates

For ($Counter = 0; $Counter -LT $Result.Updates.Count; $Counter++) {

$UpdateCollection.Add($UpdatesList.Item($Counter)) | Out-Null

$ShowThis = $UpdatesList.Item($Counter).Title

$DisplayCount = $Counter + 1

$Downloader.Updates = $UpdateCollection

$Track = $Downloader.Download()

If (($Track.HResult -EQ 0) -AND ($Track.ResultCode -EQ 2)) {

$finalresult+="Download Status:SUCCESS"

}

Else {

$finalresult+="Download Status: FAILED With Error -- $Error()"

$Error.Clear()

}

}

$Counter = 0

$DisplayCount = 0

$Installer = New-Object -ComObject Microsoft.Update.Installer

For ($Counter = 0; $Counter -LT $UpdateCollection.Count; $Counter++) {

$Track = $Null

$DisplayCount = $Counter + 1

$WriteThis = $UpdateCollection.Item($Counter).Title

$Installer.Updates = $UpdateCollection

Try {

$Track = $Installer.Install()

$finalresult+="Update Installation Status:SUCCESS"

}

Catch {

[System.Exception]

$finalresult+= "Update Installation Status: FAILED With Error -- $Error()"

$Error.Clear()

}

}

}

$finalresult -join ','

'@

$tasks = @()

$alltemplates=Get-Template "Template01", "Template02", "Template03", "Template04", "Template05" |Select-Object @{N='Name';E={$_.Name}},@{N="Portgroup";E={((Get-View -Id $_.ExtensionData.Network).name)}},@{N="vCenter";E={([System.Net.Dns]::GetHostEntry($_.Uid.Split(“:”)[0].Split(“@”)[1])).HostName}}

foreach($singletemplate in $alltemplates){

Set-Template -Template $singletemplate.Name -ToVM -Confirm:$false

$templatevm= Get-VM $singletemplate.Name

$dhcpportgroup= Get-View -Id $templatevm.VMHost.ExtensionData.Network |?{$_.config.DefaultPortConfig.Vlan.VlanId -eq '2067'}

Get-NetworkAdapter -VM $templatevm.Name |Set-NetworkAdapter -NetworkName $dhcpportgroup.Name -Confirm:$false

Start-VM -VM $templatevm.Name -Confirm:$false

while($templatevm.ExtensionData.Guest.GuestOperationsReady -ne "True"){

Start-Sleep -Seconds 3

$templatevm.ExtensionData.UpdateViewData("Guest.GuestOperationsReady")

}

if($templatevm.ExtensionData.guest.toolsversionstatus -eq 'guestToolsNeedUpgrade'){

Update-Tools -VM $task.Result.VM.Name -NoReboot

while($templatevm.ExtensionData.Guest.GuestOperationsReady -ne "True"){

Start-Sleep -Seconds 3

$templatevm.ExtensionData.UpdateViewData("Guest.GuestOperationsReady")

}

else {

while($templatevm.ExtensionData.Guest.GuestOperationsReady -ne "True"){

Start-Sleep -Seconds 3

$templatevm.ExtensionData.UpdateViewData("Guest.GuestOperationsReady")

}

$sInvoke = @{

VM            = $templatevm.Name

GuestUser     = 'administrator'

GuestPassword = ''

ScriptText    = $script

ScriptType    = 'Powershell'

ErrorAction   = 'Stop'

}

$tasks+=Invoke-VMScript @sInvoke -RunAsync -Confirm:$false

}}

while ($tasks.State -contains 'running') {

  Start-Sleep 1

}

$report=@()

$CurrentDate = Get-Date -Format 'MM-dd-yyyy_hh-mm-ss'

$csvFiles = @()

foreach ($task in $tasks) {

Restart-VMGuest -VM $task.Result.VM.Name -Confirm:$false

while($task.Result.VM.ExtensionData.Guest.GuestOperationsReady -eq "True"){

Start-Sleep -Seconds 3

$task.Result.VM.ExtensionData.UpdateViewData("Guest.GuestOperationsReady")

}

Get-VM -Name $task.Result.VM.Name |Out-Null

while($task.Result.VM.ExtensionData.Guest.GuestOperationsReady -ne "True"){

Start-Sleep -Seconds 3

$task.Result.VM.ExtensionData.UpdateViewData("Guest.GuestOperationsReady")

}

Stop-VMGuest -VM $task.Result.VM.Name -Confirm:$false

while($task.Result.VM.ExtensionData.Guest.GuestOperationsReady -eq "True"){

Start-Sleep -Seconds 3

$task.Result.VM.ExtensionData.UpdateViewData("Guest.GuestOperationsReady")

}

$report+=$task.Result.ScriptOutput.Split("`n") | Where-Object { $_ -ne '' } | Select-Object @{N='VM';E={$task.Result.VM.Name}},

@{N ='Status'; E={$_.Trim("`r`n")}}

}

$filename = "C:\AllTemplatepatchstatusreport.csv as on dated $($CurrentDate).csv"

$csvFiles += $filename

$report |Export-Csv -Path $filename -NoTypeInformation -NoClobber -UseCulture

Send-MailMessage -From "" -To "" -Subject "Script POC Template Patching" ` -Body "The attachment contains templates patching status" ` -Attachments $csvFiles -SmtpServer ''

$alltemplates |ForEach-Object -Process {

Get-NetworkAdapter -VM $_.name |Set-NetworkAdapter -NetworkName $_.Portgroup -Confirm:$false

Set-VM -VM $_.Name -ToTemplate -Confirm:$false

}

Stop-Transcript

Regards Vineeth.K
1 Solution

Accepted Solutions
LucD
Leadership
Leadership
Jump to solution

A simple example of incorporating a timeout in a wait-loop could be something like this

$timeoutValue = 30

$startTime = Get-Date


while($templatevm.ExtensionData.Guest.GuestOperationsReady -ne "True" -and (New-TimeSpan -Start $startTime -End (Get-Date)).TotalSeconds -lt $timeoutValue){ 

    Start-Sleep -Seconds 3 

    $templatevm.ExtensionData.UpdateViewData("Guest.GuestOperationsReady") 

}

if((New-TimeSpan -Start $startTime -End (Get-Date)).TotalSeconds -ge $timeoutValue) {

    Write-Error "Timeout on wait loop for $($templateVM.Name)"

}

else{

    # Continue with the rest of the script/function

 

}

I would use try-catch constructs to capture errors and provide meaningful output

try{

    $nic = Get-NetworkAdapter -VM $templatevm.Name -ErrorAction stop

}

catch{

    Write-Output "Error in Get-NetworkAdapter for VM $($templateVM.Name)"

}


try{

    Set-NetworkAdapter -NetworkAdapter $nic -NetworkName $dhcpportgroup.Name -Confirm:$false -ErrorAction Stop

}

catch{

    Write-Output "Error in Set-NetworkAdapter for VM $($templateVM.Name) on NIC $($nic.Name)"

}


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

View solution in original post

0 Kudos
6 Replies
LucD
Leadership
Leadership
Jump to solution

Looking at your script I don't think you can reduce the number of loops in there.

But you could consider implementing a timeout on each loop.

Then when the timeout occurs, the script fails for that template and continues with the next template.

A Transcript captures stdout and stderr output.

If you want to see additional info in there, you will have to send it in your script to one of those streams.

Use for example Write-Output to send something to stdout.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

vin01
Expert
Expert
Jump to solution

Hi LucD,

Can you show me an example please.

Regards Vineeth.K
0 Kudos
LucD
Leadership
Leadership
Jump to solution

Of which one?

There are 2 questions and 2 answers


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
vin01
Expert
Expert
Jump to solution

But you could consider implementing a timeout on each loop.

Then when the timeout occurs, the script fails for that template and continues with the next template.

How to implement timeout in a while loop? In below example

  1. while($templatevm.ExtensionData.Guest.GuestOperationsReady -ne "True"){ 
  2. Start-Sleep -Seconds 3 
  3. $templatevm.ExtensionData.UpdateViewData("Guest.GuestOperationsReady") 
  4. }

A Transcript captures stdout and stderr output.

If you want to see additional info in there, you will have to send it in your script to one of those streams.

Use for example Write-Output to send something to stdout.

For example if it failed in below line.Then how do I use Write-Output to send to stdout.

Get-NetworkAdapter -VM $templatevm.Name |Set-NetworkAdapter -NetworkName $dhcpportgroup.Name -Confirm:$false

Regards Vineeth.K
0 Kudos
LucD
Leadership
Leadership
Jump to solution

A simple example of incorporating a timeout in a wait-loop could be something like this

$timeoutValue = 30

$startTime = Get-Date


while($templatevm.ExtensionData.Guest.GuestOperationsReady -ne "True" -and (New-TimeSpan -Start $startTime -End (Get-Date)).TotalSeconds -lt $timeoutValue){ 

    Start-Sleep -Seconds 3 

    $templatevm.ExtensionData.UpdateViewData("Guest.GuestOperationsReady") 

}

if((New-TimeSpan -Start $startTime -End (Get-Date)).TotalSeconds -ge $timeoutValue) {

    Write-Error "Timeout on wait loop for $($templateVM.Name)"

}

else{

    # Continue with the rest of the script/function

 

}

I would use try-catch constructs to capture errors and provide meaningful output

try{

    $nic = Get-NetworkAdapter -VM $templatevm.Name -ErrorAction stop

}

catch{

    Write-Output "Error in Get-NetworkAdapter for VM $($templateVM.Name)"

}


try{

    Set-NetworkAdapter -NetworkAdapter $nic -NetworkName $dhcpportgroup.Name -Confirm:$false -ErrorAction Stop

}

catch{

    Write-Output "Error in Set-NetworkAdapter for VM $($templateVM.Name) on NIC $($nic.Name)"

}


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
vin01
Expert
Expert
Jump to solution

Thanks. I will using this examples and modify my script accordingly.

Regards Vineeth.K
0 Kudos