Passing arguments as array to PowerShell function

4.6k views Asked by At

I'm trying to figure out how I can pass multiple strings as an array to a powershell function.

function ArrayCount([string[]] $args) {
    Write-Host $args.Count
}

ArrayCount "1" "2" "3"
ArrayCount "1","2","3"
ArrayCount @("1","2","3")

Prints

2
0
0

How can I pass an array with 3 values to the ArrayCount function? Why is the Count zero for some of the invocations?

2

There are 2 answers

0
Martin Brandl On BEST ANSWER

In PowerShell, $args is a automatic variable that refers to unnamed arguments. Just change your parameter name:

function ArrayCount([string[]] $myParam) {
    Write-Host $myParam.Count
}

And you will get the expected output:

1
3
3
0
mklement0 On

Written as of: PowerShell Core 7.0.0-preview.5 / Windows PowerShell v5.1

To complement Martin Brandl's helpful, to-the-point answer with some background information:

$args is an instance of an automatic variable, meaning a special variable whose value is controlled by PowerShell itself.

Therefore, you should avoid custom use of automatic variables, even in cases where it happens to work (see below).

Ideally, PowerShell would itself prevent such custom use consistently (it only does so for some automatic variables), and there was debate on GitHub about enforcing that, but it was ultimately decided not to do it for reasons of backward compatibility.

Instead, design-time warnings about assignments to automatic variables in Visual Studio Code with the PowerShell extension installed, via PSScriptAnalyzer are now being emitted.

Without the help of Visual Code, unfortunately, you typically cannot infer whether a variable name refers to an automatic variable from its name and there is also no programmatic way of discovering them - reading the help topic is the only option; see the next section.


List of automatic variables that shouldn't be writable, but de facto are:

Note:

  • The list was generated with the command below, as of Windows PowerShell v5.1 - in PowerShell Core, the list is shorter, because some obsolete variables were removed.

    • The command relies on string parsing of the relevant help topic; such a method is brittle, but it is currently the only way to discover automatic variables, given that there is no reflection-based method.

    • This GitHub issue suggests implementing programmatic discoverability and also discusses the possibility of introducing a separate, reserved namespace for automatic variables.

  • Over time, new automatic variables could be introduced; hopefully, their read-only status, if appropriate, will be enforced on introduction.

    • Because automatic variables share a namespace with user variables, introducing any new automatic variable bears the risk of breaking existing code.
  • A select few automatic variables are legitimately writeable:

    • $OFS - the separator to use when stringifiying arrays.
    • $null - the special variable that not only represents a null value when read, but is also designed to allow assigning any value to it in order to discard it (not write it to the (success) output stream).
    • $LASTEXITCODE, in the form $global:LASTEXITCODE, to set the process exit code to use (though that is better done with exit <n>).
  • The automatic variables not listed below (e.g., $PID) already are effectively read-only (as they should be). The ones listed below - i.e. the unexpectedly writeable ones - fall into the following categories:

    • Those that always quietly discard the assigned value (e.g., $MyInvocation)

    • Those that accept a custom value, but override (shadow) it in contexts where a value is automatically assigned (e.g., $args, which is automatically set as a local variable whenever a new script block (function / script) is entered; your use as a local parameter variable was effectively ignored).

    • Hybrid cases, notably $_ / $PSItem, where assigned values are quietly discarded outside any context where $_ is automatically assigned a value, but inside such contexts you can (but shouldn't) assign a new value (e.g, 'in' | ForEach-Object { $_ = $_ + '!'; $_ } outputs in!)

Name                          Predefined
----                          ----------
_                                  False
AllNodes                           False
Args                                True
Event                              False
EventArgs                          False
EventSubscriber                    False
ForEach                             True
Input                               True
Matches                             True
MyInvocation                        True
NestedPromptLevel                   True
Profile                             True
PSBoundParameters                   True
PsCmdlet                           False
PSCommandPath                       True
PSDebugContext                     False
PSItem                             False
PSScriptRoot                        True
PSSenderInfo                       False
Pwd                                 True
ReportErrorShowExceptionClass      False
ReportErrorShowInnerException      False
ReportErrorShowSource              False
ReportErrorShowStackTrace          False
Sender                             False
StackTrace                          True
This                               False

"Predefined" refers to whether they exist in the global scope by default.

The following code was used to detect them - you can set $VerbosePreference = 'Continue' beforehand (and reset it after) to also see the already read-only variables:

$autoVarNames = ((get-help about_automatic_variables) -split [environment]::newline -match '^\s*\$\w+\s*$').Trim().Trim('$') | Sort-Object -Unique

foreach ($varName in $autoVarNames) {
  $var = Get-Variable $varName -ErrorAction 'SilentlyContinue'
  $exists = $?
  if ($exists -and $var.Options -match 'readonly|constant') {
    Write-Verbose "$varName`: read-only or constant"
  } elseif ($varName -in 'NULL', 'OFS', 'LastExitCode') { # exceptions
    Write-Verbose "$varName`: writable by design"
  } else {
    Set-Variable -Name $varName -Value $null -ErrorAction SilentlyContinue
    if ($?) {
      [pscustomobject] @{ Name = $varName; Predefined = $exists }
    }
  }
}

Note that the code has a hard-coded list of exceptions so as not to report automatic variables that should indeed be writable, such as $OFS, and $LastExitCode, or assignable as a quiet no-op, such as $null.