Skip to content

PowerShell For Loop: Command-Line Arguments

How to process dynamic command-line arguments in PowerShell using the $args array and looping constructs like for and foreach for flexible input handling.

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.
inspect-args.ps1powershell
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.Count evaluates 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:

Consolepowershell
pwsh ./inspect-args.ps1 "web-prod-01" "sql-prod-01" "ad-auth-01"
Outputplaintext
Total arguments received: 3
First argument: web-prod-01
Last argument:  ad-auth-01
args-basic.ps1powershell
foreach ($arg in $args) {
    Write-Output "Argument: $arg"
}
Consolepowershell
./args-basic.ps1 "apple" "banana" "cherry"
Outputplaintext
Argument: apple
Argument: banana
Argument: cherry

where:

  • Each space-separated value passed after the script name becomes an element in the $args array.
  • $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:

loop-args-for.ps1powershell
for ($i = 0; $i -lt $args.Count; $i++) {
    Write-Output "Argument [$i] = $($args[$i])"
}

where:

  • $i = 0 initializes the counter at index 0.
  • $i -lt $args.Count runs until all elements have been visited.
  • $args[$i] accesses each argument by its numeric position.
Consolepowershell
pwsh ./loop-args-for.ps1 Alpha Beta Gamma
Outputplaintext
Argument [0] = Alpha
Argument [1] = Beta
Argument [2] = Gamma

Method 2: foreach Statement (Sequential Processing)

Use the foreach statement when you want to process each passed argument sequentially without tracking numeric indices:

loop-args-foreach.ps1powershell
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.

Consolepowershell
pwsh ./loop-args-foreach.ps1 "192.168.1.1" "10.0.0.1"
Outputplaintext
Processing parameter value: 192.168.1.1
Processing parameter value: 10.0.0.1

Guarding Against Empty Input

process-files.ps1powershell
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"
    }
}
Consolepowershell
./process-files.ps1 "report.csv" "missing.txt"
Outputplaintext
Processing: report.csv
WARNING: File not found: missing.txt

where:

  • $args.Count -eq 0 checks whether any arguments were supplied at all before proceeding.
  • Each element of $args is treated as a file path and tested with Test-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-example.ps1powershell
param(
    [string]$Name,
    [int]$Age = 18
)

Write-Output "Name: $Name, Age: $Age"
Consolepowershell
./param-example.ps1 -Name "Tom" -Age 30
Outputplaintext
Name: Tom, Age: 30

where:

  • -Name and -Age are explicit, named parameters, self-documenting and order-independent.
  • -Age defaults to 18 if 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

mixed-usage.ps1powershell
param(
    [string]$LogPath = "C:\Logs\default.log"
)

Write-Output "Log path: $LogPath"
Write-Output "Extra arguments received: $args"
Consolepowershell
./mixed-usage.ps1 -LogPath "C:\Logs\custom.log" "extra1" "extra2"
Outputplaintext
Log path: C:\Logs\custom.log
Extra arguments received: extra1 extra2

where:

  • 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:

loop-param-array.ps1powershell
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:

Consolepowershell
pwsh ./loop-param-array.ps1 -ComputerNames "web01", "sql01", "app01"
Outputplaintext
Target Node: web01
Target Node: sql01
Target Node: app01

Practical 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:

ping-target-pool.ps1powershell
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 -AutoSize

where:

  • 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 -Quiet sends a single ICMP ping and returns $true or $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:

Consolepowershell
pwsh ./ping-target-pool.ps1 127.0.0.1 localhost 192.0.2.1
Outputplaintext
Discovered 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
Navigation