Category Archives: Quick Learn

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

A Better Way to Solve the Same Problem

There once was a time I wrote a post. Realistically, there have been many times. Like many posts though, I was proud of it. I can’t show it to you, as it wasn’t written on my site and it has since been lost. I am actually kind of embarrassed by it these days, so I’m mostly glad it is gone.

Someone, on some forum, wanted to know the previous value of a variable. Not the current value, but the last value it had been assigned before its current value. My idea then, which seems ridiculous now, was to store its last value in its description property. If you don’t know, variables have a writable description property; really, they do. It is a pretty nice feature, were anyone to actually use it. I don’t imagine many people do. I know about it, and honestly, I don’t use it.

Instead of using a variable’s description property to store its last value, I got to thinking about the $PROFILE variable and how it works. Let’s have a look, but before we do, let’s make sure we all know what this variable holds. This variable contains a path to your profile script, whether your profile script exists or not.

[PS7.2.0][C:\] $PROFILE
C:\Users\tommymaynard\Documents\PowerShell\Microsoft.PowerShell_profile.ps1

The profile script is invoked when PowerShell launches, allowing you to create variables, define functions, and modify your PowerShell environment to suit you. In a recent post, I discussed creating a work-based ASCII image and sending in some keys. This made sure the first command I want to invoke was ready when PowerShell launched; all I had to do was press Enter. Oh, and here is every time I mentioned the $PROFILE variable on tommymaynard.com going all the way back to 2014. It has come up quite a few times over the years.

We have seen the value the $PROFILE variable holds, but there’s more. Let’s run it another way.

[PS7.2.0][C:\] $PROFILE | Select-Object -Property *

AllUsersAllHosts : C:\Program Files\PowerShell\7\profile.ps1
AllUsersCurrentHost : C:\Program Files\PowerShell\7\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts : C:\Users\tommymaynard\Documents\PowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\tommymaynard\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
Length : 71


Using the Select-Object cmdlet produced three additional paths: AllUsersAllHosts, AllUsersCurrentHost, and CurrentUserAllHosts. I am not going to go into what these all mean, but host refers to the program hosting the PowerShell engine. Read more about profiles here. With these three additional paths, there are four total locations where a profile script can be located and loaded by default. So, what do we have here? We have a variable with the ability to hold more than a single value (without making it an array or a hash table). That’s just how this variable works and I want the same thing for my example variable.

In this example, we’ve returned the same values as we did above, however, in this instance, we’re using the Get-Member cmdlet. I think it is important to see the values returned a couple of different ways.

[PS7.2.0][C:\] $PROFILE | Get-Member -MemberType NoteProperty

   TypeName: System.String

Name                   MemberType   Definition
----                   ----------   ----------
AllUsersAllHosts       NoteProperty string AllUsersAllHosts=C:\Program Files\PowerShell\7\profile.ps1
AllUsersCurrentHost    NoteProperty string AllUsersCurrentHost=C:\Program Files\PowerShell\7\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts    NoteProperty string CurrentUserAllHosts=C:\Users\tommymaynard\Documents\PowerShell\profile.ps1
CurrentUserCurrentHost NoteProperty string CurrentUserCurrentHost=C:\Users\tommymaynard\Documents\PowerShell\Microsoft.PowerShell_profile.ps1

Let’s start the remainder of this post by creating my example variable. Like the $PROFILE variable, we’re just going to work with string values.

[PS7.2.0][C:\] $PROFILE.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

[PS7.2.0][C:\] 
[PS7.2.0][C:\] $PSSites = 'https://docs.microsoft.com/en-us/powershell/scripting/overview'
[PS7.2.0][C:\] $PSSites
https://docs.microsoft.com/en-us/powershell/scripting/overview
[PS7.2.0][C:\] 
[PS7.2.0][C:\] $PSSites.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

[PS7.2.0][C:\]

At this point, we’ve assigned a docs.microsoft.com URL directly to the $PSSites variable. Now, let’s take a few new values and assign each of them as a NoteProperty on the same variable. This is done by using the Add-Member cmdlet. We assign the current value of the $PSSites variable back to the $PSSites variable, while also adding the new note property.

[PS7.2.0][C:\] $PSSites = $PSSites | Add-Member -NotePropertyName 'GitHub' -NotePropertyValue 'https://github.com/PowerShell/PowerShell' -PassThru
[PS7.2.0][C:\] $PSSites = $PSSites | Add-Member -NotePropertyName 'Docs' -NotePropertyValue 'https://docs.microsoft.com/en-us/powershell/' -PassThru
[PS7.2.0][C:\] $PSSites = $PSSites | Add-Member -NotePropertyName 'Gallery' -NotePropertyValue 'https://www.powershellgallery.com/' -PassThru
[PS7.2.0][C:\] $PSSites = $PSSites | Add-Member -NotePropertyMembers @{Reddit='https://www.reddit.com/r/PowerShell/'} -PassThru

You may have noticed, but in the last of the above four examples, we used the NotePropertyMembers parameter with a hash table for the associated value. This option allows us to supply both the name and value at the same time. It does exactly what the first three examples did — it is just another way to do it.

I should mention the PassThru switch parameter. This is a requirement because Add-Member cannot add types to strings, so this will generate an object. It’s worth keeping this in mind. Without it, you’ll drive yourself crazy trying to determine why it isn’t working — always, read the help!

Using dot notation we can see the additional values (note properties), that our variable has been assigned.

[PS7.2.0][C:\] $PSSites.GitHub
https://github.com/PowerShell/PowerShell
[PS7.2.0][C:\] 
[PS7.2.0][C:\] $PSSites.Docs
https://docs.microsoft.com/en-us/powershell/
[PS7.2.0][C:\] 
[PS7.2.0][C:\] $PSSites.Gallery
https://www.powershellgallery.com/
[PS7.2.0][C:\] 
[PS7.2.0][C:\] $PSSites.Reddit
https://www.reddit.com/r/PowerShell/

