问题
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.
回答1:
Try this to specify the version of PowerShell to run the job under:
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $foobar -PSVersion 2.0
回答2:
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.
回答3:
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
来源:https://stackoverflow.com/questions/15578577/start-job-passing-xml-object-to-argumentlist-working-different-in-powershell-v2