Category Archives: Script Sharing

A collection of helpful PowerShell scripts, functions, and modules.

Show-PSDriveMenu Revisited


Welcome to the 299th post on tommymaynard.com. It’s countdown to 300!


Back on August 14, 2018, I introduced a new utility I had written and shared via the PowerShell Gallery. It was called Show-PSDriveMenu and here’s a quick example of its usage. It lists the currently available drives, includes a marker to indicate the current drive, and allows you to change drives by entering a corresponding drive number.

The problem with the 1.0 version is that when Show-PSDriveMenu was invoked the initial time, it wouldn’t include the marker to indicate the current drive. Well, now it’s included on all the invocations, to include the very first one.

The updated script be downloaded from the PowerShell Gallery using the Install-Script command, included in the PowerShellGet module, using the below example. Be sure to include the Force parameter if you installed the 1.0 version, in order that the older version is replaced by the newer version.

PS > Install-Script -Name Show-PSDriveMenu -Scope CurrentUser

As mentioned in the original post, if you want this function (downloaded as a script file from the PowerShell Gallery), to load each time you start PowerShell, then add one of the two lines of code below to your profile script. The first is for Windows PowerShell (version 5.1 and earlier), and the second is for PowerShell Core (version 6.0 and later).

. $env:USERPROFILE\Documents\WindowsPowerShell\Scripts\Show-PSDriveMenu.ps1
. $env:USERPROFILE\Documents\PowerShell\Scripts\Show-PSDriveMenu.ps1

It’s Available: Show-PSDriveMenu


Welcome to the 295th post on tommymaynard.com. It’s countdown to 300!


Update: There’s a updated post here.

It was only yesterday that I shared something I was writing in PowerShell. It was my take on Get-PSDrive that included a built-in way to change locations, within the file system, by entering the number that corresponds to a drive location. Take a look at the Tweet (and yes, I’m aware of my misspelling).

There was definitely some interest on Twitter. Because of that, I set out to correct at least a couple errors that needed a fix. For now, it’s up and available on the PowerShell Gallery at https://www.powershellgallery.com/packages/Show-PSDriveMenu/1.0/DisplayScript. Use the PowerShellGet, Install-Script function to install it.

PS > Install-Script -Name Show-PSDriveMenu -Scope CurrentUser

Here’s an image from the ConsoleHost. There’s a small visual change that’s been added since the above Tweet. Numeric values with one digit (1-9) have an extra space in front of the digit in order that everything lines up well.

Update: It appears that there may be a problem at first launch, as the marker that indicates the current drive (>), doesn’t appear. It does, however, on subsequent uses. I’ll have to take a look at that at some point and see if it can be isolated and resolved. If you catch it (and, seriously there’s a minimal amount of code in this function), feel free to let me know.

Update: If you want this function (downloaded as a script file from the PowerShell Gallery), to load each time you start PowerShell, then add one of the two lines of code below. The first is for Windows PowerShell (version 5.1 and earlier), and the second is for PowerShell Core (version 6.0 and later). You know, when Windows PowerShell and PowerShell began to mean different things.

. $env:USERPROFILE\Documents\WindowsPowerShell\Scripts\Show-PSDriveMenu.ps1
. $env:USERPROFILE\Documents\PowerShell\Scripts\Show-PSDriveMenu.ps1

Single Run Functions


Welcome to the 294th post on tommymaynard.com. It’s countdown to 300!


Up until yesterday, this was potentially the longest I’d gone without writing. Here’s what happened. Life. No really, it was a combination of wrapping up my part in The PowerShell Conference Book, which, if I might add, has done really well. Thus far it’s funded two scholarships — that’s impressive!

The other half of this combination, is that I started two posts that I just haven’t gotten back to work on. They’re fairly involved. That, and a few projects at work, have taken it out of me in the last couple weeks. You know the nights; you don’t dare turn on a computer. But, as I was writing what you’re read so far this evening, I did think of something to share.

At times you may find yourself only needing, or wanting, certain functions in a module to be executed once. Say you’re building out a new Active Directory Domain; there’s likely tasks that should only be completed a single time. But, how does one handle that?

There’s a couple different ways I can think of off the top of my head. We’ll assume we’re on Windows, and for this post, we’ll take advantage of the Windows Registry. While we can check to see if the thing(s) the functions set out to change, were actually changed, or potentially execute a Pester test, for this post, we’re going to cheat. This may not be the best idea, but here we are, a touch desperate for some new content.

Let’s say the below function, Show-String, is a part of the StringModule. Its only purpose is to write a string to the PowerShell host program — the screen. While this isn’t something you’d likely only want to run once, it will suffice for this demonstration. The Begin block will create a Registry Key called StringModule inside HKLM:\SOFTWARE if it doesn’t already exist.

