144

I would like to declare some integer constants in PowerShell.

Is there any good way to do that?

wonea
  • 4,783
  • 17
  • 86
  • 139
Tom Hazel
  • 3,232
  • 2
  • 20
  • 20

7 Answers7

151

Use

Set-Variable test -Option Constant -Value 100

or

Set-Variable test -Option ReadOnly -Value 100

The difference between "Constant" and "ReadOnly" is that a read-only variable can be removed (and then re-created) via

Remove-Variable test -Force

whereas a constant variable can't be removed (even with -Force).

See this TechNet article for more details.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Motti Strom
  • 2,555
  • 1
  • 18
  • 15
  • 4
    Hmm, but **how do you force the datatype** when using `Set-Variable`? When dealing with variables one may use `[string]$name = value` but that seems not to be possible for constants? – omni Dec 01 '12 at 23:32
  • 9
    @masi just force the value `Set-Variable test -option Constant -value [string]100` – Monso Apr 26 '14 at 23:41
  • 9
    @Monso You need parentheses around the value when specifying type like `([string]100)`. See answers below. – Polymorphix Jun 16 '16 at 09:18
  • This doesn't create a constant. As the variable cannot be used in validation sets. [ValidateSet($RunTypeStandard ,"Debug" ,"DebugWithEnvironment" )] I still get the error that "Attribute argument needs to be a constant" – deetle Nov 29 '21 at 19:44
  • Another difference between `-Option Constant` and `-Option ReadOnly` is, that you can apply `ReadOnly` to an existing variable, e. g. `$CONST_FOO = 42; $CONST_BAR = 23; Set-Variable CONST_* -Option ReadOnly`. I'm now using this scheme because it works better with code navigation. E. g. VSCode's PowerShell extension is able to navigate to the definition of a variable only if it is defined regularly (`$var = value`). By using a common prefix I only need to call `Set-Variable` once, which makes the code cleaner. – zett42 Jun 09 '23 at 10:31
21

Here is a solution for defining a constant like this:

const myConst = 42

Solution taken from http://poshcode.org/4063

    function Set-Constant {
  <#
    .SYNOPSIS
        Creates constants.
    .DESCRIPTION
        This function can help you to create constants so easy as it possible.
        It works as keyword 'const' as such as in C#.
    .EXAMPLE
        PS C:\> Set-Constant a = 10
        PS C:\> $a += 13

        There is a integer constant declaration, so the second line return
        error.
    .EXAMPLE
        PS C:\> const str = "this is a constant string"

        You also can use word 'const' for constant declaration. There is a
        string constant named '$str' in this example.
    .LINK
        Set-Variable
        About_Functions_Advanced_Parameters
  #>
  [CmdletBinding()]
  param(
    [Parameter(Mandatory=$true, Position=0)]
    [string][ValidateNotNullOrEmpty()]$Name,

    [Parameter(Mandatory=$true, Position=1)]
    [char][ValidateSet("=")]$Link,

    [Parameter(Mandatory=$true, Position=2)]
    [object][ValidateNotNullOrEmpty()]$Mean,

    [Parameter(Mandatory=$false)]
    [string]$Surround = "script"
  )

  Set-Variable -n $name -val $mean -opt Constant -s $surround
}

Set-Alias const Set-Constant
rob
  • 17,995
  • 12
  • 69
  • 94
  • 1
    Unfortunately this doesn't work when `Set-Constant` is contained in a module. It will create a constant in the module scope, where `Set-Constant` is contained. As a workaround one could pass parameter `-Surround Global`, but that's not always wanted. I would like to create a constant in another module or locally in a function. – zett42 Jan 22 '19 at 17:45
13

To use a specific type of value, say Int64, you can explicitly cast the value used in set-variable.

For instance:

set-variable -name test -value ([int64]100) -option Constant

To check,

$test | gm

And you'll see that it is an Int64 (rather than Int32, which would be normal for the value 100).

Mike Shepard
  • 17,466
  • 6
  • 51
  • 69
  • This could be shortened to: `set -o const test ([int64]100)`. This is one of the few cases where I would prefer the alias over the full command name in a script, to let my mind "scan" the source code quicker. – zett42 Apr 03 '21 at 17:47
  • I don't agree. `set -o` is a bit too cryptic for me to have in a script. – Mike Shepard Apr 03 '21 at 21:15
13

Use -option Constant with the Set-Variable cmdlet:

Set-Variable myvar -option Constant -value 100

Now $myvar has a constant value of 100 and cannot be modified.

Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
  • 2
    Wow, that's clunky. You have to use Set-Variable to do it, huh? – Tom Hazel Apr 09 '10 at 14:26
  • Yes, there's not unclunky way to do it :) – Paolo Tedesco Apr 09 '10 at 14:26
  • 1
    you can also modify and existing variable with either set-variable (aliased to sv) or by using get-variable (gv) and tinkering with its Options property. – x0n Apr 09 '10 at 16:05
  • Hmm, but how do you force the datatype when using `Set-Variable`? When dealing with variables one may use `[string]$name = value` but that seems not to be possible for constants? – omni Dec 02 '12 at 09:05
  • @masi - see Mike Shepard's answer elsewhere in this page. Copy and pasting from there, it's: `set-variable -name test -value ([int64]100) -option Constant` – Chris J Jul 06 '15 at 08:58