Like we did with the $PROFILE variable, we can use the Select-Object cmdlet to return all of our URLs. We can also use Get-Member.

[PS7.2.0][C:\] $PSSites | Select-Object -Property *

GitHub   : https://github.com/PowerShell/PowerShell
Docs     : https://docs.microsoft.com/en-us/powershell/
Gallery  : https://www.powershellgallery.com/
Reddit   : https://www.reddit.com/r/PowerShell/
Length   : 62


[PS7.2.0][C:\] $PSSites | Get-Member -MemberType NoteProperty

   TypeName: System.String

Name     MemberType   Definition
----     ----------   ----------
Docs     NoteProperty string Docs=https://docs.microsoft.com/en-us/powershell/
Gallery  NoteProperty string Gallery=https://www.powershellgallery.com/
GitHub   NoteProperty string GitHub=https://github.com/PowerShell/PowerShell
Reddit   NoteProperty string Reddit=https://www.reddit.com/r/PowerShell/

Now, with the PowerShell site URLs stored in the $PSSites variable, I can easily open whichever website I prefer using Start-Process and the corresponding property. No real idea if I’ll use this, but I’m grateful to know how to use a note property to store additional data along with a simple string variable.

[PS7.2.0][C:\] Start-Process -Path $PSSites.Gallery

Join me next time, perhaps, if I can devise a way to save the previous value of a variable to a note property when assigning a new value to the variable itself. It will take a little thought and work, but for now, I think it is possible.

Pass Range to Function Parameter

I had one of those thoughts, where you must know how something is handled in PowerShell, as soon as you possibly can. Maybe it happens to you, too. Unfortunately for me, it happen just before I decided to go to bed a few days ago, which kept me awake a bit longer than expected.

The question was this: Can I pass a range operator to a PowerShell function as a parameter? As you may know, a range operator alone isn’t worth much, so essentially the question was, Can I pass two numbers and range operator to a PowerShell function as a parameter?

Let’s start with an example of the range operator (..). It’s two dots between two numbers, and it essentially says, “provide me every number in this range.” The below example uses the numbers 1 through 10. Once executed, it returns the numbers 1, 2, 3, 4, etc., all the way up to 10.

[PS7.2.0][C:\] 1..10
1
2
3
4
5
6
7
8
9
10
[PS7.2.0][C:\]

A couple of things: One, the range operator returns an array, and two, as of PowerShell version 6, it works with letters, or characters, as well. Be sure to use single quotes around your string values, or it’ll think you are passing in command names, which will not work.

[PS7.2.0][C:\] (1..10).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

[PS7.2.0][C:\] 'a'..'e'
a
b
c
d
e
[PS7.2.0][C:\] 

Let’s review this function first. This is my New-RangeOption function. It requires that two integers be supplied. One for the RangeFirstNumber parameter and one for the RangeLastNumber parameter. The idea here is that we don’t believe a range can be supplied to a function, as a parameter, and we must create it ourselves inside of our function. We do more than just that, however. As I was just testing, we display each number and then we display the type of each parameter value. They are cast as integers, so that really wasn’t necessary. Additionally, we create the range (add the operator between the two integers), display the results, and return the type of the range, as well. This should make sense when you see the below results.

function New-RangeOption {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [int]$RangeFirstNumber,

        [Parameter(Mandatory)]
        [int]$RangeLastNumber
    )
    $RangeFirstNumber;  $RangeLastNumber
    $RangeFirstNumber.GetType(); $RangeLastNumber.GetType()

    $InFunctionRange = $RangeFirstNumber..$RangeLastNumber
    $InFunctionRange
    $InFunctionRange.GetType()
}
New-RangeOption -RangeFirstNumber 1 -RangeLastNumber 10
1
10

IsPublic IsSerial Name                                     BaseType        
-------- -------- ----                                     --------        
True     True     Int32                                    System.ValueType
True     True     Int32                                    System.ValueType
1
2
3
4
5
6
7
8
9
10
True     True     Object[]                                 System.Array

This option works, but we can do better. This, below example, has only a single parameter, Range. We send in our range, which includes the two numeric values and the range operator. It outputs the range — all the numbers — the type, and the count, as in the count of the number of members in the array. It’s 10, one for each value from 1 to 10.

function New-BetterRangeOption {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [array]$Range
    )

    $Range
    $Range.GetType()
    $Range.Count
}
New-BetterRangeOption -Range (1..10)
1
2
3
4
5
6
7
8
9
10

IsPublic IsSerial Name                                     BaseType    
-------- -------- ----                                     --------    
True     True     Object[]                                 System.Array
10

There is an important thing to notice here, and that’s the group operator — the parentheses around 1..10. It is used to pass in the digits with the range operator. Without this operator, it would’ve been a mess — let’s see.

New-BetterRangeOption -Range 1..10
1..10

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
1

In this last output, it doesn’t display the actual digits within the range. While it still thinks it’s an array, it’s an array that contains 1..10 as a single, string member. That’s it though; curiosity satisfied. We can pass in a range, using the range operator (and the group operator), to our functions. Now, I only need a reason to do it.

Fourth Grade Multiplication Tables

I remember math class… in fourth grade. It was the year we learned multiplication. Day after day, for so many consecutive days, we’d have to write our “times tables.” Zero though 10, over and over again. Good thing, I suppose, as I still know them today. The combination of knowing PowerShell and my daughter learning her “times tables” now, got me thinking: How quickly could I “write” my “times tables” today? It turns out it takes around 5 milliseconds.

