When I execute the code which is in $script inside a guest os I can get the expected results but using Invoke-VMScript the results are not writing properly.
$script=@'
$results=@()
$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
if(-not $searchresult.Count){
$results+="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
$results+=foreach($singleupdate in $downloadedupdates){
[void]$updatercall.Updates.add($singleupdate)
$installstatus=$updatercall.install()
[void]$updatercall.Updates.RemoveAt(0)
''|Select-Object @{N="KB";E={$kbnumb=$singleupdate.Title;$kbnumb.Substring($kbnumb.IndexOf("KB")).Trimend(")")}},@{N="Installstatus";E={if($installstatus.ResultCode -eq '2'){
'KBInstalled'}
elseif($installstatus.ResultCode -eq '3'){
'KBInstall Succeeded with errors'}
elseif($installstatus.ResultCode -eq '4'){
'Kb Failed to install'}
elseif($installstatus.ResultCode -eq '5'){
'KBAborted'}}}
}
}
$results
'@
foreach($templatevm in (Get-VM "*Template*")){
$sInvoke = @{
VM = $templatevm.Name
GuestUser = 'administrator'
GuestPassword = 'password'
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
}
foreach ($task in $tasks) {
$task | Select @{N='VM';E={$_.VM}},
@{N='State';E={$_.Task.State}},
@{N='Error';E={$_.Task.TerminatingError.Message}},
@{N ='Result'; E={$_.Task.Result.ScriptOutput.Split("`n") |Where-Object { $_ -ne '' } | %{$_.Trim("`r`n")}}}
}
Result of $script inside the guest OS.
But when I executed using invoke-vmscript the result is below:
Try this version
$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'){
'KBInstalled'
}
elseif($installstatus.ResultCode -eq '3'){
'KBInstall Succeeded with errors'
}
elseif($installstatus.ResultCode -eq '4'){
'Kb Failed to install'
}
elseif($installstatus.ResultCode -eq '5'){
'KBAborted'
}
}
}
}
}
$report | ConvertTo-Csv -NoTypeInformation
'@
$vmNames = 'TestVM01'
$tasks = @()
Get-VM -Name $vmNames -PipelineVariable vm |
ForEach-Object -Process {
$sInvoke = @{
VM = $vm
GuestUser = 'administrator'
GuestPassword = 'password'
ScriptText = $script
ScriptType = 'Powershell'
RunAsync = $true
Confirm = $false
}
$tasks += @{
VM = $vm
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 "." -NoNewline
}
Write-Host "All tasks finished"
$tasks | ForEach-Object -Process {
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
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Your script produces in fact output for the screen inside the Guest OS.
These formatting directives clutter up the result that is returned.
The better way to transfer data this way is to use okain text.
That is also why we used ConvertTo-Csv in the past.
And then on the receiving side you use ConvertFrom-Csv.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
After using ConvertTo-Csv I can able to get the output in correct way but how to write the output foreach kb in csv.
Right now I getting output as below:
foreach ($task in $tasks) {
$task | Select @{N='VM';E={$_.VM}},
@{N='State';E={$_.Task.State}},
@{N='Error';E={$_.Task.TerminatingError.Message}},
@{N ='KB'; E={(($_.Task.Result.ScriptOutput |ConvertFrom-Csv).KB)}},
@{N ='Installstatus'; E={(($_.Task.Result.ScriptOutput |ConvertFrom-Csv).Installstatus)}}
}
output:
--------------------------------------
Expecting output as below:
VM : VM01
State: Success
Error:
KB: KB4565628
Installstatus: KBInstalled
VM : VM01
State: Success
Error:
KB: KB4565511
Installstatus: KBInstalled
You should do the ConvertFrom-Csv on the complete output, not just individual entries.
Something like this perhaps?
I don't know what you pass as CSV from the script that runs in the Guest OS
foreach ($task in $tasks) {
$report += $task.Result.ScriptOutp | ConvertFrom-Csv |
Select VM,State,Error,KB,InstallStatus
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
This is what I have passed inside the guestos. Actually $task.Task.Result.ScriptOutput contains multiple values for each vm.
$script=@'
$results=@()
$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
if(-not $searchresult.Count){
$results+="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
$results+=foreach($singleupdate in $downloadedupdates){
[void]$updatercall.Updates.add($singleupdate)
$installstatus=$updatercall.install()
[void]$updatercall.Updates.RemoveAt(0)
''|Select-Object @{N="KB";E={$kbnumb=$singleupdate.Title;$kbnumb.Substring($kbnumb.IndexOf("KB")).Trimend(")")}},@{N="Installstatus";E={if($installstatus.ResultCode -eq '2'){
'KBInstalled'}
elseif($installstatus.ResultCode -eq '3'){
'KBInstall Succeeded with errors'}
elseif($installstatus.ResultCode -eq '4'){
'Kb Failed to install'}
elseif($installstatus.ResultCode -eq '5'){
'KBAborted'}}}
}
}
$results |ConvertTo-Csv -NoTypeInformation
'@
$templatevm=Get-VM 'TestVM01'
$sInvoke = @{
VM = $templatevm.Name
GuestUser = 'administrator'
GuestPassword = 'password'
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
}
foreach ($task in $tasks) {
$task | Select @{N='VM';E={$_.VM}},
@{N='State';E={$_.Task.State}},
@{N='Error';E={$_.Task.TerminatingError.Message}},
#@{N ='KB'; E={(($_.Task.Result.ScriptOutput |ConvertFrom-Csv).KB)}},
#@{N ='Installstatus'; E={(($_.Task.Result.ScriptOutput |ConvertFrom-Csv).Installstatus)}}
}
This is the actual output of the single VM.
------------------------------------------------------------------------------
I am expecting Output like this for single VM.
VM : VM01
State: Success
Error:
KB: KB4565628
Installstatus: KBInstalled
VM : VM01
State: Success
Error:
KB: KB4565511
Installstatus: KBInstalled
As far as I can see you are returning two types of results back.
- just a string with "There are no applicable updates for this computer."
- a CSV with 2 properties, KB and InstallStatus
To be able to handle the results uniformly, you will have to return the same layout on all occasions.
I'm not sure why insist on doing the ConvertFrom-Csv for each property separately.
When there is for example more than 1 KB returned, your calculated property KB will be an array.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
I'm not sure why insist on doing the ConvertFrom-Csv for each property separately.
Yeah That's my mistake. I am not clear on how to write the kb results when they are more then one and if no updates are available on the computer. Can you pls correct the above code or rewrite if they are more then one kb or no updates are available. something as below
VM : VM01
State: Success
Error:
KB: KB4565628
Installstatus: KBInstalled
VM : VM01
State: Success
Error:
KB: KB4565511
Installstatus: KBInstalled
VM : VM02
State: Success
Error:
KB:
Installstatus: There are no applicable updates for this computer.
VM : VM03
State: Success
Error:
KB: KB4567511
Installstatus: Kb Failed to install
Try something like this
$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 @{
VM = $env:COMPUTERNAME
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 @{
VM = $env:COMPUTERNAME
KB = &{$kbnumb=$singleupdate.Title; $kbnumb.Substring($kbnumb.IndexOf("KB")).Trimend(")")}
InstallStatus = &{
if($installstatus.ResultCode -eq '2'){
'KBInstalled'
}
elseif($installstatus.ResultCode -eq '3'){
'KBInstall Succeeded with errors'
}
elseif($installstatus.ResultCode -eq '4'){
'Kb Failed to install'
}
elseif($installstatus.ResultCode -eq '5'){
'KBAborted'
}
}
}
}
}
$report | ConvertTo-Csv -NoTypeInformation
'@
$vmNames = 'TestVM01'
$tasks = @()
Get-VM -Name $vmNames -PipelineVariable vm |
ForEach-Object -Process {
$sInvoke = @{
VM = $vm
GuestUser = 'administrator'
GuestPassword = 'password'
ScriptText = $script
ScriptType = 'Powershell'
RunAsync = $true
Confirm = $false
}
$tasks += @{
VM = $vm
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
$tasks.Task.State
}
$tasks | ForEach-Object -Process {
if($_.Task.State -eq 'Success'){
$_.Task.Result.Scriptoutput | ConvertFrom-Csv |
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
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
One Last Thing. I don't want the VMName from inside the guestos(VM = $env:COMPUTERNAME) because for few vms the displayname in the vcenter is different when compare to the vm name inside the guest os. Can it be possible to write only name which is in vcenter.
Try this version
$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'){
'KBInstalled'
}
elseif($installstatus.ResultCode -eq '3'){
'KBInstall Succeeded with errors'
}
elseif($installstatus.ResultCode -eq '4'){
'Kb Failed to install'
}
elseif($installstatus.ResultCode -eq '5'){
'KBAborted'
}
}
}
}
}
$report | ConvertTo-Csv -NoTypeInformation
'@
$vmNames = 'TestVM01'
$tasks = @()
Get-VM -Name $vmNames -PipelineVariable vm |
ForEach-Object -Process {
$sInvoke = @{
VM = $vm
GuestUser = 'administrator'
GuestPassword = 'password'
ScriptText = $script
ScriptType = 'Powershell'
RunAsync = $true
Confirm = $false
}
$tasks += @{
VM = $vm
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 "." -NoNewline
}
Write-Host "All tasks finished"
$tasks | ForEach-Object -Process {
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
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Yeah now its working. How do I export this results to csv file. Is it possible to keep the output in array. something like below so that i can easily export for all the vms
$finaloutput=@()
$finaloutput |Export-Csv
Just change the last line to
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference