I would like to declare some integer constants in PowerShell.
Is there any good way to do that?
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.
Set-Variable? When dealing with variables one may use [string]$name = value but that seems not to be possible for constants?Set-Variable test -option Constant -value [string]100([string]100). See answers below.-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.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
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.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).
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.set -o is a bit too cryptic for me to have in a script.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:
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.-Mean to -Value, for consistency with Set-Variable.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
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.
Set-Variable? When dealing with variables one may use [string]$name = value but that seems not to be possible for constants?set-variable -name test -value ([int64]100) -option ConstantThere 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 fakeConst -Value constant -Option Constant -PassThru
$var.GetType().
GetField('_value', 'NonPublic, Instance').
SetValue($var, 'nope')
$fakeConst
if ($IsCoreCLR) {
$field = '_options'
}
else {
$field = 'options'
}
$var.GetType().
GetField($field, [System.Reflection.BindingFlags] 'NonPublic, Instance').
SetValue($var, [System.Management.Automation.ScopedItemOptions]::None)
$fakeConst = 'not a constant anymore :('
$fakeConst
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'
}
PowerShell v5.0 should allow
[static] [int] $variable = 42
[static] [DateTime] $thisday
and the like.