I'm testing a deployment script for my application on Windows Server 2012 with Powershell v3. The script runs fine on Win Server 2008 R2 and Win 7 with Powershell v2. The issue I'm running into now is that I can not access properties of XML variables passed via -ArgumentList.
I've been able to reproduce the issue on Win 7 and Win Server 2012 with Powershell v3 in a simple script that doesn't have any of the SharePoint, IIS, misc that my main script does.
Script (I think I borrowed this from a similar question I can't find now):
$xml = [xml] (Get-Content (Get-Item (".\input.xml")))
$foobar = $xml.Stuff.FooBars.Foobar
$ScriptBlock = {
$foobar = $args[0]
write-host "Inside the job..."
write-host ("Foobar : "+$foobar)
write-host ("Foobar.Foo : "+$foobar.Foo)
}
write-host "Outside the job..."
write-host ("Foobar: "+$foobar)
write-host ("Foobar.Foo : "+$foobar.Foo)
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $foobar | Out-Null
While (Get-Job -State "Running") { Start-Sleep 2 }
write-host ("Jobs Completed.")
Get-Job | Receive-Job
Remove-Job *
The Input XML:
<?xml version="1.0"?>
<Stuff xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FooBars>
<FooBar>
<Foo>ThisIsAFoo</Foo>
<Bar>ThisIsABar</Bar>
</FooBar>
</FooBars>
</Stuff>
Output from Powershell v2:
PS C:\Powershell3Issues> .\demo.ps1
Outside the job...
Foobar: System.Xml.XmlElement
Foobar.Foo : ThisIsAFoo
Jobs Completed.
Inside the job...
Foobar : System.Collections.ArrayList System.Collections.ArrayList
Foobar.Foo : ThisIsAFoo
Output from Powershell v3:
PS C:\Powershell3Issues> .\demo.ps1
Outside the job...
Foobar: System.Xml.XmlElement
Foobar.Foo : ThisIsAFoo
Jobs Completed.
Inside the job...
Foobar : System.Collections.ArrayList System.Collections.ArrayList
Foobar.Foo :
Note the missing Foobar.Foo value.
I've also tried the $using syntax in v3 but it does the same thing.
Try this to specify the version of PowerShell to run the job under:
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $foobar -PSVersion 2.0
I'm using PS 3.0 and it does change data types. I modified your script to take a look:
$xml = [xml] (Get-Content .\input.xml)
$foobar = $xml.Stuff.FooBars.Foobar
"Foobar Outside type = $($foobar.Gettype())"
$ScriptBlock = {
$foobar = $args[0]
"Foobar Inside type = $($foobar.Gettype())"
}
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $foobar | Out-Null
While (Get-Job -State "Running") { Start-Sleep 2 }
Get-Job | Receive-Job
The output I got was:
Foobar Outside type = System.Xml.XmlElement
Foobar Inside type = System.Collections.ArrayList System.Collections.ArrayList
I'll keep looking and see what I find.
Update:
When I run the script in PS 2.0 using the command powershell -version 2.0
I get an error saying:
Method invocation failed because [Deserialized.System.Xml.XmlElement] doesn't contain a method named 'Gettype'.
+ CategoryInfo : InvalidOperation: (Gettype:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
+ PSComputerName : localhost
It changes from System.Xml.XmlElement to Deserialized.System.Xml.XmlElement, right? I'll keep looking and see what I find.
Workaround:
A workaround could be instead of passing an object just pass a string.
$xml_path = 'C:\input.xml'
$sb = {
$xml = [xml](Get-Content $args[0])
...
}
Start-Job -ScriptBlock $sb -Args $xml_path
I'm done searching. My head hurts when I go in depth on things.
The problem is you are trying to serialise an object of type XMLElement which does not serialise. The workaround is to clone the XMLElement and wrap it in a new XMLDocument.
cls
$XMLDocument = [xml]@"
<?xml version="1.0"?>
<Stuff xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FooBars>
<FooBar>
<Foo>ThisIsAFoo1</Foo>
<Bar>ThisIsABar2</Bar>
</FooBar>
<FooBar>
<Foo>ThisIsAFoo2</Foo>
<Bar>ThisIsABar2</Bar>
</FooBar>
</FooBars>
</Stuff>
"@
Select-Xml -Xml $XMLDocument -XPath 'Stuff/FooBars/FooBar' | foreach {
$SelectedNode = $_.Node
#$SelectedNode is now of type System.Xml.XmlElement which does not serialise
#but System.Xml.XmlDocument will serialise so wrap a clone of $SelectedNode in a new XMLDocument
#then pass that as the argument
$SerializationWrapper = New-Object System.Xml.XmlDocument
$SerializationWrapper.AppendChild($SerializationWrapper.ImportNode($SelectedNode, $true)) | Out-Null
Start-Job -ScriptBlock {
param($xml)
Write-Output "Start Job"
'The type of deserialise object $xml is: ' + $xml.getType()
$sw = New-Object system.io.stringwriter
$writer = New-Object system.xml.xmltextwriter($sw)
$writer.Formatting = [System.xml.formatting]::Indented
$xml.WriteContentTo($writer)
$sw.ToString()
Write-Output "Finish Job"
Write-Output ""
} -ArgumentList $SerializationWrapper
}
Get-Job | Wait-Job | Receive-Job
As you can see from the following output 2 jobs were spawned and a single wrapped FooBar element is passed for formatting.
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
520 Job520 BackgroundJob Running True localhost ...
522 Job522 BackgroundJob Running True localhost ...
Start Job
The type of deserialise object $xml is: xml
<FooBar>
<Foo>ThisIsAFoo1</Foo>
<Bar>ThisIsABar2</Bar>
</FooBar>
Finish Job
Start Job
The type of deserialise object $xml is: xml
<FooBar>
<Foo>ThisIsAFoo2</Foo>
<Bar>ThisIsABar2</Bar>
</FooBar>
Finish Job