Read-Only and Constant Variables

Quick reminder: You can make variables in PowerShell read-only, or constant.

I've written about this before, but it's time to cover it again for any newcomers to PowerShell, and because I actually found a need for it. Previously, it was just pure learning and sharing. Today, I'll let you in on how I recently used a read-only variable, and bonus, something else I discovered, too! Let's begin with how to use these variable options.

When you're new to PowerShell, you learn to create variables by using the assignment operator. No, not the equals sign. Look at the below example, and then say it out loud as, "ADUser (or dollar sign ADUser) is assigned the values returned by the Get-ADUser Cmdlet." Don't say equals; it's not equals. Lose that habit, if you have it. And yes, people will be listening for it. Okay fine. It may just be me.

PS C:\> $ADUser = Get-ADUser -Identity tommymaynard

You can also create a variable and assign it a value by using the New-Variable Cmdlet. Notice that we don't use a dollar sign when we do it this way. The same goes for all of the *-Variable cmdlets (Get-Command -Name *-Variable). Dollar signs aren't a part of a variable, as much as they're there to indicate to the parser, that what follows a dollar sign, is a variable. That's how I've come to understand it, anyway.

PS C:\> New-Variable -Name User -Value tommymaynard
PS C:\> $User
tommymaynard
PS C:\> $User.GetType().Name
String
PS C:\> Get-Variable -Name User

Name                           Value
----                           -----
User                           tommymaynard

We can use Set-Variable to reassign our previously existing variable. If we use Set-Variable against a variable that doesn't already exists, it essentially runs New-Variable. It may actually run New-Variable.

PS C:\> Set-Variable -Name User -Value 'maynard, tommy'
PS C:\> $User
maynard, tommy
PS C:\>

Now, one last piece of information before we move on. New-Variable (as well as Set-Variable), has an Option parameter. It'll accept a handful of predetermined arguments, or parameter values, but today we'll cover the ReadOnly value. We'll touch on the Constant value, too. A read-only variable is one where you can't change the value after the variable has been assigned. Okay, that's not entirely true. You can reassign a read-only variable if you use the Force parameter. If we use Constant instead of ReadOnly for the Option parameter's value then there would be no way to reassign the variable ever. Additionally, you can only make a variable a constant when it’s first created. That's pretty much the difference between those two.

PS C:\> New-Variable -Name Var -Value 'testing' -Option ReadOnly
PS C:\> Set-Variable -Name Var -Value 'not testing'
Set-Variable : Cannot overwrite variable Var because it is read-only or constant.
At line:1 char:1
+ Set-Variable -Name Var -Value 'not testing'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : WriteError: (Var:String) [Set-Variable], SessionStateUnauthorizedAccessException
+ FullyQualifiedErrorId : VariableNotWritable,Microsoft.PowerShell.Commands.SetVariableCommand

PS C:\> $Var = 'not testing'
Cannot overwrite variable Var because it is read-only or constant.
At line:1 char:1
+ $Var = 'not testing
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo          : WriteError: (Var:String) [], SessionStateUnauthorizedAccessException
+ FullyQualifiedErrorId : VariableNotWritable

PS C:\> $Var
testing
PS C:\> Set-Variable -Name Var -Value 'not testing' -Force
PS C:\> Get-Variable -Name Var

Name                           Value
----                           -----
Var                            not testing

PS C:\> (Get-Variable -Name Var).Value
not testing
PS C:\>

I have what I've written and called, the Advanced Function Template, for work. I won't go into all the neat things it includes, but a few years later and I'm still quite proud of all the things I've stuffed into it. I use it as my starting point for every function I author. One thing it does is logging (to the screen, to a file, or both). As a part of its logging, it lists the block location. Here's an example of a function created with the template (that has no, non-template code logging, at minimum).

