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
A simple example of incorporating a timeout in a wait-loop could be something like this
$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
$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
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
Hi LucD,
Can you show me an example please.
Of which one?
There are 2 questions and 2 answers
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
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
- while($templatevm.ExtensionData.Guest.GuestOperationsReady -ne "True"){
- Start-Sleep -Seconds 3
- $templatevm.ExtensionData.UpdateViewData("Guest.GuestOperationsReady")
- }
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
A simple example of incorporating a timeout in a wait-loop could be something like this
$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
$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
Thanks. I will using this examples and modify my script accordingly.