Category Archives: Quick Learn

Practical examples of PowerShell concepts gathered from specific projects, forum replies, or general PowerShell use.

Get Multiple Percentages of Multiple Values

Before my parents decided to trade in their truck at the dealership this week, we began to discuss the possibility of me selling it for them. Back when we started the discussion, I was thinking about possible prices for the truck, and the payout for me. Go figure, but, much like other things, I was able to incorporate Windows PowerShell into this, too.

Had this actually happened, we were going to need to determine how much I was going make in this deal. Here’s how I saw the options:

– They indicate an amount they want for the truck, and everything over that amount is mine.
– They give me a set dollar amount to sell the truck, regardless of the purchase price.
– They agree to a percentage of the final purchase price.

This last option was the one that had me flip over to my PowerShell console. You see, I often use the console as my calculator. I’m faster at typing out my math problems there, than I am using the built-in calculator in Windows.

Let’s use the possible purchase prices of $12,500, $13,000, $13,500, $14,000, and $14,500. In case you’re interested, it was a 2005 F150 SuperCrew Cab with under 70,000 miles — you read that right, less than 7,000 miles per year. What I wanted to determine was how much my parents and I would each make if I received 1%, 2.5%, 5%, 7.5%, 10%, and 12% of each of the possible purchase prices. Enter PowerShell.

The first thing I did was pipe each of the possible purchase prices to the ForEach-Object cmdlet and write their values to the screen. The results indicated that each value was properly crossing the pipeline.

12500,13000,13500,14000,14500 | ForEach-Object {
    Write-Output -Verbose $_
}
12500
13000
13500
14000
14500

Next, I created a variable for each percentage, based on a calculation. On the way into the loop, it would multiply the possible purchase price, of that iteration, by the percentage amount and store it in the proper variable. Notice that I have variables with dots (or periods, or decimals) in them, such as ${7.5Percent}. In order to use something other than a letter, number, or underscore, we need to wrap the variable in curly brackets. It’s not recommend to use variable with anything other than letters and numbers, but I thought it made sense in this case.

12500,13000,13500,14000,14500 | ForEach-Object {
    $12Percent = $_ * .12
    $10Percent = $_ * .1
    ${7.5Percent} = $_ * .075
    $5Percent = $_ * .05
    ${2.5Percent} = $_ * .025
    $1Percent = $_ * .01
}

After these variables were assigned, I created a header and footer that helped to separate the results from one another. Remember, each iteration through the loop prints these two lines of asterisks. Notice that the top row will include the current possible purchase price in the middle. The colors will also help separate the different purchase prices and the percentages.

12500,13000,13500,14000,14500 | ForEach-Object {
    $12Percent = $_ * .12
    $10Percent = $_ * .1
    ${7.5Percent} = $_ * .075
    $5Percent = $_ * .05
    ${2.5Percent} = $_ * .025
    $1Percent = $_ * .01

    Write-Host -Object "*****$_*****" -ForegroundColor Green

    Write-Host -Object '***************' -ForegroundColor Green
}

Here’s how this displayed at this point.

Get-Multiple-Percentages-of-Multiple-Values00

In this next example, you can see where included the 90% value and 10% value of each possible purchase price. To determine these two values, we subtracted our 10% amount from the full purchase price (for 90%). The second value was already determined as part of setting the variable at the beginning of the loop, so we echoed what was already in the variable.

12500,13000,13500,14000,14500 | ForEach-Object {
    $12Percent = $_ * .12
    $10Percent = $_ * .1
    ${7.5Percent} = $_ * .075
    $5Percent = $_ * .05
    ${2.5Percent} = $_ * .025
    $1Percent = $_ * .01

    Write-Host -Object "*****$_*****" -ForegroundColor Green

    Write-Host -Object "90%: $($_ - $10Percent)" -ForegroundColor Yellow
    Write-Host -Object "10%: $10Percent" -ForegroundColor Gray

    Write-Host -Object '***************' -ForegroundColor Green
}

Here’s what it looked like when the code above was run.

Get-Multiple-Percentages-of-Multiple-Values01

Here’s the final code and results.