If you’ve been reading along recently you may have seen what we’ll do today, done before. We need multiple loops. One loop, our outer loop, will cycle through the numbers 0 through 10. For each of those, we’ll have an inner loop multiply 0 through 10 to the current outer loop number. You know, we’ll do zero, so zero times zero, zero times one, and so on, until we hit 10, at which point we’ll move on from zero to one (one times zero, one times one, one times two, etc.). Let’s begin with the outer loop.

foreach ($outernumber in 0..10) {
	$outernumber
}
0
1
2
3
4
5
6
7
8
9
10

Now, let’s do the inner loop. Again, this is going to repeat zero through 10 inside each inner loop, for each outer loop. If this is hard to follow, be sure to take a look at the soon-to-be upcoming example where we’ll highlight both outer and inner loops.

foreach ($outernumber in 0..10) {
	foreach ($innernumber in 0..10) {
		$innernumber
	}
}
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
0
1
2
3
4
5
6
7
8
9
10

This is the same example as we saw above; however, we’ve included the value of the outer loop in our output. Each outer loop value has two dashes (--) in front of it.

foreach ($outernumber in 0..10) {
	"-- $outernumber"
	foreach ($innernumber in 0..10) {
		$innernumber
	}
}
-- 0
0
1
2
3
4
5
6
7
8
9
10
-- 1
0
1
2
3
4
5
6
7
8
9
10
-- 2
0
1
2
3
4
5
6
7
8
9
10
-- 3
0
1
2
3
4
5
6
7
8
9
10
-- 4
0
1
2
3
4
5
6
7
8
9
10
-- 5
0
1
2
3
4
5
6
7
8
9
10
-- 6
0
1
2
3
4
5
6
7
8
9
10
-- 7
0
1
2
3
4
5
6
7
8
9
10
-- 8
0
1
2
3
4
5
6
7
8
9
10
-- 9
0
1
2
3
4
5
6
7
8
9
10
-- 10
0
1
2
3
4
5
6
7
8
9
10

And since this post is lengthy in long examples, let’s add another before we actually get to the results we came for. Our outer loop number is on the left side of the multiplication symbol, and our inner loop number is on the right side.

foreach ($outernumber in 0..10) {
	foreach ($innernumber in 0..10) {
		"$outernumber x $innernumber ="
	}
}
0 x 0 =
0 x 1 =
0 x 2 =
0 x 3 =
0 x 4 =
0 x 5 =
0 x 6 = 
0 x 7 = 
0 x 8 = 
0 x 9 = 
0 x 10 =
1 x 0 = 
1 x 1 = 
1 x 2 = 
1 x 3 = 
1 x 4 = 
1 x 5 = 
1 x 6 = 
1 x 7 = 
1 x 8 = 
1 x 9 = 
1 x 10 =
2 x 0 = 
2 x 1 = 
2 x 2 = 
2 x 3 = 
2 x 4 = 
2 x 5 = 
2 x 6 = 
2 x 7 = 
2 x 8 = 
2 x 9 = 
2 x 10 =
3 x 0 = 
3 x 1 = 
3 x 2 = 
3 x 3 = 
3 x 4 = 
3 x 5 = 
3 x 6 = 
3 x 7 = 
3 x 8 =
3 x 9 =
3 x 10 =
4 x 0 =
4 x 1 =
4 x 2 =
4 x 3 =
4 x 4 =
4 x 5 =
4 x 6 =
4 x 7 =
4 x 8 =
4 x 9 =
4 x 10 =
5 x 0 =
5 x 1 =
5 x 2 =
5 x 3 =
5 x 4 =
5 x 5 =
5 x 6 =
5 x 7 =
5 x 8 =
5 x 9 =
5 x 10 =
6 x 0 =
6 x 1 =
6 x 2 =
6 x 3 =
6 x 4 =
6 x 5 =
6 x 6 =
6 x 7 =
6 x 8 =
6 x 9 =
6 x 10 =
7 x 0 =
7 x 1 =
7 x 2 =
7 x 3 =
7 x 4 =
7 x 5 =
7 x 6 =
7 x 7 =
7 x 8 =
7 x 9 =
7 x 10 =
8 x 0 =
8 x 1 =
8 x 2 =
8 x 3 =
8 x 4 =
8 x 5 =
8 x 6 =
8 x 7 =
8 x 8 =
8 x 9 =
8 x 10 =
9 x 0 =
9 x 1 =
9 x 2 =
9 x 3 =
9 x 4 =
9 x 5 =
9 x 6 =
9 x 7 =
9 x 8 =
9 x 9 =
9 x 10 =
10 x 0 =
10 x 1 =
10 x 2 =
10 x 3 =
10 x 4 =
10 x 5 =
10 x 6 =
10 x 7 =
10 x 8 =
10 x 9 =
10 x 10 =

In this example, we’re going to add the multiplication problem to our output. Notice that we are making use of the subexpression operator ($()) in order to embed the results of each expression — or multiplication problem — within our string.

