Pass a PowerShell array to a .NET function - powershell

I want to get some 10 values of type short from a .NET function.
In C# it works like this:
Int16[] values = new Int16[10];
Control1.ReadValues(values);
The C# syntax is ReadValues(short[] values).
I tried something like this:
$Control1.ReadValues([array][int16]$Result)
But there are only zeroes in the array.

In the comments you mention:
I believe that the C# function have a ref
So, the method signature is really:
ReadValues(ref short[] values)
Luckily, PowerShell has a [ref] type accelerator for this sort of situation
# Start by creating an array of Int16, length 10
$Result = [int16[]]#( ,0 * 10 )
# Pass the variable reference with the [ref] keyword
$Control1.ReadValues([ref]$Result)
For more inforation, see the about_Ref help file

Related

Returning a ArrayList from a function in powershell contains indexes [duplicate]

This question already has answers here:
Powershell Join-Path showing 2 dirs in result instead of 1 - accidental script/function output
(1 answer)
Why does Range.BorderAround emit "True" to the console?
(1 answer)
Create a Single-Element Json Array Object Using PowerShell
(2 answers)
Closed 1 year ago.
I am new to PowerShell and there is a weird behavior I cannot explain. I call a function that returns a [System.Collections.ArrayList] but when I print my variable that receives the content of the array, if I have one value(for example: logXXX_20210222_075234355.txt), then I get 0 logXXX_20210222_075234355.txt. The value 0 gets added for some reason as if it has the index of the value.
If I have 4 values, it will look like this:
0 1 2 3 logXXX_20210222_075234315.txt logXXX_20210225_090407364.txt
logXXX_20210204_120318221.txt logXXX_20210129_122737751.txt
Can anyone help?
Here is a simple code that does that:
function returnAnArray{
$arrayToReturn =[System.Collections.ArrayList]::new()
$arrayToReturn.Add('logICM_20210222_075234315.txt')
return $arrayToReturn
}
$fileNames = returnAnArray
Write-Host $fileNames
0 logICM_20210222_075234315.txt
It's characteristic of the ArrayList class to output the index on .Add(...). However, PowerShell returns all output, which will cause it to intermingle the index numbers with the true or other intended output.
My favorite solution is to simply cast the the output from the .Add(...) method to [Void]:
function returnAnArray{
$arrayToReturn = [System.Collections.ArrayList]::new()
[Void]$arrayToReturn.Add('logICM_20210222_075234315.txt')
return $arrayToReturn
}
You can also use Out-Null for this purpose but in many cases it doesn't perform as well.
Another method is to assign it to $null like:
function returnAnArray{
$arrayToReturn = [System.Collections.ArrayList]::new()
$null = $arrayToReturn.Add('logICM_20210222_075234315.txt')
return $arrayToReturn
}
In some cases this can be marginally faster. However, I prefer the [Void] syntax and haven't observed whatever minor performance differential there may be.
Note: $null = ... works in all cases, while there are some cases where [Void] will not; See this answer (thanks again mklement0) for more information.
An aside, you can use casting to establish the list:
$arrayToReturn = [System.Collections.ArrayList]#()
Update Incorporating Important Comments from #mklement0:
return $arrayToReturn may not behave as intended. PowerShell's output behavior is to enumerate (stream) arrays down the pipeline. In such cases a 1 element array will end up returning a scalar. A multi-element array will return a typical object array [Object[]], not [Collection.ArrayList] as seems to be the intention.
The comma operator can be used to guarantee the return type by making the ArrayList the first element of another array. See this answer for more information.
Example without ,:
Function Return-ArrayList { [Collections.ArrayList]#(1,2,3,4,5,6) }
$ArrReturn = Return-ArrayList
$ArrReturn.gettype().FullName
Returns: System.Object[]
Example with ,:
Function Return-ArrayList { , [Collections.ArrayList]#(1,2,3,4,5,6) }
$ArrReturn = Return-ArrayList
$ArrReturn.gettype().FullName
Returns: System.Collections.ArrayList
Of course, this can also be handled by the calling code. Most commonly by wrapping the call in an array subexpression #(...). a call like: $filenames = #(returnAnArray) will force $filenames to be a typical object array ([Object[]]). Casting like $filenames = [Collections.ArrayList]#(returnArray) will make it an ArrayList.
For the latter approach, I always question if it's really needed. The typical use case for an ArrayList is to work around poor performance associated with using += to increment arrays. Often this can be accomplished by allowing PowerShell to return the array for you (see below). But, even if you're forced to use it inside the function, it doesn't mean you need it elsewhere in the code.
For Example:
$array = 1..10 | ForEach-Object{ $_ }
Is preferred over:
$array = [Collections.ArrayList]#()
1..10 | ForEach-Object{ [Void]$array.Add( $_ ) }
Persisting the ArrayList type beyond the function and through to the caller should be based on a persistent need. For example, if there's a need easily add/remove elements further along in the program.
Still More Information:
Notice the Return statement isn't needed either. This very much ties back to why you were getting extra output. Anything a function outputs is returned to the caller. Return isn't explicitly needed for this case. More commonly, Return can be used to exit a function at desired points...
A function like:
Function Demo-Return {
1
return
2
}
This will return 1 but not 2 because Return exited the function beforehand. However, if the function were:
Function Demo-Return
{
1
return 2
}
This returns 1, 2.
However, that's equivalent to Return 1,2 OR just 1,2 without Return
Update based on comments from #zett42:
You could avoid the ArrayList behavior altogether by using a different collection type. Most commonly a generic list, [Collections.Generic.List[object]]. Technically [ArrayList] is deprecated already making generic lists a better option. Furthermore, the .Add() method doesn't output anything, thus you do not need [Void] or any other nullification method. Generic lists are slightly faster than ArrayLists, and saving the nullification operation a further, albeit still small performance advantage.
ArrayList appears to store alternating indexes and values:
PS /home/alistair> $filenames[0]
0
PS /home/alistair> $filenames[1]
logICM_20210222_075234315.txt

Update a value in a array of objects

If I have an array of objects like
$Results = #(
[PSCustomObject]#{
Email = $_.email
Type = $Type
}
# ...
)
and I want to update the 100th $results.type value from X to y. I thought this code would be
$results.type[100] = 'Hello'
But this is not working and I am not sure why?
$Results.Type[100] = 'Hello' doesn't work because $Results.Type isn't real!
$Results is an array. It contains a number of objects, each of which have a Type property. The array itself also has a number of properties, like its Length.
When PowerShell sees the . member invocation operator in the expression $Results.Type, it first attempts to resolve any properties of the array itself, but since the [array] type doesn't have a Type property, it instead tries to enumerate the items contained in the array, and invoke the Type member on each of them. This feature is known as member enumeration, and produces a new one-off array containing the values enumerated.
Change the expression to:
$Results[100].Type = 'Hello'
Here, instead, we reference the 101st item contained in the $Results array (which is real, it already exists), and overwrite the value of the Type property of that object.
#MathiasR.Jessen answered this. The way I was indexing the array was wrong.
the correct way is $results[100].type = 'Hello'

