Tag Archives: New-Variable

Read-Only and Constant Variables


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on September 23, 2019.


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 exist, 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, the 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, which I mentioned 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:\>

To Rename a PowerShell Variable

I had one of those thoughts…  you know, a dumb one. As dumb as it might be, it gave some brief inspiration to try something new. It seemed possible and easy enough and so I set out to prove, that as dumb (and useless), as it is, that it could be mine.

First off, on my machine, as it sits here today, I have 40 commands that include the “rename” verb. Additionally, I have five commands that include the “Variable” noun. Do you know what I do not have, though? I will tell you in a second if you have not figured it out yet. If you have figured it out, I will also tell you.

[PS7.2.1][C:\] Get-Command -Verb Rename | Measure-Object | Select-Object -Property Count

Count
-----
   40

[PS7.2.1][C:\] Get-Command -Noun Variable | Measure-Object | Select-Object -Property Count

Count
-----
    5

A Rename-Variable command. And you know why!? Because it is dumb and mostly useless. Regardless, I made it a thing and we will discuss it during this post. In the end, I think it turned out to be a success even though I will never use it again after today. That is what I say now, anyway.

Let’s do this in reverse. We will start with the commands I will invoke and then we will look at my function. My first below command Rename-Variable will send in the variable named TestVariableZero and the name to which we want to rename it, TestVariableZeroZero. This will not work, as we cannot rename a variable that does not yet exist. The function will use Write-Warning to inform us that it cannot be renamed. I could have used this section of the if-else to create the variable, but that is not today’s assignment. The next two lines will one, create the variable TestVariableOne with the value ValueOne and two, return information about the variable, so we know it has been created.

We are using the Force parameter in the first of these two commands in case the variable already exists. New-Variable cannot create a variable that already exists by default.

Rename-Variable -Name TestVariableZero -NewName TestVariableZeroZero

New-Variable -Name TestVariableOne -Value ValueOne -Force
Get-Variable -Name TestVariableOne

Rename-Variable -Name TestVariableOne -NewName TestVariableTwo

Get-Variable -Name TestVariableTwo
Get-Variable -Name TestVariableOne

Looking upward, we have three more commands to discuss. This Rename-Variable command will attempt to rename the TestVariableOne variable to TestVariableTwo. After that operation, we have two Get-Variable commands. The first will prove that we now have a TestVariableTwo variable and the second, that we no longer have a TestVariableOne variable. How fun right,… even though renaming a variable should not require its own command. With that, let’s take a look at the function.

function Rename-Variable {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        $Name,
        [Parameter(Mandatory)]
        $NewName
    )

    if (Get-Variable -Name $Name -Scope Global -ErrorAction SilentlyContinue) {
        Set-Variable -Name $NewName -Value (Get-Variable -Name $Name -ValueOnly -Scope Global) -Scope Global
        Remove-Variable -Name $Name -Scope Global
    }
    else {
        Write-Warning -Message "Unable to locate a variable with the name of $Name."
    }
}

You will be glad to know that not a lot of work went into writing this; it is very simple and very straightforward. Our function accepts two parameters: Name and NewName. Simple. We use Name to search for our existing variable and NewName to change its name. Notice the use of the -Scope Global parameter and parameter value. Without it, the variables that we would be working with inside the function would only be scoped to the function; they would not exist outside of the function. Providing the variable exists, we use Set-Variable inside the function to create a new/set an existing variable with the new name and take the value from the old variable and assign it as the value to the new variable. Once the new variable has been created and the old value assigned to it, we remove the previously named variable. Let’s try out the commands we saw at the beginning of this post.

Here is what happens when we try and rename a variable that does not exist.

[PS7.2.1][C:\] Rename-Variable -Name TestVariableZero -NewName TestVariableZeroZero
WARNING: Unable to locate a variable with the name of TestVariableZero.

So we can test out our function, let’s create our TestVariableOne variable and prove to ourselves that it has been created and that it now exists.

[PS7.2.1][C:\] New-Variable -Name TestVariableOne -Value ValueOne -Force
[PS7.2.1][C:\] Get-Variable -Name TestVariableOne

Name                           Value
----                           -----
TestVariableOne                ValueOne

Now, it is time to rename our variable from TestVariableOne to TestVariableTwo. The first command does just that. The next couple of commands proves that TestVariableTwo now exists with the proper value and TestVariableOne does not exist.

[PS7.2.1][C:\] Rename-Variable -Name TestVariableOne -NewName TestVariableTwo
[PS7.2.1][C:\] Get-Variable -Name TestVariableTwo

Name                           Value   
----                           -----   
TestVariableTwo                ValueOne

[PS7.2.1][C:\] Get-Variable -Name TestVariableOne
Get-Variable: Cannot find a variable with the name 'TestVariableOne'.

And that is it. If you are ever looking to rename a variable, using a built-in -Variable command, you are not going to find it. You can roll your own, however, if you really want to do that.

Saving Time with Background Jobs

If you’re like me, there’s something you know a decent amount about regarding PowerShell, but you just don’t get to use it much. Today, it’s PowerShell background jobs. If you’ve been reading my blog currently, then you know I’m right in the middle of a series regarding Splunk. In the series, I’m sending telemetry data from my function template to Splunk. The problem, although slight, is that it’s increased the duration, or length of time, the function takes to complete. No surprise. It’s running several additional commands where it polls the user and system for information. It’s only adding maybe a second more of time to the duration of the function. Still, why not determine if it’s time that can be reclaimed. Enter background jobs.

