vin01
Expert
Expert

how to write invoke-vmscript output which contains multiple lines

Jump to solution

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.

pastedImage_1.png

But when I executed using invoke-vmscript the result is below:

pastedImage_0.png

Regards Vineeth.K
0 Kudos
1 Solution

Accepted Solutions
LucD
Leadership
Leadership

Try this version

$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'){

                    '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

View solution in original post

11 Replies
LucD
Leadership
Leadership

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

vin01
Expert
Expert

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:

pastedImage_1.png

--------------------------------------

Expecting output as below:

VM : VM01

State: Success

Error:

KB: KB4565628

Installstatus: KBInstalled

VM : VM01

State: Success

Error:

KB: KB4565511

Installstatus: KBInstalled

Regards Vineeth.K
0 Kudos
LucD
Leadership
Leadership

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

$report = @()

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

0 Kudos
vin01
Expert
Expert

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.

pastedImage_1.png

------------------------------------------------------------------------------

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

Regards Vineeth.K
0 Kudos
LucD
Leadership
Leadership

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

0 Kudos
vin01
Expert
Expert

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

Regards Vineeth.K
0 Kudos
LucD
Leadership
Leadership

Try something like this

$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 @{

        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

vin01
Expert
Expert

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.

Regards Vineeth.K
0 Kudos
LucD
Leadership
Leadership

Try this version

$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'){

                    '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

View solution in original post

vin01
Expert
Expert

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

Regards Vineeth.K
0 Kudos
LucD
Leadership
Leadership

Just change the last line to

} | Select VM,State,KB,InstallStatus | Export-Csv -Path .\report.csv -NoTypeInformation -UseCulture


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

0 Kudos