foreach ($outernumber in 0..10) {
	foreach ($innernumber in 0..10) {
		"$outernumber x $innernumber = $($outernumber * $innernumber)"
	}
}
0 x 0 = 0
0 x 1 = 0
0 x 2 = 0
0 x 3 = 0
0 x 4 = 0
0 x 5 = 0
0 x 6 = 0
0 x 7 = 0  
0 x 8 = 0  
0 x 9 = 0  
0 x 10 = 0 
1 x 0 = 0  
1 x 1 = 1  
1 x 2 = 2  
1 x 3 = 3  
1 x 4 = 4  
1 x 5 = 5  
1 x 6 = 6  
1 x 7 = 7  
1 x 8 = 8  
1 x 9 = 9  
1 x 10 = 10
2 x 0 = 0  
2 x 1 = 2  
2 x 2 = 4  
2 x 3 = 6  
2 x 4 = 8  
2 x 5 = 10 
2 x 6 = 12 
2 x 7 = 14 
2 x 8 = 16 
2 x 9 = 18 
2 x 10 = 20
3 x 0 = 0 
3 x 1 = 3 
3 x 2 = 6 
3 x 3 = 9 
3 x 4 = 12
3 x 5 = 15
3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
3 x 10 = 30
4 x 0 = 0
4 x 1 = 4
4 x 2 = 8
4 x 3 = 12
4 x 4 = 16
4 x 5 = 20
4 x 6 = 24
4 x 7 = 28
4 x 8 = 32
4 x 9 = 36
4 x 10 = 40
5 x 0 = 0
5 x 1 = 5
5 x 2 = 10
5 x 3 = 15
5 x 4 = 20
5 x 5 = 25
5 x 6 = 30
5 x 7 = 35
5 x 8 = 40
5 x 9 = 45
5 x 10 = 50
6 x 0 = 0
6 x 1 = 6
6 x 2 = 12
6 x 3 = 18
6 x 4 = 24
6 x 5 = 30
6 x 6 = 36
6 x 7 = 42
6 x 8 = 48
6 x 9 = 54
6 x 10 = 60
7 x 0 = 0
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63
7 x 10 = 70
8 x 0 = 0
8 x 1 = 8
8 x 2 = 16
8 x 3 = 24
8 x 4 = 32
8 x 5 = 40
8 x 6 = 48
8 x 7 = 56
8 x 8 = 64
8 x 9 = 72
8 x 10 = 80
9 x 0 = 0
9 x 1 = 9
9 x 2 = 18
9 x 3 = 27
9 x 4 = 36
9 x 5 = 45
9 x 6 = 54
9 x 7 = 63
9 x 8 = 72
9 x 9 = 81
9 x 10 = 90
10 x 0 = 0
10 x 1 = 10
10 x 2 = 20
10 x 3 = 30
10 x 4 = 40
10 x 5 = 50
10 x 6 = 60
10 x 7 = 70
10 x 8 = 80
10 x 9 = 90
10 x 10 = 100

That was it. Well, there is one thing. Let’s get back to seeing how long this command takes to run.

Measure-Command -Expression {
	foreach ($outernumber in 0..10) {
		foreach ($innernumber in 0..10) {
			"$outernumber x $innernumber = $($outernumber * $innernumber)"
		}
	}
} | Select-Object -Property Milliseconds

Milliseconds
------------
           5

We could put this entire command in a loop too, and test it across ten iterations. This example includes ticks, as well as milliseconds because when this command is run back-to-back, the speed per iteration drastically increases. Have a look. So it has been said, there are 10,000 ticks per millisecond and 1,000 milliseconds per second.

1..10 | ForEach-Object {
	Measure-Command -Expression {
		foreach ($outernumber in 0..10) {
			foreach ($innernumber in 0..10) {
				"$outernumber x $innernumber = $($outernumber * $innernumber)"
			}
		}
	} | Select-Object -Property Milliseconds,Ticks
}

Milliseconds Ticks
------------ -----
           3 39181
           0  7081
           1 14917
           1 10313
           0  2318
           0  2289
           0  2235
           0  2149
           0  2099
           0  2083

Prep Command for Immediate Use

Edit: I’ve updated this post by adding a really quick video at the bottom — check it out. It briefly shows the results of the concepts in this post.

It was just yesterday when I wrote the Windows Terminal Fun Surprise post. This was a short post about executing the Alt+Shift+D keyboard key combination inside Windows Terminal. I got myself to a point where I could programmatically send in this key combination using the below commands.

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('%+D')

This got me thinking: Can I use this to prep a command for immediate use? I know, what does that even mean? My profile script $PROFILE, contains a good deal of helpful tools. And then it has something like the PowerShell ASCII art below. It’s actually a work logo, but you get the idea. This laptop with the PowerShell logo will stand in just fine, however.

Function Show-PowerShellAsciiArt {
@"
   ._________________.
   |.---------------.|
   ||   PowerShell  ||
   ||     \         ||
   ||      \        ||
   ||      /        ||
   ||     / ------  ||
   ||_______________||
   /.-.-.-.-.-.-.-.-.\
  /.-.-.-.-.-.-.-.-.-.\
 /.-.-.-.-.-.-.-.-.-.-.\
/______/__________\___o_\
\_______________________/

"@
} # End Function: Show-PowerShellAsciiArt.
Show-PowerShellAsciiArt

As cool as I think this logo is — the one I actually use — and as certain as I am about it appearing every time I open a new terminal, I feel like it’s in the way when I want to start to use my terminal. Like you, I suspect, I want my prompt and cursor in the top-leftish corner and ready to go pretty quickly. The difference is, I’m willing a momentary slowdown. I can do better, however.

The thought was: Can I place a command at the prompt as a part of my profile script, that will allow me to just press Enter to clear the screen? You see, I usually type cls or clear, press Enter and then start with whatever I’m doing. I want the logo, but I also want to get started as soon as I can. The answer to that most recent thought though is yes.

In addition to the above Show-PowerShellAsciiArt function, is one more brief function and a modification to the code that started off this post.

Function Clear-Logo {Clear-Host}

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('Clear-Logo')

Now, when my profile script executes, it places the “Clear-Logo” text at my prompt, allowing me to just press Enter to clear the image and start doing what I do at the command line. As you can see, Clear-Logo is only there to invoke Clear-Host. Still, I like it better.

Press Enter and voilà.

In order to get a better idea of how this works — just in case, I didn’t do a great job explaining it, who knows — I’ve included a short video below. This shows me opening two new tabs in Windows Terminal and pressing Enter after each is open, and therefore, clearing the screen (the logo).

Windows Terminal Fun Suprise

I saw a Tweet today from Kayla Cinnamon: https://twitter.com/cinnamon_msft/status/1455946220476657665. Here’s the Tweet.

Although she wrote to type Alt+Shift+D, she obviously didn’t mean it that way. I looked past that, although some of the comments proved that others did not. I quickly figured out what it did and then I determined how to not use that keyboard key combination. Before we see that, let’s determine what Alt+Shift+D does.

Alt+Shift+D opens up a new terminal, or rather, it duplicates the pane. If you take a look at the Settings menu you’ll quickly discover a large number of other fun surprises (a.k.a. keyboard key combinations). Someday, I’ll take the time and work through those.

Anyway, here’s what you shouldn’t do.

[PS7.1.5][C:\] Add-Type -AssemblyName System.Windows.Forms
[PS7.1.5][C:\] 1..10 | ForEach-Object {[System.Windows.Forms.SendKeys]::SendWait('%+D')}

This lovely piece of frustration — if you can sneak it into a “friend’s” profile script — will add the System.Windows.Forms assembly and then send in the Alt+Shift+D key combination for each iteration through the ForEach-Object loop. In this example, it would be a total of ten times. So it’s been said, the SendWait method includes “%” and “+” in addition to the letter “D.” As you may have already guessed, if you didn’t know, these two symbols are used for Alt and Shift, respectively. So what’s this produce? It does what a good number of people that replied to Kayla’s Tweet showed us they could do by repetitively pressing the key combination. That said, why do it manually? It’s PowerShell!

Back to when she said to “type” the keyboard command. It did make me think to do something with the string value Alt+Shift+D.

In the top-left pane, which was the only one when I started, I created a function called Alt+Shift+D. It included the two commands from earlier. Then I tested it/invoked my new function and it added the pane on the right side. Then I created an alias for my function, because who wants to type out the entirety of Alt+Shift+D each time — not me — and tested it too. It opened the bottom-left pane. If you aren’t using Windows Terminal then you should be; it continues to get better all the time.

My Work-Specific Vacation Time Function

Edit: There were some logic errors that Mark pointed out in the comments section. I was rounding throughout the function and skewing some of my own totals. I’ve updated this post to reflect all of the changes (code, images, text, etc.). Thank you for your time, Mark, for both reading the post and passing along some beneficial information.

I did that thing where I start a post that will include a shareable PowerShell function and I’ve yet to actually write any PowerShell. I’ll get there eventually, but for now, let me explain the purpose of the function I’ll be writing. Realistically, this explanation for you ought to help me with authoring the code. It’s obvious, but once I’ve defined the specifications and needs, it should be even easier to write.

My current employer — this is about to sound like a word problem from grade school — allows me to carry forward 320 vacation hours into the next calendar year. Anything above 320 hours is forfeited. Because I was on track to have greater than 320 hours by the end of the year, I’ve been taking off every Wednesday since the last week of September. If you haven’t been to Target at 8 a.m. on a weekday morning, then you’re missing out. It was so empty; it was just me and very few people — amazing. I digress.

My local HR is adamant about employees not losing/forfeiting vacation time. In all of this, I decided I should write the code to do the arithmetic in the PowerShell console. I later decided I should write a function that would do the calculations for me, instead. Before I actually construct the code and build the function, let’s walk through some needed values.

1. Number of carry forward vacation hours: 320 (default value, same for all employees)
2. Number of current vacation hours: 362.670
3. Number of pay periods during the remainder of the calendar year: 5
4. Hours of vacation accrued per pay period: 6.77 (default value, not the same for all employees)

First, I need to determine how many hours I currently have above the carry forward vacation hours of 320. That’s the current vacation hours minus the carry forward vacation hours: 362.670 – 320. It’s possible someone using my (so far non-existent) function could have less than 320 vacation hours. This would change things, and it’s probably worth keeping in mind, as the function will need to handle this possibility.

Second, I’d like to know how many more vacation hours I’ll accrue during the remainder of the calendar year. That’s the vacation accrual hours times the number of pay periods during the remainder of the calendar year: 6.77 * 5. Let’s go.

367.670 – 320 = 42.67
6.77 * 5 = 33.85
42.67 + 33.85 = 76.52

If we divide that total by 8 hours (as in, hours worked per day), we know how many days of vacation I need to take to use up my hours: 76.52 / 8 = 9.565. It’s a little over nine days, but not quite 10. In thinking through this assignment, I’ve realized that it’s possible someone may only want to know their current overage (the subtraction problem) versus their future overage (the multiplication problem) or vice versa. Anyway, I’m off to write this function now. To you, it’ll seem like no time passed at all; for me, I won’t be nearly as lucky. Still, having defined the function’s requirements upfront, before writing any code, has already simplify things.

Well, that’s done and working. I’m going to include images of the function because they’re easier on the eyes and I think it’ll simplify my code explanations. I will, however, make the function copyable at the end of this post.

Let’s start with the parameters.

This function includes five parameters. Beginning from the top, we have CarryForwardVacationHours. This has a default value of 320, however, this can be changed by the person/process when this function is invoked. It won’t need to be for me unless my current employer changes the carry forward amount. Next, we have CurrentVacationHours. The is the parameter that’s used to supply how many hours a person currently has. The value needs to be greater than the CarryForwardVacationHours or there won’t be any overage hours.

The following parameter is VacationAccrualRate. This contains a default value of 6.77 hours. This value does vary depending on how long a person has been employed by my current employer. This is the largest possible amount, as it’s based on longevity. The second to last parameter is PayPeriods. This is how many pay periods there are until the end of the calendar year. The person invoking the function will have to gather this information themself.

And finally, there is the Round parameter. This was a late addition to this function that was discussed in a recent post, Setting the Rounding Scale. This parameter has a default value of 2. This means that any values with decimals with more than 2 values, will be rounded down to two, or to whatever the value is set to when the function is invoked. As we saw in that recent post, it’s not going to add zeros into decimal positions. If the number is 42.1 and you indicate to round to three decimal places, it’s still going to be 42.1. Read the linked post to see how to add zeros if that’s something that would ever be of use to you.

Now, let’s move on to the Process block and its first region, Current Hours. It’s not pictured in these images, but the Begin block isn’t being used in this function. This is the reason it didn’t make it into this or the previous image.

This region of this Process block, first determines if the current vacation hours are greater than the carry forward vacation hours. If it is, it does a little math, and we’ll get there in a moment. If it isn’t, it sets the overage hours and overage days to zero. We’re storing all of our values in an ordered hash table, so that’s where these would land. If the current vacation hours are greater than the carry forward vacation hours, then it subtracts the two numbers as we mentioned earlier. In order to determine how many overage days there are, it divides the result by 8 hours per day, placing both values into the hash table. The carry forward hours are added to the hash table, as well.

Following this region, let’s discuss the Future/Total Hours region.

In this region, we determine our vacation accrual hours and from that total, our vacation accrual days. Finally, we put it all together for the total hours by combining the overage hours and vacation accrual hours and our total days by combing the overage days and the vacation accrual days. Much like the carry forward hours in the above region, we include the vacation accrual rate, too.

Let’s close out with the End Block. This is simple; we convert our hash table — the keys and values — into PowerShell objects, where they become properties and values. This way, they can be easier manipulated by other PowerShell commands if we choose to do that.

Now, let’s include a few test invocations and ensure we’re getting the proper results. This first one just calculates my vacation overage hours and days.

Show-VacationTime -CurrentVacationHours 362.670

CarryForward : 320
OverageHours : 42.67
OverageDays  : 5.33
AccrualRate  : 6.77
AccrualHours : 0
AccrualDays  : 0
TotalHours   : 42.67
TotalDays    : 5.33

This one returns my vacation overages, as well as my vacation accruals.

Show-VacationTime -CurrentVacationHours 362.670 -PayPeriods 5

CarryForward : 320
OverageHours : 42.67
OverageDays  : 5.33
AccrualRate  : 6.77
AccrualHours : 33.85
AccrualDays  : 4.23 
TotalHours   : 76.52
TotalDays    : 9.57 

These last example invocations run the same command as above but include the Round parameter with a couple of different values. Remember, 2 is the default value for this parameter.

Show-VacationTime -CurrentVacationHours 362.670 -PayPeriods 5 -Round 0

CarryForward : 320
OverageHours : 43
OverageDays  : 5
AccrualRate  : 6.77
AccrualHours : 34
AccrualDays  : 4
TotalHours   : 77
TotalDays    : 10

Show-VacationTime -CurrentVacationHours 362.670 -PayPeriods 5 -Round 4

CarryForward : 320
OverageHours : 42.67
OverageDays  : 5.3338
AccrualRate  : 6.77
AccrualHours : 33.85
AccrualDays  : 4.2312
TotalHours   : 76.52
TotalDays    : 9.565

Okay, one final example of when you don’t send in the current vacation hours to determine an overage, but you do want to know your vacation accrual hours.

Show-VacationTime -PayPeriods 5

CarryForward : 320
OverageHours : 0
OverageDays  : 0
AccrualRate  : 6.77
AccrualHours : 33.85
AccrualDays  : 4.23
TotalHours   : 33.85
TotalDays    : 4.23

Finally, as I promised, here’s a copyable version of this function. I’m quite aware that there are only so many of us that can and/or would make use of this function. Still, some great concepts have been demonstrated and maybe some of them can be included in some of your own projects.

Function Show-VacationTime {
    [CmdletBinding()]
    Param (
        [Parameter()]
        $CarryForwardVacationHours = 320,

        [Parameter()]
        $CurrentVacationHours,

        [Parameter()]
        $VacationAccrualRate = 6.77,

        [Parameter()]
        [int]$PayPeriods,

        [Parameter()]
        [ValidateRange(0,4)]
        [int]$Round = 2
    ) # End Param.

    Begin {} # End Begin.

    Process {
        #region: Current Hours.
        If ($CurrentVacationHours -gt $CarryForwardVacationHours) {
            $OverageHours = $CurrentVacationHours - $CarryForwardVacationHours
            $OverageDays = $OverageHours / 8 # 8 hours.
            $Hashtable = [ordered]@{
                CarryForward = $CarryForwardVacationHours
            }
        } Else {
            $Hashtable = [ordered]@{
                CarryForward = $CarryForwardVacationHours
                OverageHours = 0
                OverageDays = 0
            }
        } # End If-Else.
        #endregion.
        #region: Future/Total Hours.
        $VacationAccrualHours = $PayPeriods * $VacationAccrualRate
        $VacationAccrualDays = $VacationAccrualHours / 8
        $TotalHours = $OverageHours + $VacationAccrualHours
        $TotalDays = $OverageDays + $VacationAccrualDays
        $Hashtable['OverageHours'] = ([math]::Round($OverageHours,$Round))
        $Hashtable['OverageDays'] = ([math]::Round($OverageDays,$Round))
        $Hashtable['AccrualRate'] = $VacationAccrualRate
        $Hashtable['AccrualHours'] = ([math]::Round($VacationAccrualHours,$Round))
        $Hashtable['AccrualDays'] = ([math]::Round($VacationAccrualDays,$Round))
        $Hashtable['TotalHours'] = ([math]::Round($TotalHours,$Round))
        $Hashtable['TotalDays'] = ([math]::Round($TotalDays,$Round))
        #endregion.
    } # End Process.

    End {
        [PSCustomObject]$Hashtable
    } # End End.
} # End Function: Show-VacationTime.

Setting the Rounding Scale

Sometimes sharing one concept in a post, allows you to share another concept in another post. That brings us to today. This is that other post.