If I can collect my telemetry data in the background, while the function is doing whatever it’s supposed to be doing, then I can potentially remove any additional time added to the invocation of the function due to collecting telemetry data. Let’s take a look a couple code examples to begin.

This first function is called Start-SleepWITHOUTBackgroundJob. Notice the “without” in the name. This function will run Start-Sleep twice within the function: once for five seconds and then once for three seconds. Therefore, we’d expect the function to take around eight seconds to complete. The five second section is standing in for where we’d run our standard function code. The three second section is standing in for where we’d collect our telemetry data.

Function Start-SleepWITHOUTBackgroundJob {
    Start-Sleep -Seconds 5

    Start-Sleep -Seconds 3
} # End Function: Start-SleepWITHOUTBackgroundJob.

Measure-Command -Expression {
    Start-SleepWITHOUTBackgroundJob
}

Let’s run it a few times. As you’ll see, and just as we’d suspected, it comes in at right around the 8 second mark. If you’ve seen the output of Measure-Command then you can tell I’ve removed several of the time properties; they weren’t necessary.

Seconds           : 8
Milliseconds      : 16

Seconds           : 8
Milliseconds      : 26

Seconds           : 8
Milliseconds      : 22

The second function is called Start-SleepWITHBackgroundJob. We’ve swapped our Start-Sleep commands because we want what takes less time to happen first. It has to be what happens inside the background job. I suspect that gathering telemetry data is most always going to take less time than whatever else the function is doing. That may not always be the case, but it’s a safe choice.

Function Start-SleepWITHBackgroundJob {
    Start-Job -ScriptBlock {
        Start-Sleep -Seconds 3
    } | Out-Null

    Start-Sleep -Seconds 5
} # End Function: Start-SleepWITHBackgroundJob.

Get-Job | Remove-Job
Measure-Command -Expression {
    Start-SleepWITHBackgroundJob
}

And, look at that. We’ve shaved off three seconds from our function invocation by placing those three seconds inside of a background job. Our three seconds are running in a separate PowerShell process that executes at the same time the function sleeps for five seconds. This is going to work great for me.

Seconds           : 5
Milliseconds      : 596

Seconds           : 5
Milliseconds      : 795

Seconds           : 5  
Milliseconds      : 417

Now that we’ve proved we can use PowerShell background jobs to save time and avoid some unnecessary top-to-bottom/sequential programming, let’s do this while actually gathering some telemetry data. We’ll do two things at once and shave off some time from the overall time. The time difference may not be as dramatic as the above examples, but I’ll take anything. In fact, watch this first.

Do you see the delay? There’s a moment where my telemetry data is being gathered and sent to Splunk, before the prompt reappears. The idea is to get those milliseconds back — they add up!

As you can see below, we have another code example. This will run without a background job. It’ll sleep for five seconds (as thought it’s fulfilling its purpose), and then collect some telemetry data and display that on the screen. I’ll share the code in between each of the below regions at the end of this post in case someone finds themself interested.

Function Start-SleepWITHOUTBackgroundJob {
    Start-Sleep -Seconds 5

    #region: Obtain telemetry.
	New-Variable -Name FuncTmplHash -Value @{}
	New-Variable -Name TelemetryHashBonus -Value @{}
        #region: Determine PowerShell version.
        #endregion.
        #region: Check for other version: Windows PowerShell|PowerShell.
        #endregion.
        #region: Determine IP address(es).
        #endregion.
        #region: Determine Operating System.
        #endregion.
        #region: Determine computer tier.
        #endregion.
    $TelemetryHashBonus
    #endregion.
} # End Function: Start-SleepWITHOUTBackgroundJob.

Measure-Command -Expression {
    Start-SleepWITHOUTBackgroundJob | Out-Default
}

While the time difference isn’t too dramatic (roughly 750 milliseconds), it’s something. Something of which I want to partially reclaim. This is exactly why you see the hesitation/pause before PowerShell rewrites a fresh prompt in the above GIF. Now, let’s get this corrected.

Function Start-SleepWITHBackgroundJob {
    Start-Job -ScriptBlock {
        #region: Obtain telemetry.
        New-Variable -Name FuncTmplHash -Value @{}
        New-Variable -Name TelemetryHashBonus -Value @{}
        #region: Determine PowerShell version.
        #endregion.
        #region: Check for other version: Windows PowerShell|PowerShell.
        #endregion.
        #region: Determine IP address(es).
        #endregion.
        #region: Determine Operating System.
        #endregion.
        #region: Determine computer tier.
        #endregion.
        $TelemetryHashBonus
        #endregion.
     } -OutVariable Job | Out-Null

    Start-Sleep -Seconds 5
    Receive-Job -Id $Job.Id
} # End Function: Start-SleepWITHBackgroundJob.

Measure-Command -Expression {
    Start-SleepWITHBackgroundJob | Out-Default
}

