PowerShell function won't return object - powershell

I have a simple function that creates a generic List:
function test()
{
$genericType = [Type] "System.Collections.Generic.List``1"
[type[]] $typedParameters = ,"System.String"
$closedType = $genericType.MakeGenericType($typedParameters)
[Activator]::CreateInstance($closedType)
}
$a = test
The problem is that $a is always null no matter what I try. If I execute the same code outside of the function it works properly.
Thoughts?

IMHO that's pitfall #1. If you return an object from the function that is somehow enumerable (I don't know exactly if implementing IEnumerable is the only case), PowerShell unrolls the object and returns the items in that.
Your newly created list was empty, so nothing was returned. To make it work just use this:
,[Activator]::CreateInstance($closedType)
That will make an one item array that gets unrolled and the item (the generic list) is assigned to $a.
Further info
Here is list of similar question that will help you to understand what's going on:
Powershell pitfalls
Avoiding Agnostic Jagged Array Flattening in Powershell
Strange behavior in PowerShell function returning DataSet/DataTable
What determines whether the Powershell pipeline will unroll a collection?
Note: you dont need to declare the function header with parenthesis. If you need to add parameters, the function will look like this:
function test {
param($myParameter, $myParameter2)
}
or
function {
param(
[Parameter(Mandatory=true, Position=0)]$myParameter,
... again $myParameter2)
...

An easier way to work with generics. This does not directly solve the [Activator] approach though
Function test
{
New-Object "system.collections.generic.list[string]"
}
(test).gettype()

Related

Complex data types, byReference arguments and data not changing as expected

I have a function that can either initialize itself and return an ordered dictionary with initial values, or if a collection is provided as an argument, it manipulates that collection. And that collection is a key within a parent collection.
In my actual code I am seeing an odd behavior, where a key in the initial collection is initially $Null or has a specified value, but when I try to revise that value I do NOT get an error, but I also do not get a changed value. However, when I try creating a minimally functional example to post here, it does work correctly, both in the console and the IDE.
So, given this code
function Write-Data {
param (
[System.Collections.Specialized.OrderedDictionary]$Collection
)
foreach ($key in $Collection.Keys) {
try {
$type = $Collection.$key.GetType().FullName
} catch {
$type = 'NULL'
}
Write-Host "$key $type $($Collection.$key)"
}
Write-Host
}
function Manage-Data {
param (
[System.Collections.Specialized.OrderedDictionary]$Collection
)
if (-not $Collection) {
[System.Collections.Specialized.OrderedDictionary]$initialCollection = [Ordered]#{
initialNull = $null
initialString = 'initial string'
}
return $initialCollection
} else {
$Collection.initialNull = 'No longer null'
$Collection.initialString = 'New String'
}
}
CLS
$parentContainer = New-Object System.Collections.Specialized.OrderedDictionary
$parentContainer.Add('data', (Manage-Data))
Write-Data $parentContainer.data
Manage-Data -Collection $parentContainer.data
Write-Data $parentContainer.data
is there any obvious scenario where either of the lines revising values would not throw an error, but would also not change the value? For example, if there are actually more functions doing other things with that initialized collection object before the attempt to revise data? Or perhaps more generally, since I am depending on the default byReference behavior of complex objects, is there some situation where this behavior breaks down and I am effectively modifying a new complex object when I think I am modifying the original? Or is the fact that I am having problems with a simple data type within the complex type potentially the issue?
For what it is worth, the idea here is to basically be able to use Dependency Injection, but with functions rather than classes, and also mimic to some extent the the concept of a constructor and a method in a class, but again in a function. And that has generally been working well, if a little messy, which has reenforced in my mind that I need to move to classes eventually. But this particular issue has me worried that I will see the same problem in classes, and unless I can understand it now I will have issues. But since I can't seem to recreate the issue in a simplified example, I seem to be unable to figure anything out.
It occurs to me that one thing I haven't tried is to actually get the memory address of the collection I think I am modifying, or even of the individual key, so I can verify I actually am changing the same data that I initialized. But HOW to get the memory address of a variable escapes me, and is maybe not possible in PowerShell or .NET?
"the default byReference behavior of complex objects" concerns the properties of the object not the object itself:
The difference between (if):
[System.Collections.Specialized.OrderedDictionary]$initialCollection = [Ordered]#{
initialNull = $null
initialString = 'initial string'
}
and (else)
$Collection.initialNull = 'No longer null'
$Collection.initialString = 'New String'
Is that the later (else) statements indeed change the values of the parent values as $Collection refers to the same object as $parentContainer.data but the former (if) statement creates a new $initialCollection in the scope of the Manage-Data function which isn't visible in the parent (even if you assign it to $Collection, it would create a new object reference in the scope of the Manage-Data function).
You might return $initialCollection but then, how are you handling the different returns (either a $initialCollection or enumerable null ) in your parent function? Therefore I would just return $initialCollection for both conditions and reassign the object (where the properties are still by reference and only the $parentContainer.data reference will change/reset):
$parentContainer.data = Manage-Data -Collection $parentContainer.data
Potential problems
In other words, the potential issue in your Manage-Data function lies in the fact that parent function needs a different approach in calling it based on the condition if (-not $Collection) which is actually defined within the function. (What will be the value of this condition, as the caller already need to act differently on the condition?)
This leaves two pitfalls:
You call the function in the assumption that the argument is not a collection but it actually is:
$parentContainer = [Ordered]#{ data = [Ordered]#{} }
$parentContainer.Add('data', (Manage-Data))
In this case you get an error:
MethodInvocationException: Exception calling "Add" with "2" argument(s): "Item has already been added. Key in dictionary: 'data' Key being added: 'data'"
And the opposite (which is less obvious): you call the function in the assumption that the argument is a collection but it is actually not:
$parentContainer = [Ordered]#{}
Manage-Data $ParentContainer.Data
This will leave an unexpected object on the pipeline:
(See: PowerShell Pipeline Pollution)
Name Value
---- -----
initialNull
initialString initial string
And doesn't add anything to the $parentContainer object:
$parentContainer # doesn't return anything as it doesn't contain anything
Suggestions
See about scopes
Enable Set-StrictMode -Version Latest.
This will show you that a property is potentially empty.
Use ([ref]$MyVar).Value = 'new value' to replace a value in a parent scope.
(not related to the question) Use the IDictionary interface: [Collections.IDictionary]$Collection to accept a more general collection type in your functions.

