PowerShell Array Parameters: Pass Arrays to Functions

To make a PowerShell array parameter, give the parameter an array type such as [string[]]. Callers can then pass several values separated by commas, or just one value:

function Get-ServerReport {
    param(
        [string[]]$ComputerName
    )
    foreach ($computer in $ComputerName) {
        "Checking $computer"
    }
    "Total: $($ComputerName.Count)"
}

Get-ServerReport -ComputerName WEB01, WEB02, SQL01
Get-ServerReport -ComputerName WEB01

Output:

Checking WEB01
Checking WEB02
Checking SQL01
Total: 3
Checking WEB01
Total: 1

With [string[]], a single value still arrives as an array with one item, so the same foreach loop handles both calls.

I tested every example in PowerShell 7.6 and Windows PowerShell 5.1, and they behaved the same in both. Most of this guide is about the mistakes that make array parameters misbehave.

Pass an array to a function

Use the parameter name, then the values separated by commas. Don’t call a PowerShell function like a method with parentheses. Here’s what happens when you do:

function Send-Report {
    param([string[]]$To, [int]$Days)
    "To: $($To -join '; ') | Days: $Days"
}

Send-Report -To 'maria@contoso.com', 'it@contoso.com' -Days 7
Send-Report('maria@contoso.com', 'it@contoso.com', 7)

Output:

To: maria@contoso.com; it@contoso.com | Days: 7
To: maria@contoso.com; it@contoso.com; 7 | Days: 0
PowerShell function called with parentheses passes the whole array to the first parameter
The same function called two ways (PowerShell 7)

In the second call, PowerShell sees one array, ('maria@contoso.com', 'it@contoso.com', 7), and binds all of it to -To. The number 7 ends up in the email list, and -Days is left at 0.

In PowerShell, spaces separate arguments and commas build an array. Save the parentheses-and-commas style for .NET methods like $text.Replace('a', 'b').

Accept an array from the pipeline

Add ValueFromPipeline so you can pipe values into the function. But if you forget the process block, only the last item is used:

function Test-Server {
    param(
        [Parameter(ValueFromPipeline)]
        [string[]]$ComputerName
    )
    "Checking $ComputerName"
}

'WEB01', 'WEB02', 'SQL01' | Test-Server

Output:

Checking SQL01
PowerShell ValueFromPipeline array parameter without a process block only sees the last item
Only SQL01 is checked (Windows PowerShell 5.1)

Code that isn’t in a named block runs once, after the pipeline has finished, when the parameter holds only the last value. Put the work in process, which runs once for each piped item:

function Test-Server {
    param(
        [Parameter(ValueFromPipeline)]
        [string[]]$ComputerName
    )
    process {
        foreach ($computer in $ComputerName) {
            "Checking $computer"
        }
    }
}

'WEB01', 'WEB02' | Test-Server
Test-Server -ComputerName SQL01, SQL02

Output:

Checking WEB01
Checking WEB02
Checking SQL01
Checking SQL02

The inner foreach is what makes the second call work. When you pass the array by name instead of piping it, process runs only once, with the whole array.

Validate the array

Validation attributes check every item in the array before your code runs. ValidateCount limits how many items are allowed, and ValidateSet limits which values:

function Restart-WebServer {
    param(
        [ValidateCount(1, 3)]
        [ValidateSet('WEB01', 'WEB02', 'WEB03')]
        [string[]]$ComputerName
    )
    "Would restart: $($ComputerName -join ', ')"
}

Restart-WebServer -ComputerName WEB01, WEB03
try {
    Restart-WebServer -ComputerName WEB01, SQL01
}
catch {
    $_.Exception.Message
}

Output:

Would restart: WEB01, WEB03
Cannot validate argument on parameter 'ComputerName'. The argument "SQL01" does not belong to the set "WEB01,WEB02,WEB03" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.

The error names the bad value, which is much friendlier than a script that fails halfway through a restart.

Make the array parameter mandatory