Multidimensional arrays with only ONE entry

I wrote a generic function to call a stored procedure. I tried to use a multidimensional array to pass the parameters. Now it is possible, that the procedure only takes one parameter, so my multidimensional array has also only one parameter. But the lenght of such an array is 2!
$MyParameters = ("param1_name", "param1_value")
$MyParameters.Length returns 2!! Strange, why? It should return 1
$MyParameters returns correctly:
param1_name
param1_value
If I write:
$MyParameters = ("param1_name", "param1_value"), ("param2_name", "param2_value")
$MyParameters.Length returns also 2 which is correct. $MyParameters returns correctly all four elements:
param1_name
param1_value
param2_name
param2_value
Any reasons for that? Am I missing something?
What you are trying to do is creating an array of multi value object.
Here is an example to solve that issue:
$x = ,("param1","param2","param3")
x.Lenght
Will return 1 which is correct for your issue.
$x = ,("param1","param2","param3"),("param1","param2","param3")
x.Lenght
Will return 2
$x = ,("param1","param2","param3"),("param1","param2","param3")
x[0].Lenght
will return 1, thats because $x[0] is array with one element.
In addition, If you would like to create an Array of Arrays this is the way to do it:
$x = #("param1","param2","param3"),#("param1","param2","param3")
$x.Lenght
#2
$x[0].Lenght
#3