How to define the return type / OutputType of a function

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.

How can I call a method on multiple objects in PowerShell?

I am working with an XAML GUI created in VisualStudio, and I am trying to set up some of the code for the checkboxes. Instead of doing each one separately, I was hoping to do it dynamically since the functions are all the same.
I have searched, but was only able to find a reference to an Invoke-Method function that was written.
Below is what I have so far, just to explain better what I am trying to accomplish.
The Get-Variable line comes back with a half-dozen variables that I am trying to call the same method on.
$vars = Get-Variable WPFVarA*
Foreach ( $var in $vars ) {
$var.Add_Checked.Invoke({$Global:TicketCount++})
$var.Add_Unchecked.Invoke({$Global:TicketCount--})
}
If the checkboxes are wrapped, say with a stackpanel (could be anything) you can create a routed even handler to handle all events, like this (untested):
#Bubble up event handler
[Windows.RoutedEventHandler]$Script:CheckedEventHandler = {
$Global:TicketCount++
}
[Windows.RoutedEventHandler]$Script:UncheckedEventHandler = {
$Global:TicketCount--
}
$StackPanel.AddHandler([Windows.Controls.CheckBox]::CheckedEvent, $CheckedEventHandler)
$StackPanel.AddHandler([Windows.Controls.CheckBox]::UncheckedEvent, $UncheckedEventHandler)

PowerShell AST FindAll method take a scriptblock with $args[0]

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.

Powershell cannot pass object to function

I am unable to pass an object of type [System.Messaging.Message] into a function within my script.
e.g. (outline of code)
function global:CopyQueue() {
$vTotalCountInMSMQ = $global:qSource.GetAllMessages()
foreach ($msg in $vTotalCountInMSMQ)
{
ReadAndCopyMessage $destinationQueue ([REF]$msg)
}
}
Target Function:
function global:ReadAndCopyMessage($destinationQueueName, [REF]$message)
{
$message = $message.BodyStream.Position
.etc.....
}
Unable to access properties (Property 'Position' cannot be found on this object; make sure it exists and is settable.). However, if this code is run within the CopyQueue function, everything works as expected.
I am having trouble to outsource this and process the $msg object out of the loop.
Thanks for your help in advance
similiar questions didn't work:
PowerShell pass by reference not working for me
Powershell argument passing to function seemingly not working
It appears that you shouldn't use [REF] anymore. Also, I must have made the common "," error between parameters before and thus it didn't work.
The code above works fine without [REF]
Call:
ReadAndCopyMessage $destinationQueue $msg
Function:
function global:ReadAndCopyMessage($destinationQueueName, $message)