powershell, function calls fomr an array - arrays

So I am curious how to call a function from an array?
Function example:
function test($a, $b)
{
write-host $a
write-host $b
}
An array
$testArray = #{"test";"testA";"testB"}
And the whole bit I want to work is
$testArray[0] $testArray[1] $testArray[2]
Essentially mimic this
test "testA" "testB"
Reason for this is I have a few arrays like this and a loop that would go through each one using the custom function call on the data in each array.
Basically trying a different programing style.

Ok, it sounds like you have an array of arrays to be honest, so we'll go with that. Then let's reference this SO question which very closely resembles your question, and from that take away the whole [scriptblock]::create() thing, and splatting arrays. From that we can come up with this script:
function test($a, $b)
{
Write-Host "Function 'test'"
write-host $a
write-host $b
}
function test2($a, $b)
{
Write-Host "Function 'test2'"
write-host $b
write-host $a
}
$TestArray = #() #the correct way to create an array, instead of a broken HashTable
$testArray = #("test","testA","testB"),#("test2","testC","testD")
ForEach($Test in $TestArray){
$script = [scriptblock]::Create($test[0]+" #Args")
$script.Invoke($test[1..$test.count])
}
If all you have is one array, and not an array of arrays then I guess this is pretty simple. You could do:
$testArray = #("test","testA","testB")
$script = [scriptblock]::Create($testArray[0]+" #Args")
$script.Invoke($testArray[1..$testArray.count])
Edit (Capturing): Ok, to capture the results of a function you should be able to prefix it with $Variable = and be good to go, such as:
$MyResults = $script.Invoke($testArray[1..$testArray.count])
That will capture any output given by the function. Now, since the functions we have been working with only perform Write-Host they don't actually output anything at all, they just print text to the screen. For this I would modify the function a bit to get real output that's usable. In this example the function takes 2 parameters as input, and creates a new object with those 2 parameters assigned to it as properties. That object is the output.
function test($a, $b)
{
New-Object PSObject -Property #{Value1=$a;Value2=$b}
}
$testArray = #("test","testA","testB")
$script = [scriptblock]::Create($testArray[0]+" #Args")
$MyResults = $script.Invoke($testArray[1..$testArray.count])
Now if you ran that you would end up with the variable $MyResults being a PSCustomObject that has 2 properties named Value1 and Value2. Value1 would contain the string "testA" and Value2 would contain the string "testB". Any pair of strings passed to that function would output 1 object with 2 properties, Value1 and Value2. So after that you could call $MyResults.Value1 and it would return testA.

Related

values from a foreach loop in a function into an array

I have a function that replaces PackageID in a SCCM task sequence, I would like to capture all those package IDs into a variable, so I would be able to create a report based on that.
The problem is that I already have a foreach loop doing the work, and I can't figure out how to not overwrite the values.
$Driver.PackageID comes from a foreach loop based on $Drivers, which contains
If I run the code I get this as I have Write-Output defined:
Updated code:
function Set-Drivers{
foreach ($Driver in $Drivers) {
Write-Output "Driver Name: $($Driver.Name)"
Write-Output "DriverPackageID: $($Driver.PackageID)"
}
}
$array = #()
$array = Set-Drivers
$hash = [ordered]#{
'DriverName' = $Driver.Name
'DriverID' = $Driver.PackageID
}
$array += New-Object -Typename PSObject -Property $hash
Can someone explain, why I only get the first result in my $array? I can see the values are being overwritten if I run it in debug mode.
Your code is not iterating over the results, but instead only using one of them. This what you intended.
$array = $drivers | foreach {
[ordered]#{
DriverName = $_.Name
DriverID = $_.PackageID
}
}
Your function doesn't return anything. It only writes lines to the console. Then after the function is finished, you create a single object and add that to your array.
Try something like
function Set-Drivers{
$result = foreach ($Driver in $Drivers) {
[PsCustomObject]#{
'DriverName' = $Driver.Name
'DriverID' = $Driver.PackageID
}
}
# output the result
# the comma wraps the result in a single element array, even if it has only one element.
# PowerShell 'flattens' that upon return from the function, leaving the actual resulting array.
,$result
}
$array = Set-Drivers
# show what you've got
$array

Adding element to array in powershell scriptblock converts array to string

