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)