If we take a look a the below results versus the run without the background job we can see that we’ve saved roughly 500 milliseconds, or a 1/2 a second. That’s not much; I’d agree, even though it feels like an eternity when I’m waiting for my prompt to be rewritten. I guess I should consider that this isn’t the full telemetry gathering code I use. Still, for every two invocations, I save a single second. One hundred and twenty invocations saves me a minute. If my tools are far reaching, then there’s definitely time to be saved.

It does take time to create the job and receive its data once it’s complete, so perhaps that’s eating into my return on time, as well. That makes me think of one more thing worth sharing. If you find yourself interested in implementing something like this, then it’s probably wise to not assume the background job is complete, as I’ve done in these examples. Instead of running Receive-Job, first run Get-Job and ensure your job’s State property is “Completed,” and not still “Running.” It would probably be best to put this inside a Do-Until language construct, so it can loop until you can be certain the job is completed, before receiving its data.

I said I share the telemetry gathering code, so that’s been included below. I make no guarantees that it’ll work or make sense for you, but there it is.

#region: Obtain telemetry.
New-Variable -Name FuncTmplHash -Value @{}
New-Variable -Name TelemetryHashBonus -Value @{}

#region: Determine PowerShell version.
$FuncTmplHash.Add('PSVersion',"$(If ($PSVersionTable.PSVersion.Major -lt 6) {"Windows PowerShell $($PSVersionTable.PSVersion.ToString())"} Else {
	"PowerShell $($PSVersionTable.PSVersion.ToString())"})")
$TelemetryHashBonus.Add('PSVersion',$FuncTmplHash.PSVersion)
#endregion.

#region: Check for other version: Windows PowerShell|PowerShell.
If ($FuncTmplHash.PSVersion -like 'PowerShell*') {
	$TelemetryHashBonus.Add('PSVersionAdditional',
		"$(try {powershell.exe -NoLogo -NoProfile -Command {"Windows PowerShell $($PSVersionTable.PSVersion.ToString())"}} catch {})")
} ElseIf ($FuncTmplHash.PSVersion -like 'Windows PowerShell*') {
	$TelemetryHashBonus.Add('PSVersionAdditional',
		"$(try {pwsh.exe -NoLogo -NoProfile -Command {"PowerShell $($PSVersionTable.PSVersion.ToString())"}} catch {})")
} # End If-Else.
#endregion.

#region: Determine IP address(es).
$ProgressPreference = 'SilentlyContinue'
$TelemetryHashBonus.Add('IPAddress',(Invoke-WebRequest -Uri 'http://checkip.dyndns.com' -Verbose:$false).Content -replace "[^\d\.]")
$TelemetryHashBonus.Add('IPAddressAdditional',@(Get-NetIPAddress | Where-Object -Property AddressFamily -eq 'IPv4' |
	Where-Object -FilterScript {$_ -notlike '169.*' -and $_ -notlike '127.*'}).IPAddress)
$ProgressPreference = 'Continue'
#endregion.

#region: Determine Operating System.
If ($FuncTmplHash.PSVersion -like 'Windows PowerShell*' -and $FuncTmplHash.PSVersion.Split(' ')[-1] -lt 6) {
	$TelemetryHashBonus.Add('OperatingSystem',"Microsoft Windows $((Get-CimInstance -ClassName Win32_OperatingSystem -Verbose:$false).Version)")
	$TelemetryHashBonus.Add('OperatingSystemPlatform','Win32NT') 
} Else {$TelemetryHashBonus.Add('OperatingSystem',"$($PSVersionTable.OS)")
	$TelemetryHashBonus.Add('OperatingSystemPlatform',"$($PSVersionTable.Platform)")} # End If-Else.
#endregion.

#region: Determine computer tier.
Switch ($FuncTmplHash.'Domain\Computer') {{$_ -like '*-PT0-*'} {$TelemetryHashBonus.Add('ComputerTier','T0'); break} 
{$_ -like '*-PT1-*'} {$TelemetryHashBonus.Add('ComputerTier','T1'); break}
default {$TelemetryHashBonus.Add('ComputerTier','Unknown')}} # End Switch.
#endregion.
$TelemetryHashBonus
#endregion.

Bulk Remove Variables, Carefully

Ever have one of those situations where you need to remove, or perhaps reinitialize, a set of variables and have to deal with remembering them all? This doesn’t happen often, but there are times when I need to clear, or remove, variables between loop iterations. You either need to keep track of all your variable names, or — and I wouldn’t recommend this so much — do a Remove-Variable -Name *. While the help on PowerShell 5.1.14394.1000 (5.1 preview version), says that Remove-Variable doesn’t accept wildcards, it does, and I think it always has.

Let’s start with the below example. These few lines do the following: 1. Return the number of variables in the PowerShell session, 2. Add a new variable x, 3. Return the number of variables again, 4. Remove all the variables I can (some can’t be removed), 5. Return the number of variables again (again), and 6. See if I can return my variable x.

[tommymaynard@srv01 c/~]$ (Get-Variable).Count
51
[tommymaynard@srv01 c/~]$ New-Variable -Name x -Value 'string'
[tommymaynard@srv01 c/~]$ (Get-Variable).Count
52
[tommymaynard@srv01 c/~]$ Remove-Variable -Name * -ErrorAction SilentlyContinue
[tommymaynard@srv01 c/~]$ (Get-Variable).Count
29
[tommymaynard@srv01 c/~]$ Get-Variable -Name x
Get-Variable : Cannot find a variable with the name 'x'...