If it does exist, it’ll check for a value named after the function. If the value “Show-String” exists and holds the Data value of “Complete,” it will exit the function and indicate to the user that the function cannot be run more than once. If “Show-String” exists and holds a Data value other than “Complete,” it’ll exit the function as well; however, it’ll indicate to the user that this function may have been run before. This is based on the Name being correct, but that the Data value being incorrect. That shouldn’t happen, but, why not make this quick check and verification.

Function Show-String {
    Param (
        [Parameter()]
        [string]$String
    )

    Begin {
        $CmdName = "$($MyInvocation.MyCommand.Name)"
        $RegistryPath = 'HKLM:\SOFTWARE\StringModule'

        If (-Not(Get-Item -Path $RegistryPath -ErrorAction SilentlyContinue)) {
            New-Item -Path $RegistryPath | Out-Null

        } Else {
            If ([System.Boolean](Get-ItemProperty -Path $RegistryPath)) {

                If ((Get-ItemProperty -Path $RegistryPath).$CmdName -eq 'Complete') {
                    Write-Warning -Message "The $CmdName function has already been run (Name and Data verified)."
                    break

                } ElseIf ((Get-ItemProperty -Path $RegistryPath).$CmdName -ne 'Complete') {
                    Write-Warning -Message "The $CmdName function may have already been run (Name correct but Data incorrect)."
                    break

                } # End If-ElseIf.
            } # End If.
        } # End If-Else
    } # End Begin.

    Process {
        $String
    } # End Process.

    End {
        New-ItemProperty -Path $RegistryPath -Name $CmdName -Value 'Complete' | Out-Null
        If (-Not(Get-ItemProperty -Path $RegistryPath).$CmdName) {
            Write-Warning -Message "Unable to correctly set Registry so function isn't run more than once."
            return
        } # End If.
    } # End End.
} # End Function: Show-String.

Below we have three different runs of the above function, one right after the next. In the first run, the string is displayed. Additionally, although we don’t see it ourselves, our Registry Key is created, as is the Show-String value containing the string data of “Complete.” In the second run, we receive a warning that indicates this function has already been run. Again, it’s checking the Registry. It knows this at 100%, because the value contains the “Show-String” Name and string data of “Complete.” Just before the third run, I edited the value in Data, so that it was something other than Complete.

PS C:\> Show-String -String 'testing 123'
testing 123

PS C:\> Show-String -String 'testing 123'
WARNING: The Show-String function has already been run (Name and Data verified).

PS C:\> # Edited the data in the Registry value to something other than "Complete"

PS C:\> Show-String -String 'testing 123'
WARNING: The Show-String function may have already been run (Name correct but Data incorrect).

And in closing, what else do you think this needs? I know. A function that will clean up those Registry entries when you really have a need to rerun your function. I did that for my project, because I know there’s going to be those times during testing.

Finally, I do want to mention that this function is interacting with HKEY_LOCAL_MACHINE. This area of the Registry requires administrative permissions. You may need to alter this code to use HKEY_CURRENT_USER. If you choose to do that, every HKLM in the above script would need to be, you guessed it, changed to HKCU.

Another Old HTA: WoL Manager