Originally, I had a great title for this post: It was Rounding Precision Decision. Then, for whatever reason, I went and made sure that precision was the word I was after. It wasn’t. Precision is the total number of digits in a number. Scale is the number of digits after the decimal.

Last night I decided I should add a Round parameter to a function I’m writing. This would allow the user to determine how rounding should be applied to a given number. The parameter values, as I see them now, will be 0 through 4. This is to say a numerical value may have zero decimal places up to four, depending on the person/process running the function. Neat, right? Like I sometimes do, I opened Window Terminal to start some one-off testing.

Let’s start with an ordered hash table of numeric values. As you’ll see, the values will vary in the number of decimal places from zero to five.

[PS7.1.5][C:\] $Hash = [ordered]@{'Num0' = 42; 'Num1' = 42.1; 'Num2' = 42.12; 'Num3' = 42.123; 'Num4' = 42.1234; 'Num5' = 42.12345}
[PS7.1.5][C:\] $Hash

Name               Value
----               -----
Num0               42
Num1               42.1
Num2               42.12
Num3               42.123
Num4               42.1234
Num5               42.12345
[PS7.1.5][C:\] 

While we’re here, let’s loop through the keys and values in this hash table. We’ll need this construct soon enough anyway.

[PS7.1.5][C:\] Foreach ($Key in $Hash.Keys) {"$Key`: $($Hash[$Key])"}
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
[PS7.1.5][C:\] 

That works. Now what we need to consider is that we’re going to need to iterate over $Hash multiple times, so we can apply each of the decimal values, 0 – 5 for this post. This may be confusing, but the next example should help explain how we get there.

[PS7.1.5][C:\] Foreach ($Value in 0..5) {$Value}
0
1
2
3
4
5
[PS7.1.5][C:\] Foreach ($Value in 0..5) {Foreach ($Key in $Hash.Keys) {"$Key`: $($Hash[$Key])"}}
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
[PS7.1.5][C:\]

Now that we have this working, let’s apply some rounding to each of the sets of numbers. Before we do that, however, I’m heading back to VSCode. Sometimes the one-off code I write gets a bit unwieldy. For reference, we’re going to use the Round method in the System.Math class, such as [math]::Round(<number>,<scale>).

$Hash = [ordered]@{
	'Num0' = 42
	'Num1' = 42.1
	'Num2' = 42.12
	'Num3' = 42.123
	'Num4' = 42.1234
	'Num5' = 42.12345
}
Foreach ($Value in 0..5) {
	"Rounding to $Value"
	Foreach ($Key in $Hash.Keys) {
		"$Key`: $([math]::Round($Hash[$Key],$Value))"
	} # End Foreach.
'--separator--'
} # End Foreach.

Rounding to 0
Num0: 42
Num1: 42
Num2: 42
Num3: 42
Num4: 42
Num5: 42
--separator--
Rounding to 1
Num0: 42
Num1: 42.1
Num2: 42.1
Num3: 42.1
Num4: 42.1
Num5: 42.1
--separator--
Rounding to 2
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.12
Num4: 42.12
Num5: 42.12
--separator--
Rounding to 3
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.123
Num5: 42.123
--separator--
Rounding to 4
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.1234
--separator--
Rounding to 5
Num0: 42
Num1: 42.1
Num2: 42.12
Num3: 42.123
Num4: 42.1234
Num5: 42.12345
--separator--

At first glance, this PowerShell code may look like it’s failing. Look at the “Rounding to 3” section, for instance. There are values where it appeared to round to zero, one, and two decimal places. Remember though, these numbers didn’t start with three decimals places or more. It did exactly what we asked it to do to the best of its ability. I don’t know of a way that the Round method can add zeros if we actually wanted that — let me know if you — but there’s another way!

This time, we’ll use the -f format operator and force zeros into the decimals places. I probably won’t do this in my upcoming post, but I think it’s worth seeing, as there’s value here.

Foreach ($Value in 0..5) {
	"Rounding to $Value"
	Foreach ($Key in $Hash.Keys) {
		"$Key`: $("{0:N$Value}" -f $Hash[$Key])"
	} # End Foreach.
'--separator--'
} # End Foreach.

Rounding to 0
Num0: 42
Num1: 42
Num2: 42
Num3: 42
Num4: 42
Num5: 42
--separator--
Rounding to 1
Num0: 42.0
Num1: 42.1
Num2: 42.1
Num3: 42.1
Num4: 42.1
Num5: 42.1
--separator--
Rounding to 2
Num0: 42.00
Num1: 42.10
Num2: 42.12
Num3: 42.12
Num4: 42.12
Num5: 42.12
--separator--
Rounding to 3
Num0: 42.000
Num1: 42.100
Num2: 42.120
Num3: 42.123
Num4: 42.123
Num5: 42.123
--separator--
Rounding to 4
Num0: 42.0000
Num1: 42.1000
Num2: 42.1200
Num3: 42.1230
Num4: 42.1234
Num5: 42.1234
--separator--
Rounding to 5
Num0: 42.00000
Num1: 42.10000
Num2: 42.12000
Num3: 42.12300
Num4: 42.12340
Num5: 42.12345
--separator--

And that’s, that. Back to the other post that inspired this one. See you there in time!

PowerShell Variable Description Property

I don’t remember who mentioned it, but someone in the PowerShell community recently tweeted that they had been working with PowerShell for 10 years and had just learned something new. It wasn’t new to me, but I’m sure there are things out there I’ve yet to encounter. I sure hope so, anyway!

This individual didn’t realize that variables in PowerShell have descriptions. Did you know that!? Here are a few examples of different uses. Eventually, we’ll get to the description property, as well as all the properties you get every time you create a variable in PowerShell.