While we can do this to remove the variables we’ve created in the session, we end up removing variables that weren’t necessary to remove. We’re better than this.

In the next example, we’ll add a variable prefix “dog” to all of our variables. This will allow us to find all of the variables by prefix, and then delete just those. Not a bad idea really, but it wasn’t my first thought.

[tommymaynard@srv01 c/~]$ New-Variable -Name dog1 -Value 'string1'
[tommymaynard@srv01 c/~]$ New-Variable -Name dog2 -Value 'string2'
[tommymaynard@srv01 c/~]$ New-Variable -Name dog3 -Value 'string3'
[tommymaynard@srv01 c/~]$ Get-Variable -Name dog*

Name                           Value
----                           -----
dog1                           string1
dog2                           string2
dog3                           string3

[tommymaynard@srv01 c/~]$ Remove-Variable -Name dog*
[tommymaynard@srv01 c/~]$ Get-Variable -Name dog*
[tommymaynard@srv01 c/~]$ # No results, as they've been deleted.

Had I thought of the above option first, I might’ve just gone with that idea, but let me share what I was really thinking. It’s involves the description property. When we create our variables with New-Variable, we have the option to set more than just the Name and Value. Let’s take a look at the below example, and then discuss it.

[tommymaynard@srv01 c/~]$ New-Variable -Name a -Value 'stringA' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ $StartDate = Get-Date
[tommymaynard@srv01 c/~]$ New-Variable -Name b -Value 'stringB' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ New-Variable -Name c -Value 'stringC' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c

Name                           Value
----                           -----
a                              stringA
b                              stringB
c                              stringC

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Select-Object Name,Value,Description

Name Value   Description
---- -----   -----------
a    stringA 11/03/2016 20:52:41
b    stringB 11/03/2016 20:53:05
c    stringc 11/03/2016 20:53:20

Now, it’s important to remember that a variable’s description property is a string. This means that when we put the current date in the property, it was converted to a string. That means that Thursday, November 3, 2016 20:53:20 PM became this: 11/03/2016 20:53:20. What we’ll need to do is convert it back to a datetime before we compare. We do that in the below example by casting the description property as a datetime object. If it’s not clear yet, our end goal is to remove variables that were created after a specific point in time.

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {[datetime]$_.Description -gt $StartDate}

Name                           Value
----                           -----
b                              stringB
c                              stringC

We can also use the Get-Date cmdlet to do this, as well.

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {(Get-Date -Format $_.Description) -gt $StartDate}

Name                           Value
----                           -----
b                              stringB
c                              stringC

Since we can now isolate the variables we created after a specific time, we can go ahead and blow them away. Here goes:

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {(Get-Date -Format $_.Description) -gt $StartDate} | Remove-Variable
[tommymaynard@srv01 c/~]$ Get-Variable a,b,c

Name                           Value
----                           -----
a                              stringA
Get-Variable : Cannot find a variable with the name 'b'.
At line:1 char:1
+ Get-Variable a,b,c
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (b:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Get-Variable : Cannot find a variable with the name 'c'.
At line:1 char:1
+ Get-Variable a,b,c
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (c:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Well, that’s it for today. Now you can use description property of your variables to hold the time in which they were created. This will allow you to selective remove any of them based on a start time. I like it, although the whole prefix thing might be an easier option.

PSMonday #8: Monday, June 20, 2016

Topic: Less Used Variable Properties

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

When someone sets, or assigns, a variable in Windows PowerShell, we typically expect it to look a certain way, as is demonstrated below. This example also shows how we typically return a variable’s value, after it’s been assigned.

$String = 'Welcome back to Monday.' # Assign a value to variable.
$String # Return a variable’s value.

Welcome back to Monday.

This assigns, or sets, the variable named string, the value of ‘Welcome back to Monday.’ Straightforward, right? Well, this isn’t the only way to assign a variable in PowerShell. There’s a more formal process that offers a few extra options. When we use the *-Variable cmdlets, we don’t use the dollar sign ($). The dollar sign is only used to indicate to the PowerShell parser that what follows it, will be a variable name. The difference here is that these variable cmdlets already know you’re providing a variable name.

New-Variable -Name String2 -Value 'Come on, Friday.'
Get-Variable -Name String2

Name                           Value
----                           -----
String2                        Come on, Friday.

If you choose to use the Get-Variable cmdlet to return just the value, you can use the -ValueOnly parameter, dotted-notation, or Select-Object’s -ExpandProperty parameter. In older versions of PowerShell, dotted-notation may not be an option.

Get-Variable -Name String2 -ValueOnly
(Get-Variable -Name String2).Value
Get-Variable -Name String2 | Select-Object -ExpandProperty Value

Come on, Friday.
Come on, Friday.
Come on, Friday.

I’m not here to suggest variables should always be created with New-Variable, that values should always be returned with Get-Variable, that variables should always be updated with Set-Variable, or even that we should always clear or remove variables with Clear-Variable and Remove-Variable, respectively. What I’m out to do, is tell you about a couple extra properties that are attached to our variables, that you might not know about, and how we might use them.

Let’s modify the command we used to return the value of our $String2 variable, so we return all the properties. Keep in mind, that we can do the same thing with our $String variable that was created without the New-Variable cmdlet.

Get-Variable -Name String2 | Select-Object *

Name        : String2
Description :
Value       : Come on Friday.
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

Notice that we have a Description property and an Options property. The Description property is another way to provide additional meaning to a variable. While you should strive to name your variables in a way that describes their contents, if you’re feeling up to it, you can add additional information about the variable in this property.

Set-Variable -Name String2 -Description 'Demo purposes only'
(Get-Variable -Name String2).Description

Demo purposes only

Let’s talk about the Options property next week, as it’s a bit more useful.

Protect your Variables with ReadOnly and Constant Options

I wrote a post a day to two back about creating an array variable that contained other arrays. I then went on to create additional, easier-to-remember variables, to use as the indexes. Here’s the post if you’d like to read it: http://tommymaynard.com/ql-working-with-an-array-of-arrays-2015/.

I started thinking, what if you create a variable for this easier-to-remember purpose, and then acidentally overwrite it? Well, as assumed, it is no longer going to work as first intended. Here’s a quick example starting back at the array of arrays concept.

PS C:\> Set-Variable -Name TeamWareServers -Value @(('serv01','serv02'),('serv03','serv04','serv05'))
PS C:\> $TeamWareServers
serv01
serv02
serv03
serv04
serv05
PS C:\> Set-Variable -Name f -Value 0 # Front end servers
PS C:\> $f
0
PS C:\> Set-Variable -Name b -Value 1 # Back end servers
PS C:\> $b
1
PS C:\> $TeamWareServers[$f]
serv01
serv02
PS C:\> $TeamWareServers[$b]
serv03
serv04
serv05

Although it’s easier to remember which servers are which, we have the possibility that our variables, $f and $b, could easily be overwritten. Here’s an example of overwriting the variables’ values and then not being able to use them as we did in the last example. I added an extra space after the results, of which there were none, so it’s easy to tell that these variable no longer work.

PS C:\> $f = 20
PS C:\> $b = 'newvalue'
PS C:\> $TeamWareServers[$f]
PS C:\>
PS C:\> $TeamWareServers[$b]
PS C:\>

So, how can we better protect our variables? There’s two ways we’ll discuss: ReadOnly and Constant. These two options will protect our variables from being assigned any new value(s). Take a look at this example where we’ll reset our $f and $b variables back to their original values.

PS C:\> Set-Variable -Name f -Value 0 -Option ReadOnly
PS C:\> $f
0
PS C:\> Set-Variable -Name b -Value 1 -Option Constant
Set-Variable : Existing variable b cannot be made constant. Variables can be made constant only at creation time.
At line:1 char:1
+ Set-Variable -Name b -Value 1 -Option Constant
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (b:String) [Set-Variable], SessionStateUnauthorizedAccessException
    + FullyQualifiedErrorId : VariableCannotBeMadeConstant,Microsoft.PowerShell.Commands.SetVariableCommand

In the example above, we assigned a new value to our $f variable and made it ReadOnly using the -Option parameter. When we tried to modify the $b variable to make it a Constant, we received an error. This is because we don’t have the ability to make an existing variable a Constant. In the next example, below, we’ll remove the $b variable and then recreate it with the Constant option. Keep in mind that Set-Variable will work like New-Variable, if the variable doesn’t already exist.

PS C:\> Remove-Variable -Name b
PS C:\> Set-Variable -Name b -Value 1 -Option Constant
PS C:\> $b
1

Now let’s try what we did earlier and assign new values to these variables. You’ll soon see that when the variable’s option is set to Read-Only or Constant, that we’re not able to change their values.

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

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

If you’re anything like me, then you might be wondering what the difference is between ReadOnly and Constant. We’ll let the next example help explain.

PS C:\> Remove-Variable -Name f
Remove-Variable : Cannot remove variable f because it is constant or read-only. If the variable is read-only, try the
operation again specifying the Force option.
At line:1 char:1
+ Remove-Variable -Name f
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (f:String) [Remove-Variable], SessionStateUnauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotRemovable,Microsoft.PowerShell.Commands.RemoveVariableCommand

PS C:\> Remove-Variable -Name f -Force # This works.
PS C:\>
PS C:\> Remove-Variable -Name b
Remove-Variable : Cannot remove variable b because it is constant or read-only. If the variable is read-only, try the
operation again specifying the Force option.
At line:1 char:1
+ Remove-Variable -Name b
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (b:String) [Remove-Variable], SessionStateUnauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotRemovable,Microsoft.PowerShell.Commands.RemoveVariableCommand

PS C:\> Remove-Variable -Name b -Force # This doesn't work.
Remove-Variable : Cannot remove variable b because it is constant or read-only. If the variable is read-only, try the
operation again specifying the Force option.
At line:1 char:1
+ Remove-Variable -Name b -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (b:String) [Remove-Variable], SessionStateUnauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotRemovable,Microsoft.PowerShell.Commands.RemoveVariableCommand

When a variable is ReadOnly, the variable can be removed. This does, however, require the use of the -Force parameter. Consider that a safety net. The difference here, is that when a variable is a Constant, it cannot be removed, even with the use of the -Force parameter. If you want to remove a user-defined Constant variable, you’re going to have to end your PowerShell console and start another one.

Keep these options in mind if you ever want to protect the value(s) in your variables. While we’re at it, we probably should have made the array (of arrays) variable, $TeamWareServers, ReadOnly or Constant, too.

Working with an Array of Arrays

There’s a group of servers that I use for a specific project. I can never remember their names or how those correspond to their roles (web front end vs. data back end). Although I’ve updated their Active Directory (AD) descriptions, and created two, specifically named AD groups for them, I wanted an even quicker way to remind myself whose who. With that in mind, I updated my profile with a custom variable that is an array, of arrays.

In this Quick Learn, we’ll work with an example that actually uses three groups of roles — our DCs, our web servers, and our SQL servers. As you’ll soon see, our servers are named after Santa Claus’ reindeer. These names have nothing to do with the role of these servers, and since their names are all closely related, it’s difficult to remember who does what.

This first example, below, demonstrates how we create a new variable, or modify an already existing variable. When we echo the contents of our variable, we get all the computer names, regardless of what array they are in, within the base array. The term ‘base array’ is probably not something you’ll hear or read about outside this post. It’s being used here to help distinguish the array that holds all the other arrays — the container array.

PS C:\> Set-Variable -Name Computers -Value @(('dasher','vixen','cupid'),('comet','dancer','donner'),('blitzen','rudolph','prancer'))
PS C:\> $Computers
dasher
vixen
cupid
comet
dancer
donner
blitzen
rudolph
prancer

We can use an index to return one of the arrays within the base array. In the examples below, you can see how each can be accessed. This is probably a good time to review indexes: The first item in an array is index zero, the second item is index one, the third item is index two, and so on.

PS C:\> $Computers[0]
dasher
vixen
cupid
PS C:\> $Computers[1]
comet
dancer
donner
PS C:\> $Computers[2]
blitzen
rudolph
prancer

In the following example, we can use two indexes to access a specific server. The first index represents which array (within the base array) I want to return, like it did above, and the second index indicates which server I want to return.

PS C:\> $Computers[0][2]
cupid
PS C:\> $Computers[1][0]
comet
PS C:\> $Computers[2][1]
rudolph

The difficult part is going to be able to remember which index is for the DCs, the web servers, or the SQL servers. In that case, I’ll create three more variables to use in place of those index integers.

PS C:\> Set-Variable -Name DCs -Value 0
PS C:\> Set-Variable -Name Web -Value 1
PS C:\> Set-Variable -Name SQL -Value 2
PS C:\> $DCs,$Web,$SQL
0
1
2

With the combination of my $Computers variable and the three, role-specific variables, I am able to easily return the set of servers I want.

PS C:\> $Computers[$DCs]
dasher
vixen
cupid
PS C:\> $Computers[$Web]
comet
dancer
donner
PS C:\> $Computers[$SQL]
blitzen
rudolph
prancer

Now that we have this all figured out, I can use them in different commands. Here’s a couple examples:

PS C:\> $Computers[$SQL] | ForEach-Object {Test-Connection $_ -Count 1}

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)
------        -----------     -----------      -----------                              -----    --------
TOMMYMS PC... blitzen         10.10.10.80                                               32       1
TOMMYMS PC... rudolph         10.10.10.81                                               32       1
TOMMYMS PC... prancer         10.10.10.82                                               32       1

PS C:\> Get-Service -ComputerName ($Computers[$DCs]) -Name *bit* | Select-Object MachineName,Name,Status | Format-Table -AutoSize

MachineName Name  Status
----------- ----  ------
dasher      BITS Stopped
vixen       BITS Running
cupid       BITS Stopped

The unfortunate thing about hard coding computer names in our profile, is that we’ll run into problems when new servers are added and old ones are decommissioned. Therefore, we’re going to use AD groups — something I mentioned earlier — to populate our array variable. We’ll pull our DCs from the Domain Controllers, and our web servers and SQL servers from two fictions AD groups: WebServers and SQLBoxes. Here’s the command we’ll add to our profile to ensure we always have the correct server names. While this can all be on a single line command I’ve added line breaks to give it an easier-to-read appearance.

Set-Variable -Name Computers -Value @(
    ((Get-ADDomainController -Filter *).Name),
    ((Get-ADGroupMember -Identity WebServers).Name),
    ((Get-ADGroupMember -Identity SQLBoxes).Name)
)

And, that’s it. If for some reason you’re using PowerShell 2.0 or lower (and I really hope you’re not), you’ll need to include Import-Module -Name ActiveDirectory in your profile. As well — and it should go without saying — you’ll need to be working from a computer that actually has the ActiveDirectory module installed. Although I’m using PowerShell 4.0, I still include Import-Module -Name ActiveDirectory in my profile, so I don’t have to wait for it to auto load, when I run my first AD cmdlet.

about_Variables

This post is the help rewrite for about_Aliases. While the help files for Windows PowerShell are invaluable, the idea behind a rewrite is so true beginners might even better understand the help file concepts. At times, some things discussed in the Windows PowerShell help file will not be included in a help rewrite. Therefore, it is always best to read the actual help file after reading this post. (PS3.0)

A variable in Windows PowerShell is a storage container in memory that can hold a value or values. Variables can store numbers, letters, strings (a sequence of numbers, letters, and/or other characters), and the results of a command that has been run in Windows PowerShell. Variables are defined by a dollar sign ($) and a string of text that follows.

PS C:\> $myVariable
PS C:\> $Process
PS C:\> $UserName
PS C:\> $a
PS C:\> $Var

Windows PowerShell has three types of variables. There are user-created variables, automatic variables, and preference variables. User-created variables are created by a user such as the variables in this example.

PS C:\> $Name = 'Macy Jones'
PS C:\> $Number = 10

Automatic variables store the state of Windows PowerShell, such as the $PSHOME variable, which stores the install location of Windows PowerShell. This type of variable cannot be changed by a user. This example shows what happens when a user tries to change the value of the $PSHOME automatic variable.

PS C:\> $PSHOME
C:\Windows\System32\WindowsPowerShell\v1.0
PS C:\> $PSHOME = 'C:\Windows'
Cannot overwrite variable PSHOME because it is read-only or constant.
At line:1 char:1
+ $PSHOME = ‘C:\Windows’
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (PSHOME:String) [], SessionStateUnauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotWritable

Preference variables store a default value but can be changed. These types of variables include the $MaximumAliasCount variable that stores the maximum number of aliases Windows PowerShell will store (the default value for this variable is 4096). This example shows how this variable’s value can be changed.

PS C:\> $MaximumAliasCount
4096
PS C:\> $MaximumAliasCount = 2000
PS C:\> $MaximumAliasCount
2000
PS C:\> $MaximumAliasCount = 4096

Variables are created by combining a dollar sign ($) and a text string. It is beneficial to name variables in such a way that the name helps define what the variable will store. Then use the = operator to assign, or set, the variable with a value. This example shows two variables, $Name and $Number, being set to two different values. To display the value assigned to a variable, type a dollar sign and the variable name and press Enter.

PS C:\> $Name = 'Macy Jones'
PS C:\> $Number = 10
PS C:\> $Series = 1,2,3
PS C:\> $Name
Macy Jones
PS C:\> $Number
10
PS C:\> $Series
1
2
3

The variable’s values can also be displayed using the Write-Output cmdlet, as well as the aliases for Write-Output, write and echo. This is used more often in scripts as opposed to the Windows PowerShell console.

PS C:\> Write-Output $Name
Macy Jones
PS C:\> write $Name
Macy Jones
PS C:\> echo $Name
Macy Jones

While the Write-Host cmdlet can also display a variable’s value, in most cases, it should not be used in place of Write-Output.

PS C:\> Write-Host $Name
Macy Jones

Variable names are not case-sensitive. The case of a variable name does not matter when it is assigned or used. This example also indicates how to assign a new value to a variable that already had a value.

PS C:\> $name
Macy Jones
PS C:\> $NAME
Macy Jones
PS C:\> $NamE
Macy Jones
PS C:\> $NAME = 'Lance Andrews'
PS C:\> $name
Lance Andrews
PS C:\> $name = 'Macy Jones'
PS C:\> $NAMe
Macy Jones

Variables can hold the results of commands. The first part of this example uses the Get-Process cmdlet to immediately display the first four running processes. In the second part of the example, the first four running processes are stored in a variable and then displayed when the variable is entered.

PS C:\> Get-Process | Select-Object -First 4

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    224      19     3436        772   110    16.80   4612 ALMon
    164      14     2476       2100    44     5.53   2744 ALsvc
     77       9     1336       5288    75   140.70   4076 ApMsgFwd
     90       8     1372       5852    76   162.11   4324 ApntEx

PS C:\> $Processes = Get-Process | Select-Object -First 4
PS C:\> $Processes

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    224      19     3436        772   110    16.80   4612 ALMon
    164      14     2476       2100    44     5.53   2744 ALsvc
     77       9     1336       5288    75   140.73   4076 ApMsgFwd
     90       8     1372       5852    76   162.11   4324 ApntEx

This examples sets, or assigns, the $Date variable to the results of the Get-Date cmdlet.

PS C:\> $Date = Get-Date
PS C:\> $Date

Thursday, May 01, 2014 9:20:30 PM

The Clear-Variable cmdlet, or clv alias, in this example, will remove the value that has been assigned to a variable without destroying, or removing, the variable itself. When referencing the variable, the dollar sign ($) is not used with either of these two cmdlets or with the Get-Variable cmdlet. The Get-Variable cmdlet will list all the variables in the session or list a single variable when a variable name is supplied.

PS C:\> $Name
Macy Jones
PS C:\> Clear-Variable Name
PS C:\> $Name
PS C:\> Get-Variable Name

Name                           Value
----                           -----
Name

The Remove-Variable cmdlet, or rv alias, in this example, will completely remove a variable and its stored value from memory.

PS C:\> $Color = 'Green'
PS C:\> $Color
Green
PS C:\> Remove-Variable Color
PS C:\> $Color
PS C:\>
PS C:\> Get-Variable Color
Get-Variable : Cannot find a variable with the name ‘Color’.
At line:1 char:1
+ Get-Variable Color
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Color:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Variables can store different types of data. Normally they make their type determination based on the value(s) assigned to them. They can store integers, strings, arrays, and more. A single variable, when it is an array, can contain different types of data at the same time. The examples below use the Get-Member cmdlet to return properties (and more) about our variable. The Select-Object cmdlet has also been used to help filter what is returned.

PS C:\> $a = 12
PS C:\> $a | Get-Member | Select-Object TypeName -Unique

TypeName
--------
System.Int32

PS C:\> $a = 'Word'
PS C:\> $a | Get-Member | Select-Object TypeName -Unique

TypeName
--------
System.String

PS C:\> $a = 12,'Word'
PS C:\> $a | Get-Member | Select-Object TypeName -Unique

TypeName
--------
System.Int32
System.String

A variable can be forced to be a certain type by casting the variable. In the first part of the example below, the variable $Number will be cast with an int type (int, as in, integer). Even though the variable is cast as an integer, it is able to handle being assign a string value of “12345.” This is because the variable can change that string into a numeric value. It cannot do the same thing with the string “Hello.”

Further down in the example, the $Words variable has been cast as a string. When it is set to a numeric value it converts the numeric value into a string value. If the variable is used in a mathematical equation, such as an addition equation, it does not add the two values and instead will concatenate, or join them.

PS C:\> [int]$Number = 10
PS C:\> $Number
10
PS C:\> $Number = '12345'
PS C:\> $Number
12345
PS C:\> $Number = 'Hello'
Cannot convert value “Hello” to type “System.Int32”. Error: “Input string was not in a correct format.”
At line:1 char:1
+ $Number = ‘Hello’
+ ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException
PS C:\> [string]$Words = 'Hello'
PS C:\> $Words
Hello
PS C:\> $Words = 2
PS C:\> $Words
2
PS C:\> $Words + 10
210
PS C:\> $Number
12345
PS C:\> $Number + 10
12355

There are differences between using single quotes–which should be used as often as possible–and double-quotes. Single quotes around a variable will not expand the value stored in the variable; however, using double quotes will expand the variable.

PS C:\> $Name = 'Macy Jones'
PS C:\> 'Her name is $Name'
Her name is $Name
PS C:\> "Her name is $Name"
Her name is Macy Jones

Although variable names can include spaces and special characters, it should be avoided as it can quickly lead to confusion. Using spaces and special characters requires the variable name be enclosed in curly brackets {}.

PS C:\> ${!@#$} = 'Monday'
PS C:\> ${Favorite Day} = 'Friday'
PS C:\> ${!@#$}
Monday
PS C:\> ${Favorite Day}
Friday

Windows PowerShell creates a variable drive that looks and acts a lot like a file system drive. You can access data in the variable drive the same way things are accessed in a file system. The first example uses Get-ChildItem to get the first 4 folders in C:\Windows. The second example does the same thing but instead returns the first four variables in the variable drive.

PS C:\> Get-ChildItem C:\Windows | select -First 4

    Directory: C:\Windows

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----         11/5/2013   3:31 PM            ADAM
d----         7/13/2009  10:32 PM            addins
d----         7/13/2009   8:20 PM            AppCompat
d----         4/11/2014   5:22 PM            AppPatch

PS C:\> Get-ChildItem variable:\ | select -First 4

Name                           Value
----                           -----
!@#$                           Monday
$                              4
?                              True
^                              Get-ChildItem

The only other variable cmdlet that was not discussed is the New-Variable cmdlet. This cmdlet is often not used since a variable can be created without it. The first example below shows how to return all the variable-related cmdlets. The second example shows how to use New-Variable.

PS C:\> Get-Command *-Variable

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Clear-Variable                                     Microsoft.PowerShell.Utility
Cmdlet          Get-Variable                                       Microsoft.PowerShell.Utility
Cmdlet          New-Variable                                       Microsoft.PowerShell.Utility
Cmdlet          Remove-Variable                                    Microsoft.PowerShell.Utility
Cmdlet          Set-Variable                                       Microsoft.PowerShell.Utility

PS C:\> New-Variable -Name DaysInYear -Value 365
PS C:\> $DaysInYear
365

Bonus Information

There may come a time when two (or more) variables need to be set to the same value. These do not need to be set individually. This first example shows how to set two variables at the same time and the second example shows how to set three variables at the same time.

PS C:\> $a = $b = 'Windows PowerShell'
PS C:\> $a
Windows PowerShell
PS C:\> $b
Windows PowerShell
PS C:\> $x = $y = $z = 42
PS C:\> $x
42
PS C:\> $y
42
PS C:\> $z
42

Real World

When values of a variable are displayed in the console, it will very rarely follow the Write-Output cmdlet. This cmdlet is most often used in scripts than it is with commands written in the console.

Learn More

This information, and more, are stored in the help file about_Variables that comes with Windows PowerShell. This information can be read by typing any of the commands below. The first example will display the help file in the Windows PowerShell console, the second example will open the full help in its own window, and the third example will send the contents of the help file to the clipboard (so it can be pasted into Word, Notepad, etc.), and the fourth example will open the help file in Notepad.

PS C:\> Get-Help about_variables
PS C:\> Get-Help about_variables -ShowWindow
PS C:\> Get-Help about_variables| clip
PS C:\> Notepad $PSHOME\en-us\about_Variables.help.txt