12500,13000,13500,14000,14500 | ForEach-Object {
    $12Percent = $_ * .12
    $10Percent = $_ * .1
    ${7.5Percent} = $_ * .075
    $5Percent = $_ * .05
    ${2.5Percent} = $_ * .025
    $1Percent = $_ * .01

    Write-Host -Object "*****$_*****" -ForegroundColor Green

    Write-Host -Object "87.5%: $($_ - $12Percent)" -ForegroundColor Yellow
    Write-Host -Object "12.5%: $12Percent" -ForegroundColor Gray

    Write-Host -Object "90%: $($_ - $10Percent)" -ForegroundColor Yellow
    Write-Host -Object "10%: $10Percent" -ForegroundColor Gray

    Write-Host -Object "92.5%: $($_ - ${7.5Percent})" -ForegroundColor Yellow
    Write-Host -Object "7.5%: ${7.5Percent}" -ForegroundColor Gray

    Write-Host -Object "95%: $($_ - $5Percent)" -ForegroundColor Yellow
    Write-Host -Object "5%: $5Percent" -ForegroundColor Gray

    Write-Host -Object "97.5%: $($_ - ${2.5Percent})" -ForegroundColor Yellow
    Write-Host -Object "2.5%: ${2.5Percent}" -ForegroundColor Gray

    Write-Host -Object "99%: $($_ - $1Percent)" -ForegroundColor Yellow
    Write-Host -Object "1%: $1Percent" -ForegroundColor Gray

    Write-Host -Object '***************' -ForegroundColor Green
}

Get-Multiple-Percentages-of-Multiple-Values02

In the results, above, I’ve only included the first three possible purchase prices, to save space. While all of this could have been figured out with a calculator, there’s no possible way it could have been done as quickly — at least for someone that knows PowerShell. I didn’t bother to create an object in this instance, although it would have made sense. In my mind this wasn’t going to be reused any more than it was this one time, and so I didn’t add the additional effort.

I hope this is helpful for someone. Cheers.

Add a Dynamic Number of Asterisks

Recently, on a Windows PowerShell forum, a person wanted a script to run each time they connected to a PowerShell remote endpoint — such as a local profile does when you open the PowerShell, console host. I recommended a couple options: Create a new endpoint, and use a ScriptsToProcess .ps1 file, or modify the default, Microsoft.PowerShell endpoint, using the Set-PSSessionConfiguration cmdlet.

To use the second option I recommended, you need to add a start up script, such as the example below does. Keep in mind that you can name the .ps1 file anything you’d like. That said, I typically name these files so that I’m certain what they go with — such as, which endpoint.

PS> Set-PSSessionConfiguration -Name Microsoft.PowerShell -StartUpScript C:\Microsoft.PowerShell.StartUpScript.ps1

In order to test my updated, Microsoft.PowerShell endpoint (on my desktop computer), I decided I would add a couple informational lines of text to the script. One would indicate the “remote” computer’s name, and the other would provide the date and time. Here’s that output.

PS> Enter-PSSession -ComputerName . # This dot indicates the local computer
You are connected to TOMMYSCOMPUTER.
6/11/2015 21:17:15 PM
[localhost]: PS C:\>

As I stared at this, I decided I would prefer that the date and time was centered under the first line, with asterisks on either side, so that both lines were the same length. This means I would need to dynamically determine how many asterisks I would need on each side of my date and time, so that the asterisks, and the date and time text, would fill up as much space as the line above. It was a new PowerShell challenge, and so I ran with it.

Before we go further, let me start by showing you an image of the end results, in case the explanation wasn’t clear. Then, we’ll walk though what I did to accomplish this task.

Add-a-Dynamic-Number-of-Asterisks-2015-01

I’ve broken down my start up script into five different parts. As you’ll see, I did things very procedurally. I wasn’t worried about using a minimal amount of commands and variables. For those with more experience, you’ll easily see how things could have been much tidier. Let’s start with the first two lines.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

These two lines set one variable each: $Computer and $Date. Each of these values will be used in the next example.

$TopLength = $Computer.Length
$BottomLength = $Date.Length

This section, above, determines and stores the character length of the $Computer and $Date variables. We are going to need to add asterisks for the difference between these two lengths.

$DiffInLength = $TopLength - $BottomLength
$Before = [math]::Round($DiffInLength/2)
$After = $DiffInLength - $Before

This third section puts the difference between $TopLength and $BottomLength into a variable called $DiffInLength. It then creates a $Before variable to store 1/2 of $DiffInLength — it will round up, if this amount isn’t a whole number. It then creates an $After variable, which will hold the leftovers from the previous calculation.

$Before = '*' * $Before
$After = '*' * $After

Next, as seen above, we’ll overwrite our $Before and $After variables with new values. Each of those values will be a set of asterisks. How do we know how many? We multiply the asterisk symbol (a string character) by the numeric values stored in the $Before and $After variables.

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

