I'm trying to pass a System.XML.XMLDocument object through function calls, however that object is changed to System.Object[] after function call.
More specifically, I have a function like this:
function GenerateXml()
{
[System.XML.XMLDocument]$xmlDoc=New-Object System.XML.XMLDocument
$declaration = $xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes")
$xmlDoc.AppendChild($declaration)
[System.XML.XMLElement]$root = $xmlDoc.CreateElement("dummy")
$root.SetAttribute("Name", "dummy value")
$xmlDoc.AppendChild($root)
$xmlDoc
}
[System.XML.XMLDocument]$xmlDoc = GenerateXml
This will output an error like:
Cannot convert value "System.Object[]" to type "System.Xml.XmlDocument".
So what is the best way of passing a XMLDocument through function calls?
Turns out Powershell will return all the output from a method to an array. To get the actual return value, you can either get the last member of the output array or just don't output anything else other than what you actually want.
Your follow up response "output a method to an array" is true, but avoidable. In your example you utilize the .AppendChild() method, which in itself can produce output and therefore a Return value as objects in an array. This scenario can be avoided as
$xmlDoc.AppendChild($root) | Out-Null
Related
Why is the following changing type?
function SomeFunction($SomeParameter){
return $SomeParameter
}
I guess I need to set a return type, but how?
An example is using:
$NewFolder=Join-Path $CurrentFolder -ChildPath $FolderName
$Tmp=SomeFunction($NewFolder)
Now $Tmp is an array and not just a path
While this answer explains the behavior you're seeing, here I will attempt to answer the actual question: how to declare the expected output type of a function!
You do so by adding an [OutputType] attribute to the param() block of your function - so the first thing you'll want to do is to skip the C#-style param list and declare a proper param block instead:
function SomeFunction
{
param($SomeParameter)
return $SomeParameter
}
Now we just need to add the [OutputType] attribute decorator:
function SomeFunction
{
[OutputType([string])]
param($SomeParameter)
return $SomeParameter
}
since we're just returning the parameter argument value as-is in this example, we should play nice and make sure it's actually also a string:
function SomeFunction
{
[OutputType([string])]
param(
[string]$SomeParameter
)
return $SomeParameter
}
Worth noting that [OutputType()] makes no guarantees as to the type of objects emitted during execution, it's simply a way for the author of a function to indicate the intended output type.
Read more about [OutputType] in the about_Functions_OutputTypeAttribute help file
Your issue is per "design". PowerShell will return an array in chunks so that it can be forwarded the PowerShell pipeline.
Example:
SomeFunction -SomeParameter #(1,2,3,4) | Where-Object { $_ -gt 2 }
Without this behavior pipelining the output of the function to another function/cmdlet won't be possible.
If you want to return an array you can change to code to:
function SomeFunction($SomeParameter){
<#
# Through the unary operator we can return an array with one entry.
# This entry contains the original array.
#>
,$SomeParameter
}
Another option would be the use of #() when at the calling side:
function SomeFunction($SomeParameter){
# return to pipelin
$SomeParameter
}
$array = #(SomeFunction -SomeParameter 1,2,3,4)
There is also this reddit answer explaining the behavior in more detail.
Hope that helps.
I have been working with the PowerShell AST to create some custom rules for PSScriptAnalyzer.
In a lot of the example code for AST, there is one line that I don't understand. Here is an example.
First parse a file, in this case, the current open file in the ISE.
$AbstractSyntaxTree = [System.Management.Automation.Language.Parser]::
ParseInput($psISE.CurrentFile.Editor.Text, [ref]$null, [ref]$null)
This makes sense so far. Let's say that we want to look for all the ParameterAst objects. The code that I have seen to do this is below.
$params = $AbstractSyntaxTree.FindAll({$args[0] -is [System.Management.Automation.Language.ParameterAst]}, $true)
This line of code is calling FindAll and passing in a scriptblock, that seems to be acting as a filter, so that only ParameterAst objects are returned.
What I don't understand here is how $args[0] fits into this call. How are any parameters actually getting passed into the scriptblock when the FindAll method is invoked?
FindAll method has following signature (from msdn):
public IEnumerable<Ast> FindAll (
Func<Ast,bool> predicate,
bool searchNestedScriptBlocks
)
So first argument is a delegate that takes Ast as input, and returns bool.
In Powershell you can create such delegate like that:
$delegate = { param($ast) $ast -is [System.Management.Automation.Language.ParameterAst] }
Or without declaring parameter:
$delegate = { $args[0] -is [System.Management.Automation.Language.ParameterAst] }
FindAll method will then do something like that (pseudocode):
foreach ($node in $allNodes) {
$shouldAdd = & $delegate $node <-- this is how $node gets passed to your delegate
if ($shouldAdd) {
<add the node to the output list>
}
}
Think of the scriptblock as an anonymous callback function.
It's really the same thing that happens when you use Where-Object { $someCondition }.
.FindAll finds all the (things) and for each one it calls the function you provided it. It's apparently expecting a [bool] result, and returning the objects that satisfied the conditions present in the callback.
In a function or script or scriptblock in powershell, you can have named parameters that are explicitly defined, or you can reference parameters without declaring them using the $args array, which is what's happening here.
Using a scriptblock as a callback is similar to using it for an event:
$Args
Contains an array of the undeclared parameters and/or parameter
values that are passed to a function, script, or script block.
When you create a function, you can declare the parameters by using the
param keyword or by adding a comma-separated list of parameters in
parentheses after the function name.
In an event action, the $Args variable contains objects that represent
the event arguments of the event that is being processed.
how does putting a comma before a list affect its type?
Have a look at the following code:
function StartProgram
{
$firstList = getListMethodOne
Write-Host "firstList is of type $($firstList.gettype())"
$secondList = getListMethodTwo
Write-Host "secondList is of type $($secondList.gettype())"
}
function getListMethodOne
{
$list = new-object system.collections.generic.list[string]
$list.Add("foo") #If there is one element, $list is of type String
$list.Add("bar") #If there is more than one element, $list is of type System.Object[]
return $list
}
function getListMethodTwo
{
$list = new-object system.collections.generic.list[string]
$list.Add("foo")
$list.Add("bar")
return ,$list #This is always of type List[string]
}
StartProgram
Why is it, if you don't use a comma before returning $list in getListMethodOne it returns as type System.Object[], whereas if you do use a comma as in getListMethodTwo, it is of type List[string] as expected?
PS: I'm using PSVersion 4.0
When you return collection, than PowerShell is kind enough to unravel it for you.
Unary comma creates collection with single element, so "external" collection gets unraveled and collection you want to return is kept.
I blogged about it a while ago.
Two more things:
return is used in PowerShell to leave function early, it's not needed to return something from function (any not captured output is returned)
in PowerShell 4.0 you can use Write-Output -NoEnumerate $collection to prevent unraveling your collection.
I don't have a full answer, but I would bet this relates to PowerShell's 'flattening' behaviour.
By using the unary operator ',' you are creating a new collection wrapper around the $list object. After PowerShell 'flattens' this, you see the object that was inside the wrapper.
A fuller explaination here: http://rkeithhill.wordpress.com/2007/09/24/effective-powershell-item-8-output-cardinality-scalars-collections-and-empty-sets-oh-my/
Why do I need to declare the type of a variable when assigning it with a list?
In the below code, I need to specify that $firstList is of type list[string], if I don't do that, then its type is Object[], even though in the function returning that list, the list is of type list[string].
function StartProgram
{
[system.collections.generic.list[string]]$firstList = getList
$secondList = getList
Write-Host "firstList is of type $($firstList.gettype())"
Write-Host "secondList is of type $($secondList.gettype())"
}
function getList
{
$list = new-object system.collections.generic.list[string]
$list.Add("foo")
$list.Add("bar")
return $list
}
StartProgram
<#
Output:
firstList is of type System.Collections.Generic.List[string]
secondList is of type System.Object[]
#>
PowerShell functions write output to the output stream and when a collection is presented as output, the default behavior is to enumerate the collection and output that. What you get on the other side - when you assign the output to a collection - as an array of objects. That is, the List gets lost during the output operation. You can change this behavior by wrapping the list in an array with it as the single element by changing the return statement to;
return ,$list
or more simply just;
,$list
Anyone have any ideas why the following code would produce an error, see additional comments after the function for more details
function callee ([Hashtable]$arg0) {
[Hashtable]$hashtable = #{}
$hashtable = $arg0
$hashtable.add('passed', $True)
# $hashtable ######## toggle this line
$type = $hashtable.GetType()
Write-Host "$type"
return $hashtable
}
function caller {
[Hashtable]$hashtable = #{'00'='0'}
$hashtable = callee $hashtable ##### returns error here
$hashtable.add('returned', $True)
$hashtable
}
caller
error message:
Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Collections.Hashtable".
I receive the error on a variety of instances, I tried to narrow it down to an example that is easy to reproduce. It looks like it is changing the hashtable to an object array and that is why it won't return it? It allows me to modify the hashtable and return it but when I try to display it it changes it? This is the same effect I get when I start adding code to the callee function?
When you uncomment # $hashtable you're outputting two things from the function. The result of the function is everything 'output' from it, and PowerShell will automatically wrap multiple outputs into an array. The return statement is a short-circuit convenience and should not be confused with the only way to return a value from the function.