One of the things that makes PowerShell so fun is the endless opportunities to learn new and better ways to do things. We all began learning it by trying out useful cmdlets and following tutorials to write basic functions. However, nothing sinks in as well as when we work on a project with specific goals that stretch our PowerShell knowledge.
For me, one such project has been Locksmith, which I had the pleasure of joining earlier this year. While working on an enhancement for the Locksmith PowerShell module, I had to ask myself for the first time, can I return multiple objects from a PowerShell function?
The answer is yes, you can return multiple objects from a PowerShell function, and there are multiple ways to do it.
Here is one simple example:
function Get-Food {
$TreeFruit = @("Apple","Orange","Peach")
$Squash = @("Pumpkin","Acorn Squash","Winter Squash")
$RootVegetable = @("Potato","Sweet Potato","Turnip","Radish")
Return $TreeFruit, $Squash, $RootVegetable
}
$Food = Get-Food
This function doesn’t do anything other than return three arrays, but you get the idea. You can return any kind of object, or a mix of different object types. Cool!
At this point, it’s helpful to remember that everything in PowerShell is an object. (Yes, even strings are objects!) PowerShell lets us move those objects along the pipeline, doing whatever we need to with them in the process. It’s like a powerful little shell game! 😉
…but there’s a problem! If you run this code, you’ll notice something that could be an issue: the objects that are returned in $Food
look like an ambiguous list.
Apple
Orange
Peach
Pumpkin
Acorn Squash
Winter Squash
Potato
Sweet Potato
Turnip
Radish
The array names are gone, so if you want to reference a specific type of food from the results, you have to find a creative way to get it. What if you need to enumerate the TreeFruit array, and then enumerate the RootVegetable array later in a different function?
We can try using imatch to find arrays that might contain words we know:
$TreeFruit = $Food -imatch "Apple"
$Squash = $Food -imatch "Squash"
$RootVegetable = $Food -imatch "Turnip"
This isn’t very friendly code and also depends on you knowing one of the values, which defeats the whole point to begin with.
We could try rebuilding the arrays by referencing the index of each object returned:
$TreeFruit = $Food[0]
$Squash = $Food[1]
$RootVegetable = $Food[2]
While this does work, it is still unfriendly, error-prone, and requires you to already know the order of objects being returned. And what if the objects returned aren’t arrays? They could be integers or booleans. It is a very unreliable way to manage code for any project, especially one being developed by a team.
After an embarrassingly long time, a light finally came on in my head.
💡 The value side of a hash table entry can be any kind of object! 💡
I then put the name of the object (the array, in this case) in the key and stored the actual object (arrays) in the value.
Our function can be thus written to return a hash table like this:
function Get-Food {
$TreeFruit = @("Apple","Orange","Peach")
$Squash = @("Pumpkin","Acorn Squash","Winter Squash")
$RootVegetable = @("Potato","Sweet Potato","Turnip","Radish")
@{
TreeFruit = $TreeFruit
Squash = $Squash
RootVegetable = $RootVegetable
}
}
$Results = Get-Food
This is especially useful because of the easy way you can reference the values of hash tables by their key names:
> $Results['TreeFruit']
Apple
Orange
Peach
> $Results['Squash']
Pumpkin
Acorn Squash
Winter Squash
> $Results['RootVegetable']
Potato
Sweet Potato
Turnip
Radish
Now you can get fancy and do whatever you want with those results:
Write-Output @"
Tree Fruit:
$($Results['TreeFruit'] -join ', ')
Squash:
$($Results['Squash'] -join ', ')
Root Vegetables:
$($Results['RootVegetable'] -join ', ')
"@
Which gives you this:
Tree Fruit:
Apple, Orange, Peach
Squash:
Pumpkin, Acorn Squash, Winter Squash
Root Vegetables:
Potato, Sweet Potato, Turnip, Radish
I like it! Let’s get back to Locksmith so you can see it put to practical use. My goal was to call a number of scans from a private function and then return the results of each scan back to the main function. We needed to be able to reliably reference each result as a named array for the next steps.
$Results = Invoke-Scans -Scans $Scans
The Invoke-Scans function does its work and then returns the results with the following:
# Start Condensed Example
[array]$AuditingIssues = Find-AuditingIssue -ADCSObjects $ADCSObjects
[array]$ESC1 = Find-ESC1 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers
[array]$ESC2 = Find-ESC2 -ADCSObjects $ADCSObjects -SafeUsers $SafeUsers
# End Condensed Example
# Later, at the end of the function:
# Return a hash table of array names (keys) and arrays (values)
@{
AllIssues = $AllIssues
AuditingIssues = $AuditingIssues
ESC1 = $ESC1
ESC2 = $ESC2
ESC3 = $ESC3
ESC4 = $ESC4
ESC5 = $ESC5
ESC6 = $ESC6
ESC8 = $ESC8
}
Back in the main Invoke-Locksmith function, we can now work with these results much more easily. As noted above, hash table values can be pulled by referencing their key name, like $Results['ESC1']
, which will return all findings in the $ESC1
array.
Alternatively, we can save them as new variables in the main function, making things even easier for repeated use.
$AllIssues = $Results['AllIssues']
$AuditingIssues = $Results['AuditingIssues']
$ESC1 = $Results['ESC1']
$ESC2 = $Results['ESC2']
$ESC3 = $Results['ESC3']
$ESC4 = $Results['ESC4']
$ESC5 = $Results['ESC5']
$ESC6 = $Results['ESC6']
$ESC8 = $Results['ESC8']
That’s it! That’s the tip. Return multiple named objects from a function but using a hash table to name and store them. Let me know if this helps with any of your projects, or if you have an even better way to do it!
Be sure to check out the Locksmith project if you’re interested in Active Directory, Active Directory Certificate Services, or PKI in general.
Peace.
Sam
Photo by Adrian Schwarz on Unsplash