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
.