12

I really like the syntactic sugar that rob's answer provides:

const myConst = 42

Unfortunately his solution doesn't work as expected when you define the Set-Constant function in a module. When called from outside the module, it will create a constant in the module scope, where Set-Constant is defined, instead of the caller's scope. This makes the constant invisible to the caller.

The following modified function fixes this problem. The solution is based on this answer to the question "Is there any way for a powershell module to get at its caller's scope?".

$null = New-Module {
    function Set-Constant {
        <#
        .SYNOPSIS
            Creates constants.
        .DESCRIPTION
            This function can help you to create constants so easy as it possible.
            It works as keyword 'const' as such as in C#.
        .EXAMPLE
            PS C:\> Set-Constant a = 10
            PS C:\> $a += 13

            There is a integer constant declaration, so the second line return
            error.
        .EXAMPLE
            PS C:\> const str = "this is a constant string"

            You also can use word 'const' for constant declaration. There is a
            string constant named '$str' in this example.
        .LINK
            Set-Variable
            About_Functions_Advanced_Parameters
        #>
        [CmdletBinding()]
        param(
            [Parameter(Mandatory, Position=0)] [string] $Name,
            [Parameter(Mandatory, Position=1)] [char] [ValidateSet('=')] $Link,
            [Parameter(Mandatory, Position=2)] [object] $Value
        )

        try {
            $PSCmdlet.SessionState.PSVariable.Set( 
                [Management.Automation.PSVariable]::new( 
                    $Name, $Value, [Management.Automation.ScopedItemOptions]::Constant ) )
        }
        catch {
            # This makes sure the location of the call to Set-Constant is reported 
            # in the error message, instead of the location of the call to PSVariable.Set().
            $PSCmdlet.WriteError( $_ )
        }
    }
}

Set-Alias const Set-Constant

Notes:

  • The New-Module line is there because the function only works, when called from a different scope domain (aka session state). You could put the function into an actual module file (.psm1), but then you couldn't use it from within that same module! The in-memory module makes it usable as-is from both PowerShell scripts (.ps1) as well as module files.
  • I've renamed the parameter -Mean to -Value, for consistency with Set-Variable.
  • The function could be extended to optionally set the Private, ReadOnly and AllScope flags. Simply add the desired values to the 3rd argument of the PSVariable constructor, which is called in the above script through New-Object.

Edit 06/2023:

A disadvantage of both Set-Constant and any solution based on Set-Variable -Option Constant is, that VSCode's PowerShell extension does not support navigation to the definition of the variable, e. g. by Ctrl+Click on the variable name (see this GitHub issue).

My current workaround is to define the constants like normal variables (so VSCode sees their definition in the AST) and then make them constant by redefining them using New-Variable -Force -Option Constant. Contrary to Set-Variable, the New-Variable command can overwrite existing variables.

A typical module that exports constants, now looks like this:

# Define variables
$CONST_FOO = 42
$CONST_BAR = 23

# Make the variables constant, by redefining them as constants.
Get-Variable CONST_* | ForEach-Object { New-Variable -Name $_.Name -Value $_.Value -Option Constant -Force }

# Export the constants from the module
Export-ModuleMember -Variable CONST_*

Here is a full demo to play around with:

$null = New-Module {
    $CONST_FOO = 42
    $CONST_BAR = 23
    
    Get-Variable CONST_* | ForEach-Object { New-Variable -Name $_.Name -Value $_.Value -Option Constant -Force }
    Export-ModuleMember -Variable CONST_*
}

# All of these should error out
$CONST_FOO = 0
$CONST_BAR = 0
Remove-Variable CONST_FOO -Force
zett42
  • 25,437
  • 3
  • 35
  • 72
  • 1
    You could check the call stack to find out where the assignment took place: `if ((Get-PSCallStack)[1].Command -eq 'your-module-file-name.psm1') { <# inside module #> } else { <# outside #> }` – pfeileon Apr 01 '21 at 10:37
  • @pfeileon Found a different way using an in-memory module. – zett42 Aug 15 '22 at 17:58
0

There is really 0 benefit from creating a constant thru a psvariable. There is no performance gain and it can be updated via reflection at will:

$var = Set-Variable notareal -Value constant -Option Constant -PassThru
$var.GetType().GetField('_value', 'NonPublic, Instance').SetValue($var, 'nope')
$notareal

It's not a real constant. If you want a real const the way is thru inline C#:

$addTypeSplat = @{
    MemberDefinition = 'public const string Const = "myConstValue";'
    Name             = 'MyConstClass'
    Namespace        = 'MyNamespace'
}

Add-Type @addTypeSplat
[MyNamespace.MyConstClass]::Const

Or the closest you can get to it from PowerShell is with a static property in a PowerShell Class (from a performance standpoint):

class MyConst {
    static [string] $Const = 'constValue'
}
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
-7

PowerShell v5.0 should allow

[static] [int] $variable = 42

[static] [DateTime] $thisday

and the like.

wonea
  • 4,783
  • 17
  • 86
  • 139
MarkH
  • 1
  • 1