vin01
Expert
Expert

all windows templates patching in a datacenter

Jump to solution

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

pastedImage_0.png

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 ''

Regards Vineeth.K
0 Kudos
1 Solution

Accepted Solutions
LucD
Leadership
Leadership

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)


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

View solution in original post

2 Replies
LucD
Leadership
Leadership

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)


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

View solution in original post

vin01
Expert
Expert

Thanks LucD. Now the results are showing correctly.

Regards Vineeth.K
0 Kudos