Storing datatypes and embedding them in other datatypes

I can store a data type in a variable like this
$type = [int]
and use it like this:
$type.GetType().Name
but, how can I embed it in another declaration? e.g.
[Func[$type]]
* Update I *
so the invoke-expression will do the trick (thanks Mike z). what I was trying to do is create a lambda expression. this is how I can do it now:
$exp = [System.Linq.Expressions.Expression]
$IR = [Neo4jClient.Cypher.ICypherResultItem]
Invoke-Expression "`$FuncType = [Func[$IR]]"
$ret = $exp::Lambda($FuncType, ...)
but also thanks to #PetSerAl and #Jan for interesting alternatives
This does not appear to be possible directly, at least according to the PowerShell 3.0 specification.
The [type] syntax is called a type-literal by the spec and its definition does not included any parts that can be expressions. It is composed of type-names which are composed of type-characters but there is nothing that is dynamic about them.
Reading through the spec, I noticed that something like this however works:
$type = [int]
$try = Read-Host
$type::"$(if ($try) { 'Try' } else { '' })Parse"
Now you might wonder why $type::$variable is allowed. That is because :: is an operator who's left hand side is an expression that must evaluate to a type. The right hand side is a member-name which allows simple names, string literals, and use of the subexpression operator.
However PowerShell is extremely resilient and you can do almost anything dynamicly via Invoke-Expression. Let's say you want to declare variable that is a generic delegate based on a type you know only at runtime:
$type = [int] # This could come from somewhere else entirely
Invoke-Expression "`$f = [Func[$type]]{ return 1 }"
Now $f has your delegate. You will need to test this out if $type is some complex nested or generic type but it should work for most basic types. I tested with [int] and [System.Collections.Generic.List[int]] it worked fine for both.
It can be achieved by reflection:
$type = [int]
$Func = [Func``1] # you have to use mangled name to get generic type definition.
$Func.MakeGenericType($type)
Unfortunately, I don't thing you can do this. Have a look at this question Is possible to cast a variable to a type stored in another variable?.
There is a suggestion that a conversion is possible using Convert.ChangeType method on objects that implement IConvertible, but as far as I can tell this is not implemented in PowerShell.
You can fake it a little bit, by using your stored type in a scriptblock, but this may not be what you are after.
$type = [byte]
$code = [scriptblock]::create("[$type]`$script:var = 10")
& $code
$var.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Byte System.ValueType

Strings Expansion is changing order or the string

I'm trying to so some normal variable expansion in a string and, when it's in a function, it comes out out-of-order.
function MakeMessage99($startValue, $endValue) { "Ranges from $startValue to $endValue" }
MakeMessage99(1, 100)
This returns Ranges from 1 100 to then it should return Ranges from 1 to 100
Functions in powershell shouldn't use parenthesis to enclose parameters. Instead:
PS C:\> MakeMessage99 1 100
Ranges from 1 to 100
Where MakeMessage is your function, "1" is a parameter in the first position, and "100" is a parameter in the second position. According to about_Functions_Advanced_Parameters:
By default, all function parameters are positional. Windows PowerShell assigns position numbers to parameters in the order in which the parameters are declared in the function.
Powershell has several ways to check input going in. You could cast the input as a numeric type. There are also baked-in validation methods for parameters that may prevent this sort of error in the future. If you really want an integer, a simple cast would cause an array to be invalid input. For example:
function MakeMessage99 {
Param
(
[int]$startValue,
[int]$endValue
)
"Ranges from $startValue to $endValue"
}
You could also explore range validation (such as [ValidateRange(0,100)]), pattern validation (such as [ValidatePattern("[0-9][0-9][0-9][0-9]")] to validate a four-digit number) or other validation attributes listed in the link above.
This is a common pitfall in PowerShell. When you invoke...
MakeMessage99(1, 100)
...you're actually passing an array containing the values 1 and 100 as the first parameter. To pass 1 as the first parameter and 100 as the second parameter, use...
MakeMessage99 1 100