VERBOSE: [INFO   ] Invoking the Get-WorkDomainComputer function.
VERBOSE: [INFO   ] Invoking user is "MYDOMAIN\tommymaynard."
VERBOSE: [INFO   ] Invoking on Saturday, September 14, 2019 9:47:12 PM.
VERBOSE: [INFO   ] Invoking on the "TOMMAY-LAPTO" computer.
VERBOSE: [PARAM  ] Including the "Log" parameter with the "ToScreen" value.
VERBOSE: [BEGIN  ] Entering the Begin block [Function: Get-WorkDomainComputer].
VERBOSE: [PROCESS] Entering the Process block [Function: Get-WorkDomainComputer].
VERBOSE: [END    ] Entering the End block [Function: Get-WorkDomainComputer].

Okay, "Info" and "Param" aren't block locations like Begin, Process, and End. Even so, I've found them to be helpful. This value is stored in the $BL variable originally, and, as you can see, the value is changed while a function is executing. It occurred to me recently, and randomly, that someone else that builds a tool from this same function template might create their own BL, or bl (it doesn't matter) variable. This would cause a mess! This whole thing would be better if the variable were protected: enter the variable read-only option. While I could've made it a constant, the function (my code) wouldn't be able to change its value, but you knew that already.

So, I changed all my $BL variable assignments to these, which are a part of the template function code now.

New-Variable -Name BL -Value '[INFO   ]' -Option ReadOnly
Set-Variable -Name BL -Value '[PARAM  ]' -Force
Set-Variable -Name BL -Value '[BEGIN  ]' -Force
Set-Variable -Name BL -Value '[PROCESS]' -Force
Set-Variable -Name BL -Value '[END    ]' -Force

Now, if a function author uses my function template and without knowing they shouldn't, tries to create a variable using $BL, they won't break what the function template is doing in the background. PowerShell will put a stop to that before they get too far (without making the function template explode). Here's how it appears in my testing; this should look familiar.

Cannot overwrite variable BL because it is read-only or constant.
At line:222 char:9
+         $BL = 'testing'
+         ~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (BL:String) [], SessionStateUnauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotWritable

Now, I'm going to need to consider the nested functions in the function template and their protection, too. It would be less likely they'd choose the same name of those, but I'm of the mindset that we should put in protection even if we don't think we'll need it.

Oh, last thing (but not really). I had this thought: Can I make a preference variable read-only?

PS C:\> $VerbosePreference
SilentlyContinue
PS C:\> Set-Variable -Name VerbosePreference -Option ReadOnly
PS C:\> $VerbosePreference = 'Continue'
Cannot overwrite variable VerbosePreference because it is read-only or constant.
At line:1 char:1
+ $VerbosePreference = 'Continue'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : WriteError: (VerbosePreference:String) [], SessionStateUnauthorizedAccessException
+ FullyQualifiedErrorId : VariableNotWritable

PS C:\>

I can! I can see where this may be helpful for my function template, too!! I didn't bother trying it, but this variable likely can't be made to be a constant. This option has to be applied at the time a variable is created, unlike read-only, which can be applied to a variable after it's already been created. Important note, to which I implied earlier.

And finally, in case you cared to see the first, Active Directory example in this article using New-Variable, then here you go. It works like Math. You know, Order of Operations: complete what's in the parentheses first, and then continue. If you didn't include the parentheses, it would make Get-ADuser a string and assign it as the value of the ADUser2 variable. Then, it would fail when it thought, you thought the New-Variable Cmdlet had an Identity parameter. Uhhh, no.

PS C:\> New-Variable -Name ADUser2 -Value (Get-ADUser -Identity tommymaynard)
PS C:\> $ADuser2.GivenName
Tommy
PS C:\>

≥ Tommy Maynard (Twitter: @thetommymaynard)

About Tommy Maynard

IT Pro. Passionate for #PowerShell, #AWS (certified x2), & all things automation. I'm not done learning. Author in #PSConfBook. Writes at https://powershell.org.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.