[PS7.1.5][C:\] $Variable = 'This is a string variable.'
[PS7.1.5][C:\]  $Variable
This is a string variable.
[PS7.1.5][C:\]
[PS7.1.5][C:\] Get-Variable -Name Variable -ValueOnly
This is a string variable.
[PS7.1.5][C:\]
[PS7.1.5][C:\] (Get-Variable -Name Variable).Value
This is a string variable.
[PS7.1.5][C:\]
[PS7.1.5][C:\] Get-Variable -Name Variable

Name                           Value
----                           -----
Variable                       This is a string variable.

[PS7.1.5][C:\] Get-Variable -Name Variable | Select-Object -Property *

PSPath        : Microsoft.PowerShell.Core\Variable::Variable
PSDrive       : Variable
PSProvider    : Microsoft.PowerShell.Core\Variable
PSIsContainer : False
Name          : Variable
Description   :
Value         : This is a string variable.
Visibility    : Public
Module        :
ModuleName    :
Options       : None
Attributes    : {}

[PS7.1.5][C:\]  

There it is… Description. Let’s add one and then return it!

[PS7.1.5][C:\] Set-Variable -Name Variable -Description 'This is the description property of a string variable.'
[PS7.1.5][C:\] (Get-Variable -Name Variable).Description
This is the description property of a string variable.

If you didn’t know this either, then just maybe this is exciting for you, too! I liked it when I first found it. I will link an old post below that I wrote about variable descriptions the first time around. It may be much of what was presented here already. There’s another post I wish I could find, but I think it was posted on another website and it has since been lost. Somebody on some forum wanted to know the last value that a variable contained and so I wrote a post about storing the variable’s previous value in the description property each time the variable was updated to something new. That’s was fun, and unique!

Give  your Variable a Description

PowerShell Copy, Shallow and Deep

Back in March, when I started this post, I was reading over some Python code. Yeah,… you read that right. As I did that, I was introduced to a term I don’t think I’ve seen before: It was “deepcopy.” The Python module import looked like this: from copy import deepcopy. Deepcopy, what is that?

I don’t remember the name for it, but as I read up on this term and the function from this Python module, I do remember coming up against this in PowerShell. Essentially, it’s this. If you make a copy of an array—we’ll use that for this example—changes to the original array will change the copy, as well. The deepcopy option allows Python to create a copy that isn’t tied to the original. Let’s begin by looking at a shallow copy example in PowerShell.

Any change made to the original object—$Array—will be seen in the copy of the object, too. There’s a link between them. As a shallow copy, its values will continue to reference the original object. A deep copy is independent; there is no reference to the original object. Let’s work through a quick example of making a deep copy of our array in PowerShell.

I gather that there may be other ways to create deep copies, and you’re welcome to comment and share those if you’d like. This is an extremely simple and straightforward example—my favorite—of something of which you may need to be careful, as you continue to write PowerShell.

Using PowerShell for Docker Confidence

It’s been a while since I’ve written. I went from writing approximately six posts per month to nothing since last May. That’s unheard of, but alas, it’s true. A lot has changed in that time, including starting a new job, where there isn’t time for working in PowerShell nearly as much. There is, however, a need for me to up my Docker game. I used it in the past as I opted to learn and work with a CI/CD pipeline. But, as I get deeper into Docker I thought that there might be some people out there with an understanding of PowerShell, that could use it to feel comfortable looking at Docker closer. So, that’s who this is for. If you’re a Docker pro, then the things discussed in this post will likely hold little value. For me, the opportunity to write about this will only cement the Docker concepts in my own brain. With that, and for those where this post makes sense, let’s begin.

The first thing you’re going to need is… well, Docker of course. It’s been just about forever since I installed it, so don’t expect much out of me. Okay fine. Windows folks, go to https://docs.docker.com/get-docker and download the Windows .exe package. This is going to include both the command-line, docker tool, and Docker Desktop. I’ve never really used Docker Desktop before, but I’m not going to lie, after opening it up a time or two recently, I’ve gone back to it. It serves as a nice visual interface in which to help solidify the Docker concepts of containers, images, and volumes. Keep it in mind, sure, but let’s open PowerShell now that Docker is installed, and run our first Docker command.

The first command we’re going to run is a docker pull command. This will download a Docker image from hub.docker.com. Think of an image as a set of instructions to create a container. It’s a standalone, executable package of goodness with all the code, and runtimes, and libraries, and tools needed to run an application. Anyway, let’s pull down our image using the full command of docker pull mcr.microsoft.com/powershell. You can always search at hub.docker.com for the image or images, you want. They’ll even include the full docker pull command for you!

Because I already had the latest, or most recent, version of the PowerShell Docker image, there was nothing for docker to pull for me. When your pull is complete, run docker image ls to see the image(s) you have. You can also open Docker Desktop to see them listed visually. It’s helpful, it really is. In the below image you can see that I have the last two most recent versions of the Docker PowerShell image.

I wasn’t going to do it originally, but I found an additional image of interest, so I downloaded it in order that I could include a couple of additional images — like pictures — in this post. I first downloaded a Python Docker image, as you can see, and then ensured it was included in my list of images.

Now we’re going to have Docker run the PowerShell image, and therefore create a container for us, from that image. Do this using docker run --interactive --tty mcr.microsoft.com/powershell. The --interactive --tty options could’ve been written as -i -t or, even -it. This will come in handy if you don’t know it already! Once the above command is executed, you’ll have a Powershell prompt inside the Powershell Docker container. I know right; it’s awesome! It’s a little Linux container running right there on your bare metal computer or VM, completely isolated from everything else going on. If you’re new to Docker, this may be the push you need to start learning it… before you need it.

Oh yeah, here’s a quick piece of potentially interesting information. Using docker run with the options (arguments) and Docker image name, and Docker will pull down the image if you don’t already have it!