Sometimes read-only variables are allowed as parameters, other times they are not. Is there a rule or some understandable mechanism at play here? Is there a way to predict which names cause a VariableNotWritable error when used as parameters?
& {param($PSEdition)} # fails
& { [bool]((Get-Variable PSEdition | % Options) -band 'Constant') } # true
& {param([Alias('PSEdition')]$__PSEdition)} # succeeds
Set-Variable IAmAConstant -Value 'constant value' -Option Constant
& { [bool]((Get-Variable IAmAConstant | % Options) -band 'Constant') } # true
& {param($IAmAConstant)} # succeeds
$m = New-Module {}
& $m { [bool]((Get-Variable PSEdition | % Options) -band 'Constant') } # true
& $m {param($PSEdition)} # succeeds
I expected either all constant variables or none to be prohibited as parameters.
In some cases constant variables are permitted as parameters, in other cases they cause errors like following:
Cannot overwrite variable PSEdition because it is read-only or constant.
At line:1 char:1
+ & {param($PSEdition)} # fails
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (PSEdition:String) [], SessionStateUnauthorizedAccessExce
ption
+ FullyQualifiedErrorId : VariableNotWritable
> $PSVersionTable
Name Value
---- -----
PSVersion 6.0.0
PSEdition Core
GitCommitId v6.0.0
OS Microsoft Windows 6.3.9600
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Hi @alx9r, there are several things going on here.
In your first example with the parameter alias, you aren't actually touching the $PSEdition variable. The alias maps -PSEdition to $__PSEdition which is a completely different variable and so there is no error.
The behavior in the second example has to do with scopes and the AllScope option. A constant variable may be masked by a non-constant variable in a child scope unless that variable is marked AllScope. This is what's happening in your second example. Using '.' instead of '&' will cause it to fail because '.' evaluates the scriptblock in the current scope:
PS[1] (20) > & {param($IAmAConstant)} # succeeds because parameter is in child scope
PS[1] (21) > . {param($IAmAConstant)} # fails because parameter is in current scope
Cannot overwrite variable IAmAConstant because it is read-only or constant.
At line:1 char:1
+ . {param($IAmAConstant)} # fails because parameter is in current scop ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (IAmAConstant:String) [], SessionStateUnauthorizedAccessException
+ FullyQualifiedErrorId : VariableNotWritable
(See about_Scopes for more information about how scopes and scope options work.)
Finally the third example, involving module scopes, does seem to be a bug. Trying to modify a global, constant, allscope variable should result in an error even when evaluating a scriptblock in a module scope.
To summarize, marking a variable as AllScope prevents it from being hidden in a child scope. And there seems to be a bug in the way AllScope variables are being handled when evaluating a scriptblock in a module context. @lzybkr - any thoughts?
A constant variable may be masked by a non-constant variable in a child scope unless that variable is marked AllScope. This is what's happening in your second example.
I see. The following seems to be consistent with this explanation:
Set-Variable IAmAnAllScopeConstant -Value 'constant value' -Option Constant,AllScope
& { Get-Variable IAmAnAllScopeConstant | % Options } # Constant, AllScope
& {param($IAmAnAllScopeConstant)} # fails
Set-Variable IAmAConstant -Value 'constant value' -Option Constant
. {param($IAmAConstant)} # fails
Finally the third example, involving module scopes, does seem to be a bug. Trying to modify a global, constant, allscope variable should result in an error even when evaluating a scriptblock in a module scope.
I'm not sure this is quite on-point, but I've opened #6378 as it seems that a write to an AllScope variable from a module does not affect that variable's value in the "global" session state.
Finally the third example, involving module scopes, does seem to be a bug. Trying to modify a global, constant, allscope variable should result in an error even when evaluating a scriptblock in a module scope.
@BrucePay Unlike $Error and $PSDefaultParameterValues that are declared in every SessionState, $PSEdition is only declared in the default SessionState of the current Runspace. So the module scope doesn't have the variable PSEdition defined at all, and thus & $m {param($PSEdition)} works.
PS:106> $m = New-Module {}
PS:107> & $m {Get-Variable PSEdition -Scope local }
Get-Variable : Cannot find a variable with the name 'PSEdition'.
At line:1 char:7
+ & $m {Get-Variable PSEdition -Scope local }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (PSEdition:String) [Get-Variable], ItemNotFoundException
+ FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand
Are you considering making PSEdition and other all scope const variables like $Host and $PID to be declared in every SessionState?