If you’re still following along, we then write out our two lines. The first line is made up of the $Computer variable. Following that line, we write our first set of asterisks, the date, and then the second set of asterisks.

This might have been a lot to take in, so you may consider copying and pasting each line into your PowerShell host and watch as it creates the two lines for you. Regardless of the length of your computer name, your two lines will be the same length, too.

Now, that last statement isn’t always true. What if we changed $Computer to just be the computer name, such as $Computer = $env:COMPUTERNAME? I know what happens; it’s throws errors and won’t let you connect to the endpoint. This problem begins with us subtracting a larger number from a smaller number. I’ve added some logic to skip a good portion of our script, if the $TopLength is smaller than $BottomLength. I’ve include the full script below.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

$TopLength = $Computer.Length
$BottomLength = $Date.Length

If ($TopLength -gt $BottomLength) {
    $DiffInLength = $TopLength - $BottomLength
    $Before = [math]::Round($DiffInLength/2)
    $After = $DiffInLength - $Before
    $Before = '*' * $Before
    $After = '*' * $After
}

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

In case someone wants to a see a tidier version of this same script, then here it is. We still use the $Before and $After variables, but no others, outside of our $Computer and $Date variables.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

If ($Computer.Length -gt $Date.Length) {
    $Before = '*' * ([math]::Round(($Computer.Length - $Date.Length)/2))
    $After = '*' * $Before.Length
}

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

 

Compare Roles and Feature between Servers

We have many different ways to build our servers so they are, and stay, the same: Desired State Configuration, is one way. In case you’re not there yet (and, I’m not, entirely), you may need to compare the roles and features between your servers to check for any differences.

In this first example, we’ll create two variables to store the installed roles and features of two different servers. Keep in mind that I’m working from a Windows 8.1 client, and two Server 2012 R2 servers. You’ll need to make sure you’re using these versions, as the Get-WindowsFeature cmdlet in 8.1 returns an error when trying to connect to a 2008 R2 server. As well, some of the Roles and Features’ names have changed. Therefore, I decided to only focus on 2012 R2.

PS> $DC01 = Get-WindowsFeature -Computer DC01 | Where-Object InstallState -eq Installed
PS> $NewDC = Get-WindowsFeature -Computer NewDC | Where-Object InstallState -eq Installed

Now that we have our populated variables, we can do our comparison between the two on their Name properties.

PS> Compare-Object -ReferenceObject $DC01.Name -DifferenceObject $NewDC.Name

InputObject                                                 SideIndicator
-----------                                                 -------------
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

If you want to view the roles and features that are installed on both servers, then include the -IncludeEqual parameter on Compare-Object, such as in the example below.

PS> Compare-Object -ReferenceObject $DC01.Name -DifferenceObject $NewDC.Name -IncludeEqual

InputObject                                                 SideIndicator
-----------                                                 -------------
NET-Framework-Features                                      ==
NET-Framework-Core                                          ==
NET-Framework-45-Features                                   ==
NET-Framework-45-Core                                       ==
NET-WCF-Services45                                          ==
NET-WCF-TCP-PortSharing45                                   ==
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

What might not be immediately evident for some, is that we could have skipped setting the first two variables and only used a single Compare-Object command. It can retrieve the information from each server and then compare it, at nearly the same time. We’ve remove the -IncludeEqual parameter is this example.

Compare-Object -ReferenceObject ((Get-WindowsFeature -ComputerName DC01 | Where-Object InstallState -eq Installed).Name) -DifferenceObject ((Get-WindowsFeature -ComputerName NewDC | Where-Object InstallState -eq Installed).Name)

InputObject                                                 SideIndicator
-----------                                                 -------------
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

Based on these results, and the ones further above, we have determined that our reference computer, DC01, has five additional packages installed.

Script Sharing – Quickly Remove and Import a Module (Reset-Module)

Often in Windows PowerShell module development, you’ll need to remove and import a module again, and again, to get the newest changes to your functions. It wasn’t long after some recent coding, that I tired from entering rmo mymodule and ipmo mymodule (the rmo alias is for Remove-Module and ipmo is used for Import-Module). Even when I added both commands to the same line, with a semi-colon in between: rmo mymodule; ipmo mymodule, and used wildcards: rmo mym*;ipmo mym*, I was still annoyed it was taking too much time.

Enter this quick function and alias:

