6 Replies Latest reply on Jun 29, 2020 11:26 PM by vin01

    windows templates patching with invoke-vmscript

    vin01 Hot Shot

      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. Re: windows templates patching with invoke-vmscript
          LucD Guru
          User ModeratorsvExpertCommunity Warriors

          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: http://lucd.info | Twitter: @LucD22 | PowerCLI Reference co-author: http://tinyurl.com/hkn4glz
          • 2. Re: windows templates patching with invoke-vmscript
            vin01 Hot Shot

            Hi LucD,

            Can you show me an example please.

            Regards
            Vineeth.K
            • 3. Re: windows templates patching with invoke-vmscript
              LucD Guru
              vExpertCommunity WarriorsUser Moderators

              Of which one?

              There are 2 questions and 2 answers

              Blog: http://lucd.info | Twitter: @LucD22 | PowerCLI Reference co-author: http://tinyurl.com/hkn4glz
              • 4. Re: windows templates patching with invoke-vmscript
                vin01 Hot Shot

                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

                Regards
                Vineeth.K
                • 5. Re: windows templates patching with invoke-vmscript
                  LucD Guru
                  Community WarriorsUser ModeratorsvExpert

                  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: http://lucd.info | Twitter: @LucD22 | PowerCLI Reference co-author: http://tinyurl.com/hkn4glz
                  • 6. Re: windows templates patching with invoke-vmscript
                    vin01 Hot Shot

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

                    Regards
                    Vineeth.K