There’s a not so recent post that I wrote that discussed two old HTAs (HTML Applications) that I wrote long ago. I decided to link the post on the, The Scripting Guys’ Facebook Page (direct link: https://www.facebook.com/groups/5901799452/permalink/10154015674414453). It seemed to me that this group of people might be interested in these HTAs. It made sense, as I had learned plenty about VBS and HTAs from The Scripting Guy, Ed Wilson himself. It turned out to be good idea to share those old HTAs, as there were many downloads. If anyone really used them, I can’t honestly even say.

Someone back when I did this asked if I can, or would, add WoL (Wake on LAN), to the LoggedOnUser HTA. I think the idea was that if the computer didn’t respond, that it would leave the option to wake the computer. While I’m not interested in spending time adding that feature (unless there’s cash involved, or a whole lot of Chipotle gift cards), I did mention another HTA I had written back in my VBS/HTA days, called WoL Manager.

This HTA’s sole purpose was to wake up computers in a computer lab. I had opted to try and save some money by reducing electricity costs, by allowing the lab computers to drop into hibernation, or some form of deep sleep when they weren’t being used during the day, each evening, and over the weekends. It was an often used lab during all hours of the day. Being that I’m very much into Windows PowerShell and automation in general, you can understand that I would rather not walk to the lab every time I needed to wake up a computer. No, I would rather send a WoL magic packet, give it a moment, and then remotely connect to the system, which I did for a few years. The last time this thing was used, was… 2013.

We’ll start by looking at the WoL Manager HTA and then discuss a little more about it. There’s some things you’ll need to know if you opt to try it out. I’ll include a download link at the bottom of this post. Here’s three different views of the HTA: (left) when the HTA is first opened, (center) when the HTA is ready to wake a single computer, and (right) when the HTA is ready to wake all the computers.

script-sharing-another-old-htas-wol-manager-2015-01     script-sharing-another-old-htas-wol-manager-2015-02     script-sharing-another-old-htas-wol-manager-2015-03

Much like the LoggedOnUser HTA, this one also requires a computers.txt files arranged like the example below (name, colon, MAC address). The file needs to remain in the same directory as the HTA. The download includes this file which can be easily edited.

Server01:053ae714eaca
Server02:0044e924f241
Server03:0034e734f2a2
Server04:0043e944f5a3
Server05:0034e654a533
Server06:0043e664e3d3

The HTA requires a third-party executable to wake the computer, which I’m not distrubuting. It’s called wol.exe and can be downloaded here: http://www.gammadyne.com/cmdline.htm#wol. Make sure this executable is located in the same directory with the HTA and computers.txt file, too.

In the environment where I (used to) use WoL Manager, I was able to use the wol.exe command line tool, such as:

PS> wol.exe 0043e944f5a2

It did not require that I include an IP address, like it does in the second example back on this page: http://www.gammadyne.com/cmdline.htm#wol, or in my below example. If your environment requires the IP address of the computer where the HTA is running (not the computer you’re trying to wake), then the HTA will need to be modified (which you’re welcome to do). I think there’s some commented out HTML at the bottom where I may have been getting ready to add this option. I don’t believe there’s any logic in the HTA, however.

I used this same wol.exe executable at home to wake up my desktop computer, although at home, it’s used as part of a Windows PowerShell function. To use this there, my laptop’s IP must be included to wake up the home desktop, such as:

PS> wol.exe 0043e944f5a2 10.10.10.33

If you’re interested in this HTA, start by downloading wol.exe and seeing if it works, first. Also, you may have to make some modifications to the system(s) in which you want to wake, but I’m sure Google, or Bing, can help with setting up the NIC(s) to accept WoL magic packets, if they don’t already.

One final note: I cannot remember for the life of me if the Wake All option actually works, or not. I briefly looked a the subroutine called by clicking that button, and it looks good. Someone might want to let me know. After writing all this, and reading it two years later, maybe I understand why this post as been sitting in my drafts for so long. Enjoy, if you try it and it works!

You can download this HTA here: WoLManager1.1.zip (10445 downloads )

Linux Prompt on Windows – Part IX

In the last installment of this series, just published a day ago, I mentioned that I’d like to be able to pass in the fake username and computer name to my prompt function, instead of relying on a hard coded value. Let’s back up, but seriously, only for a second. There’s only so much I’m willing to repeat if you’re starting to read this series at the ninth installment. You can use the links at the very bottom to easily access any of the previous posts.

My prompt function includes my real username and computer name. At times, I like to switch it to a fake username and computer name, so that screen captures won’t include my real information. These hard coded, fake username and computer name values are inside my prompt function as “tommymaynard@cpux1789.” Until now, I wasn’t able to choose the fake username and computer name, and that’s why we’re here today. I’ve added a way to make these values dynamic. I’ve included everything you’d need to copy to your $PROFILE script in order to use this same prompt function. I warn you however, once you start using it, you may not want switch back. And that’s fine, because it just got better.

Here’s an example, before I include the current prompt code. Do notice that the new UserName and ComputerName parameters are completely ignored when the Type parameter value is “Real.” This is because they are not needed in order to use the real username and computer name.

Unfortunately, the Set-Prompt function didn’t feel like a job for ParameterSets. I imagine I could have accomplished “adding” the UserName and ComputerName parameters, when the Type parameter had a value of “Fake,” using dynamic parameters; however, that’s just way too much involvement for now, and would likely bring little return on the invested time. I can be happy with this, at the moment.

Here’s the newest version. Copy it to your $PROFILE script and try it out! You will need a fake, default username and computer name. Toward the bottom of the code, inside the parameter section of the Set-Prompt function, swap out “tommymaynard” for your default, fake username, and “cpux1789,” with your default, fake computer name.

https://gist.github.com/tommymaynard/0dc88b5927ab5f63ee4738d87252fd63

Part VIII | Part VII | Part VI | Part V | Part IV | Part III | Part II | Part I

Linux Prompt on Windows – Part VIII

Update: There’s a part nine, now.

This, is the topic that will never die.

I’m literally about to begin the eighth installment on this topic. There’s no question about it, I love my prompt. In the last installment, we added a more complete PowerShell version number to the prompt, and the entire version number to the WindowTitle. We also added a Type parameter to the prompt function. This allowed me to manually (yuck) edit my function’s Type parameter default value between “Real” and “Fake.” Well, those days are done.

As of now, I’ve removed the default assignment of the Type parameter ([string]$Type = ‘Real’) in the below prompt function. Instead, I added a second function. It’s toward the bottom of the included code, so let’s meet down there.

https://gist.github.com/tommymaynard/4a544e89f71c00abf3611eb18971c71e

Hello again.

The additional function is called Set-Prompt. As the prompt function and this function are in my $PROFILE script, I can expect that they’re both loaded into memory each time a PowerShell host program is opened. In addition to loading the Set-Prompt function into memory, my $PROFILE script also invokes the function. Even though Set-Prompt has a default value for the Type parameter (much like the prompt function had previously), I still pass the “Real” parameter value to the Type parameter.

What this provides is an assurance that my prompt will always be real (include my real username and computer name), when I open a new PowerShell host. If I want to change it to my fake username and computer name for a screen recording, or screen captures, I simply run the below command. The Set-Prompt function doesn’t truly interact with the prompt function. What it does instead, is it updates the $PSDefaultParameterValues variable, so that every time the prompt function is invoked (each new prompt is created), it includes the correct Type parameter value even though we don’t actually see it entered.

Set-Prompt -Type Fake

Since I can’t seem to get away from adding more to my prompt function and this topic, perhaps we’ll do a ninth installment. I would kind of like a way to pass in the fake username and computer name, too. As of now, those values are still hard coded in the prompt function.

Update: I decided to add a graphic to this post. In the below image, although it looks like two different consoles, we’re actually working in the same PowerShell console. In each instance where it was necessary I hid a portion of the computer name. This may actually assist in seeing the real vs. the fake prompt, as I hid a part of my real computer name. The red line helps to indicate the same command in each image.

Part VII | Part VI | Part V | Part IV | Part III | Part II | Part I

Linux Prompt on Windows – Part VII

Notes: There have been updates to this post since it was first published. See those notes near the bottom of the post.

There no huge changes between the most current version of my Linux prompt and this one, but it has changed. Because of that, I’ve decided a new post is in order. All the other related posts are linked below.

I took a brief look, and here’s what’s changed between this version and the last.

– There’s a $CustomPrompt variable. You can manually set this to either “real” or “fake.” If it’s “real,” it’ll use the true user and computer name in the prompt. If it’s “fake,” it will allow you to use your own fake user and computer name. In this case, it’ll use whatever has been assigned to the $UserComputer variable. Enter the fake user and computer name as <username>@<computername>. As you can see in the code, it currently says tommymaynard@cpux1789. I tend to use this for demos and screen captures where I don’t want my true username and computer name included.

– The other update here was a modification to the version number. Previously we’d have 2.0, 5.1, and 6.0 at the end of our prompt, and in the WindowTitle. Since the release of 6.0.2, the prompt now includes the first digit of the build, or patch number (it was changed to patch in 6.0). The WindowTitle, however, will include the full patch, or build number. Take a look at this screen capture.

I get the feeling someone might see all this code and think the prompt will load too slowly. It doesn’t. But as proof, I’ve included a quick Gif of the prompt in action. Maybe it’s slower than it should be, but even if it is, I bet you can’t tell. Each time a new prompt appears (Enter key is pressed), the below prompt function code has executed. Yeah, it’s not slow, so no worries on the prompt function length (60ish lines).

And without wasting any more of your time, here’s the prompt code. Drop it into your $PROFILE .ps1 file and give it a run. Like me, you just might find out you want it everywhere.

https://gist.github.com/tommymaynard/7f5907c9a4952a7f77b02c85801d3653

Update I: After posting my prompt function, I noticed something that didn’t make sense any longer. The variable assignment of $UserComputer was outside of the real/fake If-ElseIf statement. Therefore, that assignment was moved into the If portion, as it only needs to be set if the prompt is going to use the the real username and computer name.

Update II: I grew tired of the mid function $CustomPrompt variable assignment, and instead opted for a Type parameter that took care of this assignment. While you can’t pass the parameter value at run time, it’s better than burying the variable assignment deep in the prompt. Due to this, there were a few changes that had to be made to the If and ElseIf conditions. This was nothing more that changing $CustomPrompt to $Type (the newly added parameter).

Update III: Yes, another update (to the above code). Previously the switch statement that determined the host for the WindowTitle didn’t have a default value. That’s to say that if you were using a host other than the ConsoleHost, the Windows PowerShell ISE Host, or the Visual Studio Code Host, it would enter nothing in the WindowTitle before the PowerShell version number. Now, it’ll enter $Host.Name… whatever that ends up being.

Update IV: I removed my code and embedded the GitHub Gist where I’ve been keeping my prompt (outside of my $PROFILE script, of course).

Part VI | Part V | Part IV | Part III | Part II | Part I

Read-Host with a Previous Value Part II

It’s simply not everyday that Rob Sewell (@sqldbawithbeard) places a link on Twitter to your blog. Yours, as in mine, but here we are. That was yesterday.

Today, knowing this is out there, I decided to add a bit more to this short post with a second installment. In this post, I’ve written a few changes to the single If statement from Part I. While it was a simple statement to see if I could reuse a previously stored value in the first installment, it’s a bit more full featured now.

We’re getting away from statically setting the default user. We’ll actually let the user assign that value. This determination — whether there’s a default user or not — defines our prompt message, which is most of what’s been added.

If ($User) {
    $Prompt = "Press Enter for the last user [$User], or enter a new user"
} Else {
    $Prompt = "Please enter a new user"
}

If (($Result = Read-Host -Prompt $Prompt) -eq '' -or $Result -eq $User) {
    "You're using the previously used ""$User"" user."
} Else {
    "You're using the previously unused ""$Result"" user."
    $User = $Result
}

I won’t bother including any examples of the running code, but do run the example yourself if you’re interested. Remember to clear or remove the $User, $Prompt, and $Result variables if you want to start fresh. The following command will remove those variables, if you find you need it before a new run.

Remove-Variable -Name User,Prompt,Result -ErrorAction SilentlyContinue

And that’s it. Thanks for the link to the blog, Rob!

Read-Host with a Previous Value

Update: There’s a Part II, read it next.

Here’s a quick one. For an upcoming project, I decided I may need a way to use Read-Host in combination with a previously set, default value. Here’s the working code I threw together in case it’s needed.

$User = 'tommymaynard'

If (($Result = Read-Host -Prompt "Press Enter for the last user [$User], or enter a new user") -eq '' -or $Result -eq $User) {
    "You're using the previously used ""$User"" user."
} Else {
    "You're using the previously unused ""$Result"" user."
}

I set my user to “tommymaynard,” and then the If statement executes. The execution pauses while the Read-Host cmdlet waits for my input. If I press Enter, the If portion runs indicating that I’m using the previously set value of “tommymaynard.” It’ll do that if I enter tommymaynard again, too. If I enter a new value, it indicates I’m using the new value.

While we’re here, let’s run the code with three possible values: nothing entered, tommymaynard entered, and a different user entered.

Press Enter for the last user [tommymaynard], or enter a new user:
You’re using the previously used “tommymaynard” user.

Press Enter for the last user [tommymaynard], or enter a new user: tommymaynard
You’re using the previously used “tommymaynard” user.

Press Enter for the last user [tommymaynard], or enter a new user: lsmith
You’re using the previously unused “lsmith” user.

And there it is, a super short post about using a previously set value for Read-Host.

Measure the Comment-Based Help Synopsis

There was a recent tweet — I believe it was a tweet, anyway — that indicated that a comment-based help synopsis, should only be as long as what’s allowable for a tweet. That’s 140 characters. With that in mind and a few minutes, I wrote this little function to check the character count in a string.

Function Measure-CharacterCount {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [string]$String,

        [int]$CharacterCount = 140
    )
    $StringCount = $String.ToCharArray().Count

    If ($StringCount -le $CharacterCount) {
        Write-Verbose -Message "The string is equal to or less than $CharacterCount characters ($StringCount)." -Verbose
    } Else {
        Write-Warning -Message "The string is longer than $CharacterCount characters ($StringCount)."
    }
}

In the example below, I’ve included two commands: one where the character count is less than 140 and one where it’s greater. On that note, let me add a third where the character count equals the default 140 character, and then 141, just to make sure this thing works…

PS > Measure-CharacterCount -String 'Today is awesome, maybe.'
VERBOSE: The string is equal to or less than 140 characters (24).
PS >
PS > Measure-CharacterCount -String 'Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe.'
WARNING: The string is longer than 140 characters (149).
PS > 
PS > Measure-CharacterCount -String 'Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesom'
VERBOSE: The string is equal to or less than 140 characters (140).
PS >
PS > Measure-CharacterCount -String 'Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome'
WARNING: The string is longer than 140 characters (141).