1

I am facing strange behaviours of XmlElements within Jobs in my Powershell-Script. $CommandConfiguration is an XML-Element with nested elements inside. These elements exist within the script outside of the job's scope, this has been tested and debugged. Consider this snippet of code:

if($CommandConfiguration.ChildNodes.Name -contains "DomainCheck"){
    Write-Host ChildNode exists                            # this is executed
}
$Jobs += Start-Job -Name "WMI-Job" -ScriptBlock {
    $th = $Using:TargetHost
    $cc = $Using:CommandConfiguration                     
    $creds = $Using:Creds

    if($cc.ChildNodes.Name -contains "DomainCheck"){
        Write-Host ChildNode exists                        # this is NOT executed
    }

...

Why does the childnode exist outside of the job but not inside of it, apparently? Am I missing something?

Thanks for helping me out!

Daniel
  • 578
  • 6
  • 18
  • Just for completeness, you do use something like `Receive-Job` to actually get the job output, right? Otherwise `Write-Host` isn't going to show much anyway. – Jeroen Mostert May 07 '20 at 11:31
  • Yes I am waiting for job failure or completion and receive it afterwards. the output write-host should give does not show up. – Daniel May 07 '20 at 11:44
  • 1
    Depending on what `CommandConfiguration` is it might not get serialized correctly to be useful in a `using`, but that's speculating. See what `($using:CommandConfiguration).GetType()` outputs as opposed to its type outside the block. Anything more complicated than a `string` runs the risk of not ending up exactly as the object it used to be, and certain conveniences like scripted or magic properties can disappear. A simple XML element works in my tests, but you never know. – Jeroen Mostert May 07 '20 at 11:50
  • @JeroenMostert outside the Job it is an XMLElement, inside it is a System.Management.Automation.PSObject. Is there any way I can resolve this? – Daniel May 07 '20 at 11:59
  • Easiest way is to take care of the serialization yourself: either parse the XML proactively and pass custom PS objects (do you really need all of `CommandConfiguration`?), or pass the XML as a serialized string and convert it back to XML inside the job script block. The latter may in fact work out of the box if you're lucky and the default serialization was "good enough"; try just `[xml] ($using:CommandConfiguration)`. – Jeroen Mostert May 07 '20 at 12:03
  • Your proposal ends in the error message: "The specified node cannot be inserted as the valid chold of this node, because the specified node is the wrong type." So I take it that I cannot serialize the XmlElement because it has nested childnodes? – Daniel May 07 '20 at 12:24
  • 1
    No, it just means PowerShell has serialized it to something that cannot be trivially converted to a string (I don't know exactly what), and hence my proposed method of deserializing it fails. You can always force it to a flat string instead (`$stringCommandConfiguration = $CommandConfiguration.InnerXml`; ... `$cc = [xml] $using:stringCommandConfiguration`). – Jeroen Mostert May 07 '20 at 12:28

1 Answers1

1

tl;dr

As Jeroen Mostert states, you must reference the XML elements from the caller's scope as strings in your background job and parse them into an XML DOM again there.

Start-Job -Name 'WMI-Job' -ScriptBlock {
  # ...
  # Use .OuterXml to get the XML element's string representation
  # from the caller's scope, and parse it into an XML document,
  # which makes $cc.ChildNodes work as expected.
  [xml] $cc = $Using:CommandConfiguration.OuterXml
  # ...
}

Your attempt to directly reference System.Xml.XmlElement from the caller's scope in the job resulted in loss of the type identity due to the serialization that values transferred between sessions across process boundaries must undergo, and the deserialized emulations of the XmlElement input objects lack the ChildNodes property - read on for more.


Context

Generally, when data is transferred to and from out-of-process sessions, such as background jobs started with Start-Job, only a handful of well-known types deserialize with type fidelity.

Instances of all other types are method-less emulations of the original objects, effectively [pscustomobject]s with static copies of the original object's property values, and with a PowerShell ETS (Extended Type System) type name that reflects the original type's full name prefixed with Deserialized.

See this answer for more information.


Perhaps surprisingly,

Note that in the case of XmlElement only the so-called adapted properties of the original instance are reflected in the Deserialized.System.Xml.XmlElement (pscustomobject) property.

That is, the emulation has only the properties that PowerShell added to the original instance, namely properties that directly surface the child elements and attributes of the underlying XML DOM, which enables convenient use of the usual dot notation - see this answer.

The .NET XmlElement type's true properties, such as ChildNodes are not present on the deserialized emulation, which is why your code fails.

The workaround, as shown at the top, is to pass the string representation of the XmlElement instances to the background job - available via the OuterXml property - and re-parse that string into an XML DOM there (note that, strictly speaking, casting to [xml] creates a System.Xml.XmlDocument instance, but with respect to what you're trying to do that shouldn't matter).

mklement0
  • 382,024
  • 64
  • 607
  • 775