I noticed odd behaviour using arrays in scriptblocks. The following code shows the problem:
$array = #("x", "y")
Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"
$bad = {
$array += "z"
Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"
$array
}
$good = {
$array = $array.Clone()
$array += "z"
Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"
$array
}
& $good
& $bad
Executing the script will produce the following output:
Object[]
array
Object[]
array
x
y
z
String
System.Object
z
The scriptblock $bad does not work as I would expect. It converts the array to string, but it should simply add the element z to the array. If there is no element added, the array can be used as expected.
I noticed this behaviour in powershell 5.0 and 5.1 but not in the ISE. Is it a bug or can anyone explain this?
It's a scope issue. The variable on the left side of the assignment operation in the scriptblocks is defined in the local scope.
This statement
$array = $array.Clone()
clones the value of the global variable $array and assigns it to the local variable $array (same name, but different variable due to different scope). The local variable $array then contains a copy of the original array, so the next statement
$array += "z"
appends a new element to that array.
In your other scriptblock you immediately append a string to the (local) variable $array. In that context the local variable is empty, so $array += "z" has the same effect as $array = "z", leaving you with a variable containing just the string "z".
Specify the correct scope and you'll get the behavior you expect:
$array = #("x", "y")
$not_bad = {
$script:array += "z"
Write-Host "$($script:array.GetType().Name)"
Write-Host "$($script:array.GetType().BaseType)"
$script:array
}
& $not_bad
Beware, however, that this will actually modify the original array in the global/script scope (your $good example leaves the original array unchanged).
I'm not sure if I would consider this behavior a bug, but it's definitely a gotcha.
I would like to post my preferred solution which bases on Ansgars explanation:
$array = #("x", "y")
$not_bad = {
$array = $array + "z"
Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"
$array
}
& $not_bad
Important is the assignment to the local variable (or better to create a local variable) before adding further elements. A simple
$array = $array
would do, but this line may be confusing.

Powershell: multidimensional array changes return value from "system.object[]"

I wondered if anyone could shed some light on the issue I am facing when returning values from a multidimensional array through a function:
$ArrayList = #()
function MultiDimensionalArrayTest
{
param(
[array]$ArrayList
)
for($i = 0; $i -lt 1; $i++)
{
$ArrayModify += ,#("Test", "Test")
}
return $ArrayModify
}
$ArrayModify = MultiDimensionalArrayTest
foreach ($item in $ArrayModify)
{
Write-Host $item[0]
}
When the loop is executed once the values returned are:
T
T
However if the for statement is lopped twice, the values returned are:
Test
Test
My aim is to retrieve x amount of values "Test" from the Write-Host $item[0] regardless of how many times the statement is executed
It appears that if two or more rows are captured and returned in the $ArrayModify array, the value is a system.object[], however if looped once, the value "Test, Test" is captured and when Write-Host $item[0] is executed, it will print T.
Any advice would be greatly appreciated.
Not the cleanest way of dealing with it but you need to prevent PowerShell from unrolling the single element array into an array with two elements.
function MultiDimensionalArrayTest{
$ArrayModify = #()
for($i = 0; $i -lt 1; $i++){
$ArrayModify += ,#("Test$i", "Test$i$i")
}
,#($ArrayModify)
}
Using the above function will get the desired output I believe. ,#($ArrayModify) ensures that the array is returned and not unrolled into its elements as you saw above.
$ArrayList = #()
$ArrayList = MultiDimensionalArrayTest
foreach ($item in $ArrayList){$item[0]}
Giving the output for $i -lt 1 in the loop
Test0
Giving the output for $i -lt 2 in the loop
Test0
Test1
Your Output
Concerning your output from your example with $i -lt 1 PowerShell is unrolling the array into a single dimension array with 2 elements "Test" and "Test". You are seeing the letter "T" since all strings support array indexing and will return the character from the requested position.
Other issues with code
Not to beat a dead horse but really look at the other answers and comments as they provide some tips as to some coding errors and anomalies of the code you presented in the question.
First of all I notice several mistakes in your code.
This line $ArrayList = #() is useless since you don't use the $ArrayList variable afterwards.
Regarding the MultiDimensionalArrayTest function, you declared an $ArrayList argument but you never use it in the function.
Finally when this line makes no sense $ArrayModify = MultiDimensionalArrayTest = $item, you probably meant $ArrayModify = MultiDimensionalArrayTest.
This is not a complete answer to your question, since I am not sure what you are trying to achieve, but take a look at this line:
$ArrayModify = MultiDimensionalArrayTest = $item
This makes no sense, since you are calling the function MultiDimensionalArrayTest and passing two arguments to it, which are "=" (powershell assumes this is a string) and $item (null object). Then you assign whatever is returned to $ArrayModify.
The reason "T" is outputted, is because you are outputting the first element of what is at the moment a string. "Test" is outputted when $item is an array of strings.

