Search code examples
powershellinvoke-commandwindows-update

Powershell Capture Invoke-Command Output


I've come across a problem which I can't seem to figure out. I have this piece of code which does exactly what I want. It searches for Windows Updates which are installed and have an specific UpdateID.

param(
    $updateId = $false,
    $hostName = $false
)


if(($updateId -eq $false) -or ($hostName -eq $false))
{
   Write-Host "checkUpdateInstalled.ps1 -updateId <updateIdValue> -hostName <Remote Host Name>"
   exit
}


Invoke-Command -ComputerName $hostName -ScriptBlock {
   $searcher = New-Object -ComObject Microsoft.Update.Searcher
   $searcher.Search("IsInstalled=1 AND UpdateID='$Using:updateId'")
   $tmp.Updates| ForEach-Object {
        $i++
        Write-Host "UpdateInfo Update No. $i"
        Write-Host "Title: `t`t" $_.Title
        Write-Host "Description: `t`t " $_.Description
        Write-Host "UpdateID: `t`t " $_.Identity.UpdateID
        Write-Host "RevisionNumber: `t`t " $_.Identity.RevisionNumber
        Write-Host "KBArticleIDs: `t`t " $_.KBArticleIDs
        Write-Host "==============================================="    
    }
}

With this solution I can't use $tmp.Updates outside of the Invoke-Command but the information I try to gather with the ForEach-Object Loop works fine. Printing $tmp.Updates in this case gives me information about the specific update.

So I tried the following to have access to $tmp:

## Same top part


$tmp = Invoke-Command -ComputerName $hostName -ScriptBlock {
   $searcher = New-Object -ComObject Microsoft.Update.Searcher
   $searcher.Search("IsInstalled=1 AND UpdateID='$Using:updateId'")
}

$tmp.Updates| ForEach-Object {
    $i++
    Write-Host "UpdateInfo Update No. $i"
    Write-Host "Title: `t`t" $_.Title
    Write-Host "Description: `t`t " $_.Description
    Write-Host "UpdateID: `t`t " $_.Identity.UpdateID
    Write-Host "RevisionNumber: `t`t " $_.Identity.RevisionNumber
    Write-Host "KBArticleIDs: `t`t " $_.KBArticleIDs
    Write-Host "==============================================="    
}

With this attempt the Loop does not print information. If I try to print $tmp.Updates I just get System.__ComObject.

Can anyone relate?


Solution

  • That is the kind of behavior you will get with Invoke-Command by design.

    Invoke-Command do not return the objects from the remote session. Rather, it return a representation of the object that did go through several processes.

    First, it is serialized on the remote environment, then deserialized back on the local environment.

    That is for everything that get transmitted. There are some primitive types, serialization-wise, that get deserialized into a "live" object directly, such as:

    • Byte, SByte, Byte[]
    • Int16, Int32, Int64, UInt16, UInt32, Uint64
    • Decimal, Single, Double
    • TimeSpan, DateTime, ProgressRecord
    • Char, String, XmlDocument, SecureString
    • Boolean,Guid, Uri, Version

    Then you have types that are not deserialized with full fidelity, but behave as primitive types for most practical purposes.

    This include Enums, which are deserialized into an underlying integer. Similarly, deserializer will preserve contents of lists, but might change the actual type of the container. (eg: List deserialized to ArrayList, Dictionaries deserialized into Hashtables, etc...)

    Finally, you also have some objects that get rehydrated into their live counterpart. For instance, IP Address object get serialized, then deserialized into a Deserialized.System.Net.IPAddress and converted again to its original type through "rehydration", which is the process that dictate how the deserialized type should be converted again.

    There is some built-in rehydration for some of PowerShell types… :

    • PSPrimitiveDictionary
    • SwitchParameter
    • PSListModifier
    • PSCredential

    as well as for some types from base class libraries:

    • IPAddress, MailAddress
    • CultureInfo
    • X509Certificate2, X500DistinguishedName
    • DirectorySecurity, FileSecurity, RegistrySecurity

    So, to do what you seek, you will need to return objects that are serializable. You will need to dig into the COM object and return the properties values that you want. You could use Get-Member to determine the available properties and from there, return what you want.

    You could also use ConvertTo-Json on the remote object to return a json representation of it and convert it back to a PSObject locally. You won't get an accurate representation either, type-wise, but you might get a better view of the properties / values. Don't forget to set the -Depth parameter to an higher number if needed since the default is 4 layers deep.

    Reference

    Microsoft Dev-blogs - How objects are sent to and from remote address.