Automation

 View Only
  • 1.  all windows templates patching in a datacenter

    Posted Aug 31, 2020 09:38 AM

    Hi LucD,


    I have finally completed the below script to do windows updates on all templates in a datacenter using a DHCP vLan. Thanks for your support.

    I need one fix in the script output. After updating windows I am writing output in a csv but that is not writing in correcting format. Every VMs output is written twice and leaving blank rows between every vms output. Sample output

    Script:

    $location=Get-Location

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

    $logfilelocation= "$location\$($CurrentDate)logfile.txt"

    $alltemplatesexportpath="$location\$($CurrentDate)-templateswindows.csv"

    $Outputfile = "$location\AllTemplatepatchstatusreport.csv as on dated $($CurrentDate).csv"

    $csvFiles = @()

    Write-Host "Enter Administrator Credentials for logging into templates" -ForegroundColor Yellow

    $cred=Get-Credential

    Start-Transcript -Path $logfilelocation -NoClobber -Force -Confirm:$false

    $script = @'

    $report = @()

    $ErrorActionPreference = "SilentlyContinue"

    If ($Error) {

        $Error.Clear()

    }

    $updatesession=New-Object -ComObject Microsoft.update.session

    $Criteria="IsInstalled=0 and Type=Software and IsHidden=0"

    $searchresult=$updateSession.CreateupdateSearcher().Search("IsInstalled=0 and Type='Software' and IsHidden=0").Updates

    $report = if(-not $searchresult.Count){

    New-Object -TypeName PSObject -property @{

    KB = ''

    InstallStatus = 'There are no applicable updates for this computer.'

    }

    }

    else{

    $pendingdownloads=$searchresult | Where-Object {$_.IsDownloaded -eq $false}

    if(($pendingdownloads |Select-Object IsDownloaded).count -ne '0'){

    $downloadercall=$updatesession.CreateUpdateDownloader()

    $downloadercall.Updates=New-Object -ComObject Microsoft.update.updatecoll

    foreach($pendingdownload in $pendingdownloads){

    [void]$downloadercall.Updates.add($pendingdownload)

    $downloadercall.Download() |Out-Null

    [void]$downloadercall.Updates.RemoveAt(0)

    }

    }

    $updatesession=New-Object -ComObject Microsoft.update.session

    $Criteria="IsInstalled=0 and Type=Software and IsHidden=0"

    $searchresult=$updateSession.CreateupdateSearcher().Search("IsInstalled=0 and Type='Software' and IsHidden=0").Updates

    $downloadedupdates = $searchresult  | Where-Object {$_.IsDownloaded -eq $true}

    $updatercall=$updatesession.CreateUpdateInstaller()

    $updatercall.Updates= New-Object -ComObject Microsoft.update.updatecoll

    foreach($singleupdate in $downloadedupdates){

    [void]$updatercall.Updates.add($singleupdate)

    $installstatus=$updatercall.install()

    [void]$updatercall.Updates.RemoveAt(0)

    New-Object -TypeName PSObject -property @{

    KB = &{$kbnumb=$singleupdate.Title; $kbnumb.Substring($kbnumb.IndexOf("KB")).Trimend(")")}

    InstallStatus = &{

    if($installstatus.ResultCode -eq '2'){

        'KB Installed'

    }

    elseif($installstatus.ResultCode -eq '3'){

        'KB Install Succeeded with errors'

    }

    elseif($installstatus.ResultCode -eq '4'){

        'Kb Failed to install'

    }

    elseif($installstatus.ResultCode -eq '5'){

        'KBAborted'

    }

    elseif (-not $installstatus.ResultCode){

         'KB Failed to Download'

    }

    }

    }

    }

    }

    $report | ConvertTo-Csv -NoTypeInformation

    '@

    $tasks = @()

    $alltemplates=Get-Datacenter  | Get-Template   |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}}

    $alltemplates|Export-Csv -Path $alltemplatesexportpath -NoTypeInformation -NoClobber -UseCulture

    foreach($singletemplate in $alltemplates){

    Write-Host "Marking Template Name $($singletemplate.Name) to VM"

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

    $templatevm= Get-VM $singletemplate.Name

    if(-not $templatevm.Name){

    Write-Host "Setting Template $($singletemplate.Name) to VM Failed Moving to Next Template"

    }

    else{

    Write-Host "Collecting DHCP PortGroup Name for Vlanid 2067 from VMhost $($templatevm.VMHost)"

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

    Write-Host "Collecting Network Adapter for $($templatevm.Name)"

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

    Write-Host "Adding Network Adapter to VM $($templatevm.Name) if not Present"

    if($nic -eq $null){

    New-NetworkAdapter -VM $templatevm.Name -Portgroup $dhcpportgroup -Type Vmxnet3 -StartConnected -Confirm:$false

    }

    else{

    Write-Host "Changing Portgroup to $($dhcpportgroup.Name) to VM $($templatevm.Name)"

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

    }

    Write-Host "Starting VM $($templatevm.Name) and wait in loop till GuestOperationsReady is true "

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

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

    Start-Sleep -Seconds 3

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

    }

    Write-Host "Updating vmtools on $($templatevm.Name) if they are Outdated"

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

    $timeoutSeconds = 900

    $start = (Get-Date)

    $task = Get-View -Id (Update-Tools -VM $templatevm.Name -NoReboot  -RunAsync).Id

    while((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds -lt $timeoutSeconds -and

    ($task.Info.State -eq [VMware.Vim.TaskInfoState]::running -or

    $task.Info.State -eq [VMware.Vim.TaskInfoState]::queued)){

    Sleep 5

    $task.UpdateViewData()

    }

    if($task.Info.State -eq [VMware.Vim.TaskInfoState]::running){

    $task.CancelTask()

    }

    elseif($task.Info.State -eq [VMware.Vim.TaskInfoState]::error){

    Write-Error "Update Tools failed"

    }

    }

    Write-Host "Waiting for GuestOperationsReady to be true on $($templatevm.Name)"

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

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

    Start-Sleep -Seconds 3

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

    }

    Write-Host "Performing Invoke Operation on $($templatevm.Name)"

    $sInvoke = @{

    VM            = $templatevm.Name

    GuestCredential=$cred

    ScriptText    = $script

    ScriptType    = 'Powershell'

    RunAsync      = $true

    Confirm       = $false

    }

    $tasks += @{

    VM = $templatevm.Name

    Task = Invoke-VMScript @sInvoke

    }

    }

    }

    Write-Host "Invoke Operation is performed on all the templates and waiting for results to be collected"

    while($tasks.Task.State -contains 'Running'){

    sleep 2

    Write-Host "waiting on Invoke Operation to Complete" -ForegroundColor Yellow

    }

    Write-Host "Passing tasks information to foreach loop"

    $tasks |ForEach-Object -Process {

    $vm=Get-VM -Name $_.VM

    Write-Host "Stopping VM $($vm.Name) to Apply windows updates"

    Stop-VMGuest -VM $vm.Name -Confirm:$false

    while($vm.ExtensionData.Runtime.PowerState -ne 'poweredOff'){

    Start-Sleep -Seconds 1

    $vm.ExtensionData.UpdateViewData("Runtime.Powerstate")

    }

    Write-Host "Starting back the VM $($vm.Name) after applying updates"

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

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

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

    Start-Sleep -Seconds 1

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

    }

    Write-Host "Performing Invoke operation on VM $($vm.Name) to check if all updates are installed"

    $updatescheckscript=@'

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

    $updateSearcher = $updateObject.CreateUpdateSearcher()

    $searchResults = $updateSearcher.Search("IsInstalled=0")

    $timeoutValue = 1200

    $startTime = Get-Date

    while($searchResults.Updates.Count -ne '0' -and (New-TimeSpan -Start $startTime -End (Get-Date)).TotalSeconds -lt $timeoutValue){

    Start-Sleep 1

    $searchResults.Updates.Count

    }

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

    'windows update completed Partially'

    }

    else{

    'windows update completed sucessfully'

    }

    '@

    $sInvoke = @{

    VM            = $vm.Name

    GuestCredential=$cred

    ScriptText    = $updatescheckscript

    ScriptType    = 'Powershell'

    ErrorAction   = 'SilentlyContinue'

    Confirm       = $false

    }

    $invokeresult=Invoke-VMScript @sInvoke

    Write-Host "Result of invoke operation on VM $($vm.Name)"

    Write-Host $invokeresult.ScriptOutput

    Write-Host "Performing Stop operation on VM $($vm.Name) before converting to Template"

    $maxCount = 3

    $count = 0

    while($count -lt $maxCount -and $vm.PowerState -ne 'PoweredOff' ){

    Stop-VMGuest -VM $vm.Name -Confirm:$false

    $count++

    Sleep 300

    $vm = Get-VM -Name $vm.Name

    }

    if($vm.PowerState -ne 'PoweredOff'){

    Stop-VM -VM $vm -Confirm:$false

    }

    Write-Host "Updating report to csv"

    if($_.Task.State -eq 'Success'){

    $_.Task.Result.Scriptoutput | ConvertFrom-Csv |

    Add-Member -MemberType NoteProperty -Name VM -Value $_.VM.Name -PassThru |

    Add-Member -MemberType NoteProperty -Name State -Value $_.Task.State -PassThru

    }

    else{

    New-Object -TypeName PSObject -Property @{

    VM = $_.VM.Name

    KB = ''

    InstallStatus = ''

    State = $_.Task.State

    }

    }

    } | Select VM,State,KB,InstallStatus |Export-Csv -Path $Outputfile -NoTypeInformation -NoClobber -UseCulture

    Write-Host "Saved results to csv file"

    $csvFiles += $Outputfile

    Write-Host "Converting Back to templates"

    $alltemplates |ForEach-Object -Process {

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

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

    }

    Stop-Transcript

    $csvFiles+=$logfilelocation

    $csvFiles+=$alltemplatesexportpath

    Send-MailMessage -From "" -To "" -Subject "Template Patching Info" ` -Body "The attachment contains templates patching status after script execution" ` -Attachments $csvFiles -SmtpServer ''



  • 2.  RE: all windows templates patching in a datacenter
    Best Answer

    Posted Aug 31, 2020 12:04 PM

    Since you are using the pipeline in the $tasks foreach loop to capture the results, you should make sure that nothing else is placed on the pipeline.

    The Stop-VMGuest, Stop-VM and Start-VM cmdlets place objects in the pipeline.

    Redirect those to Out-Null (see attached file).

    PS: line breaks and indentation make a file so much easier to read (and debug)



  • 3.  RE: all windows templates patching in a datacenter

    Posted Sep 03, 2020 04:37 AM

    Thanks LucD. Now the results are showing correctly.