Powershell array values being overwritten

I'm at a bit of a loss to understand why this code isn't doing what I think it should.
This is part of a larger plan but in essence I'm attempting to fill part of an array with entries from a text file, though I've replaced that bit with a hard coded array. The end result is the same.
$users = 12345,23456,34567,45678,56789,67890
$a = New-Object PSCustomObject
$a = $a|Select ID
$collection = #()
$users|%{
$a.ID = $_
$collection += $a
}
$collection|ft -a
This outputs the following:
ID
--
67890
67890
67890
67890
67890
67890
If you output the array to screen as it gets built, you can watch the values get replaced each time with the most recent entry.
What's the fault? Is there something unusual with the way I'm initialising the $a variable or the array?
It's doing that because there is only one object ($a), and all your loop is doing is changing the value of the one property (ID), and adding another reference to it to the array.
You need to create a new object for each cycle of the loop:
$users = 12345,23456,34567,45678,56789,67890
$a = New-Object PSCustomObject
$a = $a|Select ID
$collection = #()
$users|%{
$a = New-Object PSCustomObject
$a = $a|Select ID
$a.ID = $_
$collection += $a
}
$collection|ft -a
PS is adding a reference to the object itself into the array, rather than the object contents at the time it's added. Changing the value of $a.ID at any point changes the contents displayed by $collection.
To fix this, you can move the initialization for $a within the % statement like so:
$users = 12345,23456,34567,45678,56789,67890
$collection = #()
$users|%{
$a = New-Object PSCustomObject
$a = $a|Select ID
$a.ID = $_
$collection += $a
}
$collection|ft -a
or just simply add $a.ID to $collection if you only want the ID value in that array.
Not quite sure what you're trying to accomplish here, really... but what if you try it like this?
$collection += $a.ID
You create one object ($a). You then add this one object to $collection multiple times. Each time you add it you change its .ID property value. So at the end you have $collection holding many copies of this one object. So you get many copies of that one ID value. Create a new object each time inside the loop if you want multiple objects with different values.

PowerShell array unshift

After reading this helpful article on the Windows PowerShell Blog, I realized I could "shift" off the first part of an array, but didn't know how to "unshift" or push elements onto the front of an array in PowerShell.
I am creating an array of hash objects, with the last read item pushed onto the array first. I'm wondering if there is a better way to accomplish this.
## Create a list of files for this collection, pushing item on top of all other items
if ($csvfiles[$collname]) {
$csvfiles[$collname] = #{ repdate = $date; fileobj = $csv }, $csvfiles[$collname] | %{$_}
}
else {
$csvfiles[$collname] = #{ repdate = $date; fileobj = $csv }
}
A couple of things to note:
I need to unroll the previous array element with a foreach loop %{$_}, because merely referencing the array would create a nested array structure. I have to have all the elements at the same level.
I need to differentiate between an empty array and one that contains elements. If I attempt to unroll an empty array, it places a $null element at the end of the array.
Thoughts?
PS: The reason the empty hash element produces a NULL value is that $null is treated as a scalar in PowerShell. For details, see https://connect.microsoft.com/PowerShell/feedback/details/281908/foreach-should-not-execute-the-loop-body-for-a-scalar-value-of-null.
ANSWER:
Looks like the best solution is to pre-create the empty array when necessary, rather than code around the $null issue. Here's the rewrite using a .NET ArrayList and a native PoSh array.
if (!$csvfiles.ContainsKey($collname)) {
$csvfiles[$collname] = [System.Collections.ArrayList]#()
}
$csvfiles[$collname].insert(0, #{ repdate = $repdate; fileobj = $csv })
## NATIVE POSH SOLUTION...
if (!$csvfiles.ContainsKey($collname)) {
$csvfiles[$collname] = #()
}
$csvfiles[$collname] = #{ repdate = $repdate; fileobj = $csv }, `
$csvfiles[$collname] | %{$_}
You might want to use ArrayList objects instead, as they support insertions at arbitrary locations. For example:
# C:\> $a = [System.Collections.ArrayList]#(1,2,3,4)
# C:\> $a.insert(0,6)
# C:\> $a
6
1
2
3
4
You can simply use a plus operator:
$array = #('bar', 'baz')
$array = #('foo') + $array
Note: this re-creates actually creates a new array instead of changing the existing one (but the $head, $tail = $array way of shifting you refer to works extactly in the same way).

Resources