2

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?

k_k
  • 83
  • 7

2 Answers2

4

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.

Sage Pourpre
  • 9,932
  • 3
  • 27
  • 39
  • Thank you Sage. This actually helped me to understand the 'magic' I was confronted with. I have to search for another solution to my problem. – k_k Jun 21 '22 at 13:12
  • 1
    @k_k Not sure if you saw that as I just added it but you could do a `ConvertTo-Json` remotely then `ConvertFrom-Json` on the received object to dig into it and view the values. This should work relatively well, value wise (Type-wise... it won't help) . – Sage Pourpre Jun 21 '22 at 13:15
  • Thanks I appreciate that. I am actually using `Export-Clixml` and `Import-Clixml`. The basic problem of mine is that I did not want to save anything on the remote computer. Now I can't see any other solution than serializing on the target machine and than importing that and returning the value from the invoke command. Before I used `Start-WUScan` but I realized that on our actual Windows Builds it's not working and there is a big thread going on where ppl want that feature again. I thought it would be good to have a backup solution on our Windows Servers 2019. – k_k Jun 21 '22 at 13:27
  • 1
    @k_k Instead of `Export-CLIXML`, use the `[System.Management.Automation.PSSerializer]::Serialize($Object,2)` method and its deserialize counterpart. This will produce the same result but you never go to the filesystem that way. See [this](https://github.com/PowerShell/PowerShell/issues/3898#issuecomment-542025969) for reference and Mklement0 comment just under it. – Sage Pourpre Jun 21 '22 at 14:13
3

There seems to be a limit to the depth of the serialized object returned (no methods). This works for me, returning the updates sub-property directly. Otherwise "updates" becomes an array of strings. Although I would use "get-ciminstance win32_quickfixengineering" instead.

$tmp = Invoke-Command localhost {  # elevated
  $searcher = New-Object -ComObject Microsoft.Update.Searcher
  $searcher.Search('IsInstalled=1')
}
$tmp.Updates
System.__ComObject
System.__ComObject
System.__ComObject
...


$tmp.Updates[0].gettype()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object
$tmp = Invoke-Command localhost {  # elevated
    $searcher = New-Object -ComObject Microsoft.Update.Searcher
    $searcher.Search('IsInstalled=1').updates
 }
$tmp | % title

MSXML 6.0 RTM Security Update  (925673)
Update for Microsoft Report Viewer Redistributable 2010 Service Pack 1 (KB2549864)
Security Update for Microsoft Visual C++ 2005 Service Pack 1 Redistributable Package (KB2538242)
...

Return the properties you want...

Invoke-Command localhost {  # elevated
  $searcher = New-Object -ComObject Microsoft.Update.Searcher
  $searcher.Search('IsInstalled=1').updates | select title, description,
    @{n='UpdateID'; e={$_.Identity.UpdateID}},
    @{n='RevisionNumber';e={$_.Identity.RevisionNumber}}, KBArticleIDs
}

Title          : Update for Windows Defender Antivirus antimalware platform - KB4052623 (Version 4.18.2001.10)
Description    : This package will update Windows Defender Antivirus antimalware platform’s components on the user machine.
UpdateID       : c01629fc-64ea-45f3-b7cb-cabc7d566933
RevisionNumber : 200
KBArticleIDs   : {4052623}
PSComputerName : localhost
RunspaceId     : 57b3ae43-6ee6-4e0c-add1-d9d82aba3f61
invoke-command localhost { get-ciminstance win32_quickfixengineering }

Source        Description      HotFixID      InstalledBy          InstalledOn                PSComputerName
------        -----------      --------      -----------          -----------                --------------
              Update           KB5013887     NT AUTHORITY\SYSTEM  6/18/2022 12:00:00 AM      localhost
              Update           KB4562830     NT AUTHORITY\SYSTEM  2/1/2021 12:00:00 AM       localhost
              Update           KB4577586     NT AUTHORITY\SYSTEM  5/24/2021 12:00:00 AM      localhost
js2010
  • 23,033
  • 6
  • 64
  • 66
  • I am not quite sure if this will work as I expect. It has to depend on which information the IUpdateInstaller needs from the Updates properties. I realize that e.g I can't print updateID and Revisionnumber with this method. I'll try to install updates with this method (can't right now because all of our remote machines are already updated). As soon as I find out I'll mention the result. Thanks for the input – k_k Jun 22 '22 at 07:39
  • 1
    Yeah, the Identity sub-property still gets clobbered. – js2010 Jun 22 '22 at 13:25
  • The edit you did is a really useful input for me. Thanks again! – k_k Jun 23 '22 at 09:07