Mark it Mandatory and PowerShell prompts for the values when they’re missing, one item per line. Press Enter on an empty line to finish. Here’s what that looked like when I ran it and typed two names:

function Get-ServerReport {
    param(
        [Parameter(Mandatory)]
        [string[]]$ComputerName
    )
    "Checking $($ComputerName.Count) servers: $($ComputerName -join ', ')"
}

Get-ServerReport

Output:

cmdlet Get-ServerReport at command pipeline position 1
Supply values for the following parameters:
ComputerName[0]: WEB01
ComputerName[1]: WEB02
ComputerName[2]:
Checking 2 servers: WEB01, WEB02

This only works when someone is at the keyboard. In a scheduled task, pass the values on the command line instead.

Pass an array to a script from the command line

This catches people who call scripts from Task Scheduler or a batch file. With -File, a comma-separated list arrives as a single string. With -Command, it becomes a real array:

pwsh -NoProfile -File C:\psfaqs\report.ps1 -ComputerName WEB01,WEB02
pwsh -NoProfile -Command "& C:\psfaqs\report.ps1 -ComputerName WEB01,WEB02"

Output in PowerShell 7:

Count: 1 -> WEB01,WEB02
Count: 2 -> WEB01 | WEB02
PowerShell -File passes a comma-separated array as one string while -Command passes an array
-File vs -Command (PowerShell 7)

Windows PowerShell 5.1 printed exactly the same with powershell.exe. If you must use -File, split the value inside the script, for example with $ComputerName = $ComputerName -split ','.

For more on script arguments, see how to run a PowerShell script with parameters.

Can a function change the array you pass in?

Partly. The function gets the same array, so changing an item is visible to the caller. Adding with += builds a new array that the caller never sees:

function Update-List {
    param($List)
    $List[0] = 'DC01'
    $List += 'NEW01'
    "Inside: $($List -join ', ')"
}

$servers = 'WEB01', 'WEB02'
Update-List -List $servers
"Outside: $($servers -join ', ')"

Output:

Inside: DC01, WEB02, NEW01
Outside: DC01, WEB02

If a function needs to add items, return the new array and assign it, or pass a List. Appending to arrays in PowerShell shows both ways.

Return an array from a function

PowerShell unrolls arrays when a function outputs them. That means a one-item array comes back as a plain value:

function Get-SiteServers {
    param([string]$Site)
    if ($Site -eq 'Austin') { @('WEB01') } else { @('WEB01', 'WEB02') }
}

$austin = Get-SiteServers -Site Austin
$dallas = Get-SiteServers -Site Dallas
"Austin: $($austin.GetType().Name)"
"Dallas: $($dallas.GetType().Name)"

$wrapped = @(Get-SiteServers -Site Austin)
"Austin in @(): $($wrapped.GetType().Name)"

Output:

Austin: String
Dallas: Object[]
Austin in @(): Object[]

If you then use $austin[0], you get the first letter, W, instead of WEB01. Wrap the call in @() and you always get an array.

Microsoft documents every parameter attribute in about_Functions_Advanced_Parameters.

Frequently Asked Questions

How do I pass an array to a PowerShell function?

Call it with the parameter name and comma-separated values, like Get-Report -ComputerName WEB01, WEB02. Don’t use parentheses, which send everything to the first parameter.

How do I declare an array parameter in PowerShell?

Give it an array type in the param() block, such as [string[]]$ComputerName or [int[]]$Id. A single value is accepted as an array of one.

Why does my pipeline function only process the last item?

The code isn’t in a process block. Put it in process { } so it runs once for each piped item.

How do I pass an array to a script with pwsh -File?

You can’t directly: -File passes a,b as one string. Use -Command instead, or split the string inside the script with -split ','.

Why does my function return a string instead of an array?

PowerShell unrolls a one-item array when a function outputs it. Wrap the call in @(), like $servers = @(Get-Servers), to always get an array.

If you work with arrays in functions, these help too: