Tricks I wish I knew before I started - Arrays
The trick to foreach and PowerShell overall is knowing that if there's nothing on the right hand side (no assignment to the variable) it gets pushed out further down the script.
# Some input data - simplified for example purposes
$SomeData = @('be', 'me', 'one', 'more', 'time')
# assignement of values to array in foreach with slight change to value.
# not optimized approach
$MyOtherArray = @()
foreach ($Something in $SomeData) {
$MyOtherArray += "MyValue $Something"
}
# better approach
$MyOtherArray1 = foreach ($Something in $SomeData) {
"MyValue $Something"
}
Do you see what I did there? Since I've not done any assignment and just left value as is, it will get pushed down to $MyOtherArray1 and populate it in one go. We can use a similar approach to functions. When we define a function and just leave foreach loop in it without any assignments the value will be returned from a function just like you would first assign it to Array and then return that Array.
function Show-FirstExample {
param(
[string[]] $Test
)
foreach ($my in $Test) {
$my
}
}
In old days I would do something like this (which I now consider bad practice – and by bad practice, I mean using $Value += assignment)
function Show-FirstExample {
param(
[string[]] $Test
)
$Value = @()
foreach ($my in $Test) {
$Value += $my
}
return $Value
}
While I have your attention to the first example there's one more thing that's worth mentioning here. Let's take a look at following scenario:
Write-Color 'Example 1' -Color Cyan
$Value1 = Show-FirstExample -Test 'one', 'two'
$Value1 -is [array]
$Value2 = Show-FirstExample -Test 'one'
$Value2 -is [array]
Same function, different input, different output.
As you can see above the same function is returning an Array when two strings are passed to function, but only returns a string when one string is passed. While in general, you may say it's not a big deal, sometimes you expect Array and not having it can lead to weird results that you have to deal with.
function Show-SecondExample {
param(
[string[]] $Test
)
[Array] $Output = foreach ($my in $Test) {
$my
}
# I want to do something with value before returning
if ($Output -is [array]) {
Write-Color 'Array' -Color Green
}
# Actually returning
$Output
}
Write-Color 'Example 2' -Color Cyan
$Value1 = Show-SecondExample -Test 'one', 'two'
$Value1 -is [array]
$Value2 = Show-SecondExample -Test 'one'
$Value2 -is [array]
While you may be thinking that explicitly telling foreach loop to assign a value to Array solves this issue you're wrong. As you can see in the example above (and results below) I'm verifying that $Output variable is indeed Array before returning it from a function and it reports success, while at the same time, after I verify the output of a function it's no longer array.
The solution to this is using a comma before variable you want to output. It tells PowerShell that you want to keep value type. The comma is a specific type of a prefix unary operator.
As a binary operator, the comma creates an array. As a unary operator, the comma creates an array with one member. Place the comma before the member.
PowerShell is still doing its flattening work thou, but it results in returning your original object intact. If you need to more about operators in PowerShell Microsoft has excellent documentation on that topic. Please be aware using comma also has consequences. Have a read PowerShell – Returning one object from a function as an Array. It discusses some issues with a unary operator that you should address if you're going to use it.
function Show-ThirdExample {
param(
[string[]] $Test
)
[Array] $Output = foreach ($my in $Test) {
$my
}
# I want to do something with value before returning
if ($Output -is [array]) {
Write-Color 'Array' -Color Green
}
# Actually returning
, $Output
}
Write-Color 'Example 3' -Color Cyan
$Value1 = Show-ThirdExample -Test 'one', 'two'
$Value1 -is [array]
$Value2 = Show-ThirdExample -Test 'one'
$Value2 -is [array]
Of course, for this to work, you have to set type for $Output variable explicitly. Otherwise, with just one value it would be a string. In another example (below), I'm getting an output of two loops, put them into Arrays and output, as one array. But doing the addition part is most likely creating new Array for the output which is both resource intensive and something you need to remember in the end. You also have to deal with two variables, and if you've more loops in function, this may get quickly out of hand.
function Show-FourthExample {
param(
[string[]] $Test
)
[Array] $Output1 = foreach ($my in $Test) {
$my
}
[Array] $Output2 = foreach ($my in $Test) {
$my
}
# I want to do something with value before returning
if (($Output1 + $Output2) -is [array]) {
Write-Color 'Array' -Color Green
}
# Actually returning
, ($Output1 + $Output2)
}
Write-Color 'Example 4' -Color Cyan
$Value1 = Show-FourthExample -Test 'one', 'two'
$Value1 -is [array]
$Value2 = Show-FourthExample -Test 'one'
$Value2 -is [array]
Before giving you a solution to this one I want to show you two more examples that are very common in PowerShell.
function Show-FifthExample {
param(
[string[]] $Test
)
if ($Test -contains 'one') {
'one'
}
if ($Test -contains 'two') {
'two'
}
}
Write-Color 'Example 5' -Color Cyan
$Value1 = Show-FifthExample -Test 'one', 'two'
$Value1 -is [array]
$Value2 = Show-FifthExample -Test 'one'
$Value2 -is [array]
As you can see above I'm using what I've learned earlier and I don't assign anything in if/else scenario, and I'm putting variables out in the open to be returned from a function. But the problem is I wanted (expected) an Array, even if there is just one element in it. So now I would go for my usual bad practice and define an Array at the top, and simply assign values during if/else scenario.
function Show-FifthExampleComplicated {
param(
[string[]] $Test
)
$Value = @()
if ($Test -contains 'one') {
$Value += 'one'
}
if ($Test -contains 'two') {
$Value += 'two'
}
# Do something with $Value
if ($Value.Count -eq 2) {
Write-Color 'Happy ', 'Birthday' -Color Red, Yellow
}
, $Value
}
Write-Color 'Example 5 Complicated' -Color Cyan
$Value1 = Show-FifthExampleComplicated -Test 'one', 'two'
$Value1 -is [array]
$Value2 = Show-FifthExampleComplicated -Test 'one'
$Value2 -is [array]
What I didn't know till recently, is that you can something like this:
function Show-SixthExample {
param(
[string[]] $Test
)
$Output = @(
if ($Test -contains 'one') {
'one'
}
if ($Test -contains 'two') {
'two'
}
)
, $Output
}
Write-Color 'Example 6' -Color Cyan
$Value1 = Show-SixthExample -Test 'one', 'two'
$Value1 -is [array]
$Value2 = Show-SixthExample -Test 'one'
$Value2 -is [array]
You open an Array at the top and you just close it in the bottom. Anything that's output from if/else, or even foreach loops can be output into this Array for easy return or other data processing. While I constantly mention foreach loops following tricks also apply to While/Do loops.
function Show-SeventhExample {
param(
[string[]] $Test
)
$Output = @(
if ($Test -contains 'one') {
'one'
}
if ($Test -contains 'two') {
'two'
}
foreach ($T in $Test) {
"modified$T"
}
$i = 1
do {
$i++
'one'
} while ($i -le 5)
)
# Now we can process whole output with just one assingment
if ($Output.Count -eq 4) {
Write-Color 'Hurray' -Color Red
}
, $Output
}
Write-Color 'Example 7' -Color Cyan
$Value1 = Show-SeventhExample -Test 'one', 'two'
$Value1 -is [array]
$Value1.Count
Write-Color 'Example 7', ' - ', 'Value 1 output in one line' -Color DarkMagenta, White, Green
$Value1 -join ','
$Value2 = Show-SeventhExample -Test 'one'
$Value2 -is [array]
$Value2.Count
Write-Color 'Example 7', ' - ', 'Value 2 output in one line' -Color DarkMagenta, White, Yellow
$Value2 -join ','