4

Is it possible to create a typed read-only variable/constant in a PowerShell script? Afaik there a two ways to create a variable:

First way:

[int] $myVar1 But I am not aware of any constant / readonly keyword nor any other way to prevent changes from this variable.

Second via Cmdlet:

New-Variable -Name 'myVar1' -Option ReadOnly (editable with -Force) or New-Variable -Name 'myVar1' -Option Constant

But I don't know any way to apply both. Any ideas?

  • 2
    `Set-Variable -Name 'myVar1' -Option Constant -Value `. If you create the variable `ReadOnly`, there is no way to enforce a typing constraint since the caller can always forcibly remove the variable and recreate it, so there's no real point to wanting a variable that can only be `Force`d into a particular type. – Jeroen Mostert Mar 09 '20 at 13:45

2 Answers2

2

To bring Adam Luniewski's helpful answer and Jeroen Mostert's helpful recommendation together:

  • You cannot set variable options as part of a variable assignment.

    • While you can apply validation attributes to an assignment, as shown in Adam's answer, they can't be used to enforce read-only behavior.
  • Only New-Variable and Set-Variable allow you to specify variable options, via their -Option parameter.

    • Options ReadOnly and Constant differ only by whether you can later remove the variable again:

      • a ReadOnly variable can be removed, with Remove-Variable, if you also pass the -Force switch.

      • a Constant variable cannot be removed once created.


Therefore, to get what you want:

  • Use New-Variable (or Set-Variable) with -Option Constant, which prevents its later removal.

    • Note that your variable can by default still be shadowed by a variable of the same name created in a descendant scope; e.g.: Set-Variable -Option Constant var 1; & { $var = 2; $var } - a new, local $var was created inside the script block, which shadowed the outer, constant one.

    • To prevent that, use -Option Constant, AllScope, which ensures that all code in the session sees the same value for a variable with the given name, but note that code that tries to create its own copy can then fail, so you should only do this with variable names that can be assumed not to be used for different purposes in other code.

  • To assign a specifically typed value, pass it to -Value via an expression (enclosed in (...), the grouping operator) - if necessary:

# Create a constant, [int]-typed variable.
# Note that just `-Value 42` would be sufficient here.
New-Variable -Option Constant -Name myVar1 -Value ([int] 42)

Just -Value 42 would be sufficient in this case, because PowerShell by default parses unquoted arguments that can be parsed as numbers as such, with [int] as the smallest type chosen; you can even use number-type suffixes such as L for [long]. However, negative "number strings" (e.g., -42) are not recognized as numbers and require (...).

Similarly, a quoted token (e.g., '42') would implicitly create a [string], a hashtable literal (@{ key = 'value' }) would create a hashtable, and so on; a simple variable reference (e.g., $HOME) or member access (e.g., $HOME.ToUpper()) would preserve the variable / member value's type.

See also:

  • This answer provides a comprehensive overview of how unquoted token are parsed in argument mode (the parsing mode for commands to which arguments are passed).

  • This answer explains how PowerShell parses number literals (which also applies when passing unquoted tokens as command arguments).

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

Wouldn't this work?

New-Variable -Name x -Value ([double]5) -Option ReadOnly -Force
$x.GetType()
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Double                                   System.ValueType

Optionally you could also use attributes to make syntax shorter:

[ValidateSet(2)][int]$y=2
[ValidateRange(3,3)][int]$z=3

But it won't throw an error if you try to assign same value again, while readonly/constant will.

AdamL
  • 12,421
  • 5
  • 50
  • 74