Set-Alias -Name dump -Value Reset-Module
Function Reset-Module {
    [CmdletBinding()]
    Param ()

    Begin {
    } # End Begin.

    Process {
        Write-Verbose -Message 'Removing module.'
        Remove-Module -Name mymodule -ErrorAction SilentlyContinue -Verbose:$false
        Write-Verbose -Message 'Adding module.'
        Import-Module -Name mymodule -Verbose:$false
    } # End Process.

    End {
    } # End End.
} # End Function.

Now, I simply enter dump and it will remove and import the module again, with the newest changes. It’s PowerShell: You can use it to develop, and use it to speed up development.

Note: In case you’re thinking it, yes, there are many things that can be improved in this function: checking for the module before trying to remove it (and eliminating the -ErrorAction parameter in Remove-Module), not hard-coding the module’s name and using a parameter instead, and adding help. Even so, I wanted to throw something together quickly that probably wouldn’t live long after the end of the module’s development. Providing it does, I can add some refinements later.

Fix Register-PSSessionConfiguration Error 1326

I guess if you do this long enough, you start finding and reporting on errors before anyone else does. My decision to write today began because of this error message: “Register-PSSessionConfiguration : The verification of the runAs user credentials failed with the error 1326.”

After receiving the error, I took it to Google, and wrapped it in double quotes, only to find there were no relevant results. Once I had a solution, I decided I should do my part to help the next person that encounters this error. As you may be able to conclude, the error message appeared when I tried to register a PowerShell constrained endpoint, using the Register-PSSessionConfiguration cmdlet’s -RunAsCredential parameter.

The primary reason I figured this out so quickly, was because I noticed that the user logon name I chose ended up being longer than my ‘Pre-Windows 2000 logon.’ Think SamAccountName (and its length limitation) vs. the Name property.

It’s apparent (now) that the Register-PSSessionConfiguration’s -RunAsCredential Parameter is expecting the username portion of the PSCredential object to be the SamAccountName, even if the help file doesn’t explicitly indicate that. Hope this helps someone else.

Update: I decided to do the same Google Search I did last week (for the error message), and now something is showing up: this post. Yea, Internet!

Fix-Register-PSSessionConfiguration-Error-1326-01

Give a Parameter a Default Value (Part II)

Part I: http://tommymaynard.com/quick-learn-give-a-parameter-a-default-value-2015
Part III: http://tommymaynard.com/quick-learn-give-a-parameter-a-default-value-part-iii-2016

An old co-worker and friend contacted me last week. We hadn’t chatted since the last time I was interviewing for a job — a job I have now (well, pretty close anyway). I remember telling him I wanted a job that allowed me to work with Windows PowerShell. While he probably wasn’t surprised to hear this job did just that, he might have been, to hear I had started this site — a site using my name, and all about PowerShell. It’s a pretty serious commitment to associate your name with a technology, and continue to provide new content.

He mentioned to me that he wasn’t aware of the $PSDefaultParameterValues preference variable until he read this post. No joke, but it’s exciting to know that something I wrote was helpful for him. You see, this isn’t your average intelligence IT Pro, no, this is walking, talking IT genius. You know the kind — one of the ones you’d hire first, if you opened your own consulting business. In fact, during that same first chat, I discovered that he spoke at the Microsoft Higher-Education Conference in October 2014. He’s definitely doing something right.

As last weekend passed, I occasionally thought about that $PSDefaultParameterValues post and decided I should add something more to it, especially since I highlighted what I would consider to be the less used approach for populating this variable. First off, for those that haven’t read the previous post, the $PSDefaultParameterValues variable allows you to specify a default value for a cmdlet’s, or function’s, parameter(s).

For example, if I wanted the Get-Help cmdlet to always include the -ShowWindow parameter, then I can add that default value to the cmdlet like so:

PS C:\> $PSDefaultParameterValues
PS C:\> # Proof the variable is empty.
PS C:\> $PSDefaultParameterValues.Add('Get-Help:ShowWindow',$true)
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-Help:ShowWindow            True

With this value set, every time we use the Get-Help cmdlet, it will include the -ShowWindow parameter, even though I don’t specifically enter it at the time the command is run. In the previous example, we use the .Add() method to add a key-value pair; however, I suspect that most people will actually be more likely to use a hash table to add the cmdlet and parameter, and its new, default parameter value. Here’s the same example as above using the hash table option.

PS C:\> $PSDefaultParameterValue
PS C:\> # Proof the variable is empty.
PS C:\> $PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true}
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-Help:ShowWindow            True

Now for the reason I led with with .Add() method: If you use the hash table option above, and the variable is already populated, then it’s going to overwrite the value it’s already storing, unless we use the += assignment operator. Follow along in the next few examples under the assumption that $PSDefaultParameterValues, is already populated from the previous example.

PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-Help:ShowWindow            True

PS C:\> $PSDefaultParameterValues = @{'Get-ADUser:Server' = 'MyClosestDC'}
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-ADUser:Server              MyClosestDC

In the example above, using the = assignment operator overwrote our Get-Help ShowWindow key-value pair, as it would with any populated variable. The idea here is that we either need to assign all the parameter default values at once (when using a hash table), or use the += assignment operator to append other key-value pairs. Here’s both ways:

PS C:\> $PSDefaultParameterValues
PS C:\> # Proof the variable is empty.
PS C:\> $PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true;'Get-ADUser:Server' = 'MyClosestDC'}
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-ADUser:Server              MyClosestDC
Get-Help:ShowWindow            True

PS C:\> $PSDefaultParameterValues = $null
PS C:\> $PSDefaultParameterValues
PS C:\> # Proof the variable is empty.
PS C:\> $PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true}
PS C:\> $PSDefaultParameterValues += @{'Get-ADUser:Server' = 'MyClosestDC'}
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-ADUser:Server              MyClosestDC
Get-Help:ShowWindow            True

That’s all there is to it. If you’re going to use a hash table and assign values to your $PSDefaultParameterValues at different times, then be sure to use the correct assignment operator, or use the .Add() method — it’s up to you. For me, I populate this variable in my profile, so overwriting it, is something in which I’m not concerned. Thanks for reading!

Create a Function to Open Internet Explorer (Like in Run)

Even before Windows PowerShell, I strove to do things quickly. One such thing was to open Internet Explorer (IE) from the Run dialog. In the image below, you can see how you can enter iexplore followed by a space and then a URL. When you press OK, it will launch IE and direct itself to the URL that was passed along. If no URL is provided, it will open the home page.

Create Function to Open Internet Explorer (Like in Run)01

When I transferred this knowledge to PowerShell, I was sad to see that entering the same thing in the console host resulted in an error: “The term ‘iexplore’ is not recognized as the name of a cmdlet, function, script file, or operable program.”  I wasn’t overly concerned about it, moved on, and mostly forgot about it.

After spending last week at the PowerShell Summit North America 2015, I saw several demos from speakers that included ‘start iexplore <URL>,’ where start is an alias for Start-Process. Use Get-Alias to see this yourself: Get-Alias -Name start. I decided I would write up a simple function, to add to my profile, that would allow me to just use iexplore again.

With the function below, in place, I can enter iexplore in PowerShell to open Internet Explorer to the home page, or iexplore bing.com to open Internet Explorer to bing.com, for example — pretty straightforward. Here it is: the little function that saves me six keystrokes every time I use it. Don’t worry, it won’t be long before I make up for the lost keystrokes from writing the function itself.

Function iexplore {
	Param ([string]$Url)

	If ($Url) {
		start iexplore $Url
	} Else {
		start iexplore
	}
}

And, here’s the results.

Create Function to Open Internet Explorer (Like in Run)02

Before we close for the day, we should probably makes a couple changes to our function. We should change the function name so it uses an approved verb, along with the verb-noun naming convention, and then create an alias (iexplore), to call the function.

Set-Alias -Name iexplore -Value Open-InternetExplorer

Function Open-InternetExplorer {
	Param ([string]$Url)

	If ($Url) {
		start iexplore $Url
	} Else {
		start iexplore
	}
}

Give a Parameter a Default Value

Part II: http://tommymaynard.com/quick-learn-give-a-parameter-a-default-value-part-ii-2015/

If you’re into Windows PowerShell, then you probably think a little like me. You want to do things fast, and efficiently. There’s a reason we pound out commands and write scripts: we want to make the most of our time, and PowerShell let’s us do that. With that in mind, I decided I should modify a cmdlet I often use, so that using it takes less time, by default.

The Test-Connection cmdlet does for us what ping always has. It sends ICMP request packets to a remote computer or device. If the remote computer or device sends us a reply, then we know the device is up. It should be stated, that ICMP replies can be restricted so that a computer or device that is up, may not reply. Although I’ve opted to use Test-Connection for my example, this concept can be used with any number of other cmdlets and their parameters.

Let’s start by taking a look at the default output of the Test-Connection cmdlet. By default, Test-Connection will ping a given computer four times. If I were checking for latency, four replies would probably be better than one, but because I just want to know if the computer is up or down, a single reply is suitable.

PS C:\> Test-Connection -ComputerName bing.com

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)
------        -----------     -----------      -----------                              -----    --------
TOMMYMAYNA... bing.com        204.79.197.200                                            32       14
TOMMYMAYNA... bing.com        204.79.197.200                                            32       13
TOMMYMAYNA... bing.com        204.79.197.200                                            32       14
TOMMYMAYNA... bing.com        204.79.197.200                                            32       13

This cmdlet includes a -Count parameter, and so I could have used that, as -Count 1, so that my computer only sent a single packet. But remember, I want be fast and efficient — I don’t want type more to see less. I want to type the same and make things faster. To accomplish this, what I’ll do is change the default behavior of this command to only send one packet. These means I’ll need to make use of the $PSDefaultParameterValues variable.

The $PSDefaultParameterValues variable stores any modifications to the default values of any of our cmdlet’s parameters. Since I don’t have any modified default values, the variable has nothing to return.

PS C:\> $PSDefaultParameterValues
PS C:\>

In the example below, we’ll use the Add() method to add a key/value pair to the variable. The keys are anything in the Name property, and the values are anything in the Value property. There is a one-to-one ratio between keys and values. These are also referred to as hash tables, associative arrays, or a dictionary object.

After we’ve added our key/value pair, we’ll try Test-Connection again, as we did in the first example.

PS C:\> $PSDefaultParameterValues.Add('Test-Connection:Count','1')
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Test-Connection:Count          1

PS C:\> Test-Connection -ComputerName bing.com

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)
------        -----------     -----------      -----------                              -----    --------
TOMMYMAYNA... bing.com        204.79.197.200                                            32       13

PS C:\>

As we can see, Test-Connection will now only ping a computer one time by default. Now, what happens when I do want to test for latency? I can use the -Count parameter and supply whatever number I want, so long as it’s 1 through 2147483647. Using zero or less, or 2147483648 will not work. I know, I tried.

You can read more information on the about_Parameters_Default_Values page here: https://technet.microsoft.com/en-us/library/hh847819.aspx.

Give your Variable a Description

When we think of variables in Windows PowerShell, we often only consider two parts: the name of the variable, and the value of the variable. Well, it turns out that those aren’t the only properties; let’s discuss the description property.

I can’t say I’ve ever added a description to a variable until now, but I can think of times, when looking at someone’s code, where I wish I knew the reason why a variable existed. Best practice would indicate we use meaningful names for our variables, so it’s probably safe to assume that the same people that might use a poorly-named variable, probably don’t know or care that variables can have descriptions… but, I digress.

In this first example, we’ll create a new variable using what I’d call the standard method — the equal sign assignment operator.

PS C:\> $a = 12345
PS C:\> $a
12345
PS C:\> Get-Variable a | Select-Object *

Name        : a
Description :
Value       : 12345
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

In line 1, we created a new variable, $a, and set its value to the numeric 12345. In the next line, we simply echoed the variable’s value. In line 4, we used the Get-Variable cmdlet and piped it to the Select-Object cmdlet and * so we were able to view all of its properties. If you take a look at the results, you’ll see the description property, and notice that it’s currently blank.

In the next example, we’ll use the Set-Variable cmdlet to modify the variable’s description.

PS C:\> Set-Variable a -Description 'This variable contains a 5-digit number.'
PS C:\> Get-Variable a | Select-Object *

Name        : a
Description : This variable contains a 5-digit number.
Value       : 12345
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

PS C:\>

Once we use the Get-Variable cmdlet, and return all the properties again, we can see that our variable now contains a description value. Now, while many user-defined variables, such as we created here, don’t often contain descriptions, many of the automatic and preference variables do — take a look at a segment of the results returned by the next command.

PS C:\> Get-ChildItem variable: | Select-Object Name,Description | Format-Table -AutoSize

Name                       Description
----                       -----------
...
MaximumAliasCount          Maximum number of aliases allowed in a session
MaximumDriveCount          Maximum number of drives allowed in a session
MaximumErrorCount          Maximum number of errors to retain in a session
MaximumFunctionCount       Maximum number of functions allowed in a session
MaximumHistoryCount        Maximum number of history objects to retain in a session
MaximumVariableCount       Maximum number of variables allowed in a session
MyInvocation
NestedPromptLevel          Dictates what type of prompt should be displayed for the current nesting level
...

Well, there you go: something you might not have known about, and something you may never use.

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.