When a PowerShell script executes from a terminal, any parameters or flags passed to it can be processed dynamically using looping constructs. If a script does not declare a formal parameter block, PowerShell automatically captures all incoming command-line values into the $args automatic array, allowing scripts to iterate through an arbitrary number of inputs.
The $args Automatic Array
The $args variable is a built-in, zero-indexed array (System.Object[]) containing all arguments passed to a script, function, or script block that were not bound to named parameters. It behaves like a standard array, so you can loop through it, index into it, or check its .Count.
Like any standard PowerShell array, $args exposes properties and indexing capabilities:
$args.Count: Returns the total number of arguments passed.$args[0]: Accesses the first positional argument.$args[-1]: Accesses the last positional argument.
Write-Output "Total arguments received: $($args.Count)"
if ($args.Count -gt 0) {
Write-Output "First argument: $($args[0])"
Write-Output "Last argument: $($args[-1])"
}where:
$args.Countevaluates the length of the arguments array.$($args[0])uses the subexpression operator$()to evaluate the indexed array element inside a double-quoted string.
Invoke the script with three space-separated arguments:
pwsh ./inspect-args.ps1 "web-prod-01" "sql-prod-01" "ad-auth-01"Total arguments received: 3
First argument: web-prod-01
Last argument: ad-auth-01foreach ($arg in $args) {
Write-Output "Argument: $arg"
}./args-basic.ps1 "apple" "banana" "cherry"Argument: apple
Argument: banana
Argument: cherrywhere:
- Each space-separated value passed after the script name becomes an element in the
$argsarray. $args[0]would retrieve"apple",$args[1]would retrieve"banana", and so on.
Looping Through Arguments
You can iterate through $args using either the index-driven for loop or the collection-driven foreach statement. Use $args with a loop to process an unknown number of inputs, which is especially helpful for scripts meant to handle a flexible list of files, names, or values.
Method 1: Classic for Loop (Index Tracking)
Use the classic for loop when you need to know the explicit argument position or skip arguments in fixed increments:
for ($i = 0; $i -lt $args.Count; $i++) {
Write-Output "Argument [$i] = $($args[$i])"
}where:
$i = 0initializes the counter at index 0.$i -lt $args.Countruns until all elements have been visited.$args[$i]accesses each argument by its numeric position.
pwsh ./loop-args-for.ps1 Alpha Beta GammaArgument [0] = Alpha
Argument [1] = Beta
Argument [2] = GammaMethod 2: foreach Statement (Sequential Processing)
Use the foreach statement when you want to process each passed argument sequentially without tracking numeric indices:
foreach ($argument in $args) {
Write-Output "Processing parameter value: $argument"
}where $argument is a temporary variable assigned to each element in $args on each iteration.
pwsh ./loop-args-foreach.ps1 "192.168.1.1" "10.0.0.1"Processing parameter value: 192.168.1.1
Processing parameter value: 10.0.0.1Guarding Against Empty Input
if ($args.Count -eq 0) {
Write-Warning "No files specified. Usage: ./process-files.ps1 <file1> <file2> ..."
exit 1
}
foreach ($file in $args) {
if (Test-Path $file) {
Write-Output "Processing: $file"
} else {
Write-Warning "File not found: $file"
}
}./process-files.ps1 "report.csv" "missing.txt"Processing: report.csv
WARNING: File not found: missing.txtwhere:
$args.Count -eq 0checks whether any arguments were supplied at all before proceeding.- Each element of
$argsis treated as a file path and tested withTest-Path.
param() vs. $args
While $args captures all unnamed arguments generically, the param() block defines named, typed parameters with support for defaults, validation, and mandatory checks. Understanding when to use each is key to writing clear, robust scripts.
param(
[string]$Name,
[int]$Age = 18
)
Write-Output "Name: $Name, Age: $Age"./param-example.ps1 -Name "Tom" -Age 30Name: Tom, Age: 30where:
-Nameand-Ageare explicit, named parameters, self-documenting and order-independent.-Agedefaults to18if the caller omits it.
Comparison Matrix
| Feature | Dynamic $args Array |
Formal param() Block |
|---|---|---|
| Declaration Requirement | Automatic (no declaration needed) | Explicitly defined at the top of the file |
| Naming | Positional only, no built-in names | Named parameters (-Name, -Age) |
| Named Parameter Support | None (arguments are strictly positional) | Full support (e.g., -ComputerName "Server01") |
| Tab Completion | No | Built-in CLI tab completion for parameter names |
| Strong Type Enforcement | No (all inputs default to [string]) |
Yes (e.g., [int]$Port, [datetime]$Date) |
| Mandatory Validation | Manual if checks required |
Built-in via [Parameter(Mandatory=$true)] |
| Default Fallback Values | Requires manual null-coalescing | Native assignment (e.g., $Timeout = 30) |
| Help Integration | Incompatible with Get-Help |
Automatically documents parameters in Get-Help |
| Best for | Unknown/variable number of simple inputs | Well-defined, structured script interfaces |
Mixed Usage: param() + $args
param(
[string]$LogPath = "C:\Logs\default.log"
)
Write-Output "Log path: $LogPath"
Write-Output "Extra arguments received: $args"./mixed-usage.ps1 -LogPath "C:\Logs\custom.log" "extra1" "extra2"Log path: C:\Logs\custom.log
Extra arguments received: extra1 extra2where:
- Named parameters declared in
param()are bound first (-LogPath). - Any remaining unmatched arguments still fall through into
$args.
Accepting Multiple Values in a param() Block
You do not need to rely on $args just to accept an arbitrary list of inputs. You can declare a strongly-typed array parameter within a param() block and loop through it directly:
param (
[string[]]$ComputerNames = @("localhost")
)
foreach ($computer in $ComputerNames) {
Write-Output "Target Node: $computer"
}This approach allows callers to pass values either as a comma-separated list or as individual named arguments, while still giving you a clean array to loop through:
pwsh ./loop-param-array.ps1 -ComputerNames "web01", "sql01", "app01"Target Node: web01
Target Node: sql01
Target Node: app01Practical Example: Batch Host Connectivity Validator
The following script accepts an arbitrary list of target hostnames or IP addresses via $args, loops through each entry with a for loop, and validates ICMP reachability:
if ($args.Count -eq 0) {
Write-Error "No targets specified! Usage: ./ping-target-pool.ps1 <Host1> <Host2> ... <HostN>"
exit 1
}
Write-Output "Discovered $($args.Count) host target(s) to evaluate.`n"
$AuditReport = for ($index = 0; $index -lt $args.Count; $index++) {
$targetHost = $args[$index]
$pingResult = Test-Connection `
-ComputerName $targetHost `
-Count 1 `
-Quiet `
-ErrorAction SilentlyContinue
[PSCustomObject]@{
ArgumentPosition = $index + 1
TargetAddress = $targetHost
IsReachable = $pingResult
Status = if ($pingResult) { "ONLINE" } else { "UNRESPONSIVE" }
CheckedAt = Get-Date -Format "HH:mm:ss"
}
}
$AuditReport | Format-Table -AutoSizewhere:
if ($args.Count -eq 0)checks whether any CLI arguments were supplied; if none, it exits with an error.for ($index = 0; $index -lt $args.Count; $index++)steps through each argument sequentially using its array index.$args[$index]resolves the current target hostname.Test-Connection -TargetName $targetHost -Count 1 -Quietsends a single ICMP ping and returns$trueor$false.$AuditReport = for (...)collects the custom objects emitted by each iteration into a report array.
Execute the script from the console with multiple target arguments:
pwsh ./ping-target-pool.ps1 127.0.0.1 localhost 192.0.2.1Discovered 3 host target(s) to evaluate.
ArgumentPosition TargetAddress IsReachable Status CheckedAt
---------------- ------------- ----------- ------ ---------
1 127.0.0.1 True ONLINE 19:41:01
2 localhost True ONLINE 19:41:01
3 192.0.2.1 False UNRESPONSIVE 19:41:06