Countdown Options

In a somewhat recent post here — that I can’t remember off the top of my head — I put something together much like the below example. It completed a countdown from 10 to 1 seconds (or did it count up?), paying attention to whether it should use the word “second” or “seconds.” I’m not going to chase down that post, but do take a look at the example.

10..1 | ForEach-Object {
    If ($_ -eq 1) {
        "$_ second"
    } Else {
        "$_ seconds"
    }
    Start-Sleep -Seconds 1
}

Here’s what that looks like once it’s done executing. Do notice that each string includes seconds up until we get down to one second. It works as expected!

10 seconds
9 seconds
8 seconds
7 seconds
6 seconds
5 seconds
4 seconds
3 seconds
2 seconds
1 second

Before we move on, if you find this in my code, you’ll likely see it use substantially less amount of lines. Like one. While I pride on myself on readable code, something this simplistic and of little consequence need not be on full display at times — it’s up to your discretion.

10..1 | ForEach-Object {If ($_ -eq 1) {"$_ second"} Else {"$_ seconds"}; Start-Sleep -Seconds 1}

I got to thinking (due a project — which is typical), what if we didn’t move downward in the console as we did the count down? Is it possible to overwrite the last countdown indication? What I mean is, is it possible to overwrite 10 seconds, with 9 seconds, and so on? if you’re reading this, then yes, it’s possible.

10..1 | Foreach-Object {
    If ($_ -eq 10) {
        $Cursor = [System.Console]::CursorTop
    }
    [System.Console]::CursorTop = $Cursor
    '{0:d2}' -f $_
    Start-Sleep -Seconds 1
}

The following gif indicates how the above code executes. You can see that the countdown occurs right over the top of itself. There’s no moving downward in the console, and for me, that’s preferred. Do notice that we’re using the the -f formatting operator. I bring this up, because it’s helping me deal with a bit of a problem. If we didn’t use it, and used single digits for the values 9 through 1, we’d have the left over zero from 10 to the right of each single digit. That would make 9, 90, it would make 8, 80, etc. There’s probably a better way to handle this obnoxiousness, but this is the option I went with this evening. I’ll keep this in mind, and perhaps update this post if I determine a better way to avoid double digit, single digits.

In this example, we’ve combined the previous two. We’re including the string values of “seconds,” and “second” in our countdown. I’ve also included a gif for this example further below.

10..1 | Foreach-Object {
    If ($_ -eq 10) {
        $Cursor = [System.Console]::CursorTop
    }
    [System.Console]::CursorTop = $Cursor
    If ($_ -eq 1) {
        "$('{0:d2}' -f $_) second"
    } Else {
        "$('{0:d2}' -f $_) seconds"
    }
    Start-Sleep -Seconds 1
}

Take a look at this gif, and then we’ll discuss it. Ugh.

Did you see it? The problem I mentioned previously is back. It needs to be dealt with now, not some time in the future, which was what was preferred. When it goes to 1 second remaining, our final “s,” in the plural form of seconds, sticks around and gives the appearance the code is failing in a manner different than one might expect at first.

I wanted to get this post published, so let me show you what I did. I’ll do my best to get back to this post later, but for now, here’s the “fix.”

        10..1 | Foreach-Object {
            If ($_ -eq 10) {
            $Cursor = [System.Console]::CursorTop
            }
            [System.Console]::CursorTop = $Cursor
            "Seconds remaining: $('{0:d2}' -f $_)"
            Start-Sleep -Seconds 1
        }

Yeah, you saw that correctly. I’ve moved the numeric values to the end, or right-most, portion of our string. This means that we’re not going to leave the final “s” in seconds. Even so, we do have to continue to use double digits for single digits (the whole leading-zero thing). I can live with that for now. Have a look. Now to add this to my project, so the user can have something to do while these seconds pass before a restart.

Skip Process and End Blocks

I started to reply to this recent Tweet on Twitter when I stopped and jumped into a new post here instead. It’s been awhile, but I’ve found a Tweet I couldn’t reply to in the allotted characters allowed by Twitter (these days). This isn’t to say I reply to everything. It does say however, that I’m intrigued by the Twitter, posed question. Here’s that Tweet now. Well, mostly. It was deleted at some point after I started this post, so here’s the text that was that Tweet.

“Is there a fast way to return from Function from within Begin {}
without having to go thru Process/End ? #PowerShell”

— (Name and handle removed) October 16, 2018

My first thought here was, why would Process and End take so long to finish that someone wouldn’t want them to execute? In my world, we put as little code into our functions as possible. I’ve said it multiple times already, but I’d rather have five, 20 line functions, before I had one, 100 line script. This makes troubleshooting so much easier. If I can immediately remove 60, or 80, lines in which to deal with, then I’m all for it. Well, I’m not in charge of everyone’s functions and scripts, so I quickly pushed that thought away. This and that fact that there wasn’t a mention of too much code in the Process and End blocks.

Next, I recalled what I’ve done a few times. In some tooling I’ve written in the past, I’ve created a variable that only when True ($true), would it allow the code to fully enter into the Process/End block. Something like the below example, which uses the same variable to determine whether the blocks, other than the Begin block, are executed.

Here’s how this works: The below Limit-BlockUsageOne function chooses a random number — either 1 or 2 — inside the Begin block. If the numeric value is 1, as stored in $RandomNumber, we will enter the Process and End blocks. If it’s 2, we will only enter the two additional blocks long enough to know we won’t run any additional code from inside those two blocks. We can’t completely avoid them, or skip them, but we can get close.

Clear-Host
Function Limit-BlockUsageOne {
    [CmdletBinding()]
    Param (
    )

    Begin {
        $RandomNumber = Get-Random -Minimum 1 -Maximum 3
    } # End Begin
    Process {
        If ($RandomNumber -eq 1) {
            "Inside the Process block."
        } # End If.
    } # End Process.
    End {
        If ($RandomNumber -eq 1) {
            "Inside the End block."
        } # End If.
    } # End End.
 } # End Function: Limit-BlockUsageOne

1..10 | ForEach-Object {
    "-----Execution # $_-----"
    Limit-BlockUsageOne
}

So again, while we do enter the Process and End blocks, we leave almost instantly when the value stored in the $RandomNumber variable is a 2. Here’s some random results, based on the Get-Random cmdlet, which is used inside the Limit-BlockUsageOne function.

-----Execution # 1-----
-----Execution # 2-----
-----Execution # 3-----
-----Execution # 4-----
Inside the Process block.
Inside the End block.
-----Execution # 5-----
Inside the Process block.
Inside the End block.
-----Execution # 6-----
-----Execution # 7-----
-----Execution # 8-----
Inside the Process block.
Inside the End block.
-----Execution # 9-----
Inside the Process block.
Inside the End block.
-----Execution # 10-----
Inside the Process block.
Inside the End block.

Another thought, because there was at least a couple, was to make use of the break command within the Begin block. Part of me wonders if this option is why the Tweet was deleted — did he figured out the answer to his own question? In this next example, we simply exit the function the moment after we write the “Begin” string. Simple.

Function Limit-BlockUsageTwo {
    Begin {
        'Begin.'
        break
    }
    Process {
        'Process'
    }
    End {
        'End.'
    }
} # End Limit-BlockUsageTwo.

The output of the above function is always going to be the string “Begin”. Because of the break command, we’ll never see the string “Process” or “End” output to the screen. Do keep in mind that we can add some logic to determine whether or not the break command is actually  executed. This, much as we did when we checked for the numeric value of 1 and 2 in the previous example. Do note that this option can in fact completely avoid executing any code inside the Process and End blocks. Based on the Tweet, that’s exactly what the user was after.

While I can’t think of an official way to avoid going through Process or End — you know, like something built in — there’s a couple ways to get there and get close, even if it’s not official.

Run One from the Other

It wasn’t but a few days ago that I saw something that piqued my interest. It was an example of running a command against PowerShell Core (pwsh.exe) from Windows PowerShell (powershell.exe), or maybe it was the other way around? Whichever, I’ve included an image that shows this working in both directions. That’s to say it’s an example of running the same command in both versions of PowerShell, from each version of PowerShell.

I do suspect the picture will help explain things, if that last paragraph didn’t.

In the above image, we can see that we’ve run a command against both Windows PowerShell (5.1) and PowerShell Core (6.1). In both consoles, we’ve run the same command; it’s included below. Its goal was to return the version of PowerShell, for both versions of PowerShell.

'powershell.exe','pwsh.exe' | ForEach-Object {
    & $_ -Command {$PSVersionTable.PSVersion}
}

Our results are identical; we knew, we were running 5.1 and 6.1. Neat, right!? Keep this trick in your back pocket, as I do suspect it may be helpful for one of us, one day. Maybe it won’t have anything to do with obtaining the version, of a version of PowerShell, and instead someone will find another use.

Here’s a start. The thought I had was, can I run an Active Directory command in Windows PowerShell (powershell.exe) from PowerShell Core (pwsh.exe)? You bet I can.

In the below example, I’ve returned my GivenName from Active Directory using Windows PowerShell, from PowerShell Core. That could be potentially helpful for someone in some yet-to-be-thought-of project.

Especially in the case of Active Directory, it’s important to remember that each time a command is run, it must import the Active Directory module. Consider that each command is spinning up a new powershell.exe process. For all we know, this may be the reason why the WindowsCompatibility module uses PowerShell sessions, and not PowerShell processes.

In this next example, we issue a Windows PowerShell only command that’s tied to a builtin Microsoft Windows PowerShell model. We issue the Get-LocalUser cmdlet out of the Microsoft.PowerShell.LocalAccounts module. While it’s still importing a module into a powershell.exe process, it loads quicker than the ActiveDirectory PowerShell module did.

I’m going to need to call this a night here soon, but I keep finding more things to try. Like this example. There’s two commands. The first one runs Windows PowerShell, which runs PowerShell Core to determine the version of PowerShell Core.

The second one runs Windows PowerShell, which runs PowerShell Core, which runs Windows PowerShell to determine the version of Windows PowerShell. The image for these examples might be easier to understand, too.

Alright, that’s it for now. Perhaps I’ll come up with some other ideas to try another day. I need to put this post, and me, to bed.

Command Type and Name

Sometimes I just need to write something down somewhere. That way, I might be able to find what I need when I need it. Have you been to my website? If so, then just maybe you already know this. Over the last many years, I’ve done this repeatedly. It’s basically why this place exists. Best part, it’s not just for me. It’s for me and the 196.5 people that have visited daily over the last 20 days, and those before them. That excludes weekends, of course, as only some of us PowerShell on the weekend.

As you may know, we have way to determine a command type and command name (think function and function name), from within a command itself, as it executes. I’m tired of looking for this code inside something I’ve already written, or figuring out again, and so here we are. I’ve given myself another chance to “remember” it sooner. Here’s how it’s done.

Function Get-CommandInfo {
    $CmdType = "$($MyInvocation.MyCommand.CommandType)"
    $CmdName = "$($MyInvocation.MyCommand.Name)"
    "The name of this $($CmdType.ToLower()) is $CmdName."
} # End Get-CommandInfo.

In the above function, we create two variables — $CmdType and $CmdName. These hold, as you might expect, the type of command we’re executing and the name of it, as well. These variables are made possible due to the $MyInvocation automatic variable. This variable holds a great deal of information. As you can tell, we’ve used the CommandType and Name nested properties that reside inside the MyCommand property. We then echo a string to include the two values we’ve derived from this variable.

PS > Get-CommandInfo
The name of this function is Get-CommandInfo.

And, there it is; the type and name returned. Now to remember that I’ve written about it here, for that next time I don’t feel like exploring the $MyInvocation variable again, or tracking down the use of these properties somewhere else. Enjoy your Wednesday.

Making Dates: Good and Better

I have a new task. Review some old code written in the time of (Windows) PowerShell 2.0 ensuring I can support it, in case that’s ever needed.

Well, I started last week. While I haven’t been able to get back to it just yet, as some other work came up, I did code a potential change. First off, before you see this code, I didn’t write the original myself. Second, the person that did, would probably do if differently now, too. I’m not here to dis on anyone’s five-year-old code. It’s quite old, in this industry.

Here’s the code as I found it:

$Today = Get-Date
$DateArray = (Get-Date $Today.AddDays(-59) -Format "M-d-yyyy"),(Get-Date $Today.AddDays(-58) -Format "M-d-yyyy"),(Get-Date $Today.AddDays(-57) -Format "M-d-yyyy"),(Get-Date $Today.AddDays(-56) -Format "M-d-yyyy"),(Get-Date $Today.AddDays(-55) -Format "M-d-yyyy"),(Get-Date $Today.AddDays(-54) -Format "M-d-yyyy"),(Get-Date $Today.AddDays(-53) -Format "M-d-yyyy"),(Get-Date $Today.AddDays(-46) -Format "M-d-yyyy")

It’s a bit tough to follow. Let me use the included commas as line breaks. Do keep in mind that we can always move to the next line after a comma, and not interrupt our command. It already looks better; it’s much easier on the eyes, at minimum.

$Today = Get-Date
$DateArray = (Get-Date $Today.AddDays(-59) -Format "M-d-yyyy"),
(Get-Date $Today.AddDays(-58) -Format "M-d-yyyy"),
(Get-Date $Today.AddDays(-57) -Format "M-d-yyyy"),
(Get-Date $Today.AddDays(-56) -Format "M-d-yyyy"),
(Get-Date $Today.AddDays(-55) -Format "M-d-yyyy"),
(Get-Date $Today.AddDays(-54) -Format "M-d-yyyy"),
(Get-Date $Today.AddDays(-53) -Format "M-d-yyyy"),
(Get-Date $Today.AddDays(-46) -Format "M-d-yyyy")

Even though the readability is better, we can still clean this up some more. Before we do that however, let’s discuss what this code does. It essentially creates eight, string dates. Yesterday’s date was October 15, 2018. Based on that date, this code is designed to create string dates from dates in the past. It creates a date string from 58 days ago, 57 days ago, 56 days ago, 55, 54, 53, and even 46 days ago. We’ll run the code now, and return the $DateArray variable.

PS > $DateArray
8-17-2018
8-18-2018
8-19-2018
8-20-2018
8-21-2018
8-22-2018
8-23-2018
8-30-2018

Before I show you what I came up with to change this code, I do want to remind everyone that just because you can simplify something, doesn’t mean you always should. This is especially true if it’s going to make the next person to see your code wonder what the hell you did. I don’t think I’ve done that in this case.

In the below code, we set things up in a ForEach-Object loop. We iteratively send in the numeric values of -59 though -53, and -46. You’ll see that these same values were used in the above example. Inside our loop, we’ll run the Get-Date command subtracting the number of days, based on the number submitted to the loop on that iteration. Additionally, we format the date such as we did originally.

$DateArray = -59..-53 + -46..-46 | ForEach-Object {
    Get-Date -Date (Get-Date).AddDays($_) -Format 'M-d-yyyy'
}

As you can see below, we get the exact same output as we did above. We, simplified the code in such as way that it’s easier to follow visually and intuitively. There’s no speed improvement, as best I could tell, but I’ll still take what I’ve written over what was written years and years ago.

PS > $DateArray
8-17-2018
8-18-2018
8-19-2018
8-20-2018
8-21-2018
8-22-2018
8-23-2018
8-30-2018

Until next time, and for me at least, back to these two PowerShell projects!

Those AWS Region Commands

More and more, Amazon Web Services has become a significant part of my day. Luckily for me, PowerShell and AWS, work well together. There’s long been the AWSPowerShell module, which much like the AWS CLI, allows us to issue API calls to AWS from a command line.

As a part of continuing my journey into AWS, and maintaining my mild obsession with PowerShell, I’ve decided to better learn a few PowerShell cmdlets, from the AWSPowerShell module throughout at least a couple of posts. As of version 3.3.365, the module only contains a few thousand cmdlets. It seems like AWS has gone ahead and made an investment here in PowerShell. Especially, when it wasn’t even terribly long ago that there were only 2,000.

(Get-Command -Module AWSPowershell | Measure-Object).Count
4499

Oh yeah, that reminds me, Lambda supports PowerShell (Core) now, too. As I read somewhere recently, “It’s not Bash, it’s not Ruby. It’s PowerShell.”

In a few previous, AWS-specific posts I was able to point out some things I thought should be changed. And somehow, AWS paid close enough attention, that some changes were actually made. It’s still hard to believe; it’s a bit surreal.

Hashtag AWS Tweet Prompts Fix to AWSPowerShell Module
AWS Stop-EC2Instance Needs Complimentary Cmdlet
More AWS PowerShell Changes Due to Twitter (and Me)

I’m mostly writing this evening to help solidify a few commands for my own education, and anyone else who is reading along. But… as I experimented with some of these AWS PowerShell cmdlets, I couldn’t help but feel that some changes were in order. So, with that knowledge, let’s review a few Region-specific cmdlets and touch on some potential changes, as they make the most sense to me.

Get-AWSRegion: “Returns the set of available AWS regions.”

When the Get-AWSRegion cmdlet is invoked, it returns the Region Name (the full name), the Region (abbreviated Region Name), and whether or not the Region is the default in the shell. In this first example, you can see that the default output returns all the Regions.

Get-AWSRegion

Region         Name                      IsShellDefault
------         ----                      --------------
ap-northeast-1 Asia Pacific (Tokyo)      False
ap-northeast-2 Asia Pacific (Seoul)      False
ap-south-1     Asia Pacific (Mumbai)     False
ap-southeast-1 Asia Pacific (Singapore)  False
ap-southeast-2 Asia Pacific (Sydney)     False
ca-central-1   Canada (Central)          False
eu-central-1   EU Central (Frankfurt)    False
eu-west-1      EU West (Ireland)         False
eu-west-2      EU West (London)          False
eu-west-3      EU West (Paris)           False
sa-east-1      South America (Sao Paulo) False
us-east-1      US East (Virginia)        False
us-east-2      US East (Ohio)            False
us-west-1      US West (N. California)   False
us-west-2      US West (Oregon)          False

Wrong. Be sure to take a look at the examples further below. As you’ll see there are a couple of parameters—IncludeChina and IncludeGovCloud—that add some Regions that aren’t there by default. I’m not suggesting a change here, mostly, but Get-AWSRegion should return all the Regions, right?

Based on the cmdlet name alone, I suspected that all Regions were going to be listed in the output that’s returned. Good thing I looked into this cmdlet’s parameters, instead of assuming that the Regions were actually all included. And why China? I get why we might make an exception for GovCloud—we shouldn’t—but what was the thought in regard to China? You’ll see what I mean in the following examples.

(Get-AWSRegion | Measure-Object).Count
15
(Get-AWSRegion -IncludeChina | Measure-Object).Count
17
(Get-AWSRegion -IncludeChina -IncludeGovCloud | Measure-Object).Count
18

Now, let’s take a look at the SystemName parameter included in Get-AWSRegion. This is where it becomes quickly evident to me that we can definitely do better. Why does the Region property use a parameter called SystemName? I think maybe that parameter needs a Region parameter alias, at minimum.

Get-AWSRegion -SystemName us-west-1

Region    Name                    IsShellDefault
------    ----                    --------------
us-west-1 US West (N. California) False

Get-AWSRegion -SystemName us-west-2

Region    Name             IsShellDefault
------    ----             --------------
us-west-2 US West (Oregon) False

Get-AWSRegion -SystemName us-west-*

Region    Name    IsShellDefault
------    ----    --------------
us-west-* Unknown False

I didn’t spend any time reading the help for Get-AWSRegion, but as you can see directly above, the wildcard character isn’t supported with the SystemName parameter. That’s too bad. That would’ve been a great addition to this cmdlet (and one that can still be added). To use a wildcard character against this value, you’re required to pipe your output to the Where-Object cmdlet and therefore, filter it further down the pipeline. Yes, this does mean that all results are piped to Where-Object, whereas a wildcard character built in to Get-AWSRegion, would filter immediately and avoid the need for the pipeline. The pipeline is crucial to the language, but when it’s not needed, we’re better off.

Get-AWSRegion | Where-Object Region -like 'us-west-*'

Region    Name                    IsShellDefault
------    ----                    --------------
us-west-1 US West (N. California) False
us-west-2 US West (Oregon)        False

And if you’re wondering, Get-AWSRegion doesn’t include a Name parameter, so there’s no checking there for the ability to use wildcards.

Get-DefaultAWSRegion: “Returns the current default AWS region for this shell, if any, as held in the shell variable $StoredAWSRegion.”

This command’s purpose, as indicated, returns the Region the AWSPowerShell module’s commands should use by default. If the command returns nothing, then a default hasn’t been set. If it does, then someone has likely already used the command we’ll discuss after Get-DefaultAWSRegion: Set-DefaultAWSRegion.

Get-DefaultAWSRegion
# Nothing here yet...

Before we move on, the commands that include the string “Default” before “AWS,” should have instead maintained the same noun prefix as Get-AWSRegion. That’s right, each of these three cmdlets should’ve included the verb, the dash, and then the string AWS, before the remaining portion of the included nouns. Why in the world would we stuff “AWS” into the middle of some command names and at the beginning of others? Amazon should have maintained a consistent prefix. Every Microsoft Active Directory command uses an AD prefix, right after the dash. You’ll never find it anywhere else. Even in the office, the prefix we use on our self-written functions is right where we’ve been trained to expect it:

<ApprovedVerb>-<Dept><SingularNoun(s)InCamelCase>

In my experience, AWS isn’t afraid of using command aliases—so one command can resolve to another—discontinuing the use of parameter names at times, and changing cmdlet names altogether. Therefore, I suspect someone needs to revisit these three. It’s not like Get-AWSRegionDefault, Set-AWSRegionDefault, and Clear-AWSRegionDefault don’t make sense and are already being used. The current commands should be aliases to these three, keeping the prefixes in the proper place.

Get-DefaultAWSRegion -> Get-AWSRegionDefault
Set-DefaultAWSRegion -> Set-AWSRegionDefault
Clear-DefaultAWSRegion -> Clear-AWSRegionDefault

While we’re here, we need to stop using the plural form of any nouns. That said, I do recognize this move is happening, such that Get-AWSCredentials is an alias that resolves to Get-AWSCredential. Oh look at that, the AWS prefix is in the right place on those two!

Set-DefaultAWSRegion: “Sets a default AWS region system name (e.g. us-west-2, eu-west-1 etc) into the shell variable $StoredAWSRegion. AWS cmdlets will use the value of this variable to satisfy their -Region parameter if the parameter is not specified.”

The first thing to notice here is that we have a Region parameter. That’s what Get-AWSRegion should’ve been included (in addition to SystemName [as not to create a breaking change]). Maybe make Region the actual parameter, and SystemName an alias to the Region parameter. That sounds like the best way to phase out that parameter.

Set-DefaultAWSRegion -Region ca-central-1
Get-DefaultAWSRegion

Region       Name             IsShellDefault
------       ----             --------------
ca-central-1 Canada (Central) True 

Clear-DefaultAWSRegion: “Clears any default AWS region set in the shell variable $StoredAWSRegion.”

Get-DefaultAWSRegion
Region       Name             IsShellDefault
------       ----             --------------
ca-central-1 Canada (Central) True
Clear-DefaultAWSRegion
Get-DefaultAWSRegion
# Nothing here again.

This evening we covered some Region cmdlets from the AWSPowerShell module. They all do what they should in the end, but in my mind, there’s some room for some changes for consistency’s sake and overall improvement. Perhaps we’ll do this again… there are some credential-related AWS cmdlets I’m going to need to learn once. and. for. all.

Change Prompt on Module Import

To me, my PowerShell prompt is quite important. I’ve written about it nine times already. While I’m not going to write about it again, so much, I am going to focus on an updated prompt I’ve created for a recent project. I couldn’t help but take a few things into this project from my prompt, and that’s why that’s been mentioned.

I recently received a screen capture from someone running into a problem using one of the tools I’ve written. Sure, it needs some error checking, I won’t deny that, but it was a very obscure and unforeseen problem. You know, how we often find out about error conditions.

This tool, or function, can be run on an Amazon Web Services EC2 instance within a project, to determine the status of its partner EC2 instance. When it works, it returns the status of the other instance, to include things like running, stopping, stopped, etc. There’s also a couple other companion functions that can start and stop the partner instance. The second of the two machines has very high specifications, and so we ask that our users shut down those secondary instances when they’re not running experiments. They pricey.

The problem is that when I look at this error, the prompt doesn’t tell me enough. I can only tell it’s PowerShell — the PS in the prompt — and the current path — some of which has been hidden in this first image. I want more information without having to ask the user, and so I’ve added that in.  Here’s the default prompt up close.

The new prompt includes the username (to the left of the @), the computer name (to the right of the @), the project name (while it’s not in this example, it’s normally a part of the computer name), the path, and whether the user is an admin (#) or not ($). Now, when I receive a PowerShell screen capture, I already have a few of my first questions answered.

All of the functions, such as the one that was run that generated this error, are a part of the same PowerShell module. There’s somewhere near 20 of them so far, and I keep finding reasons to add new ones. If you don’t know, creating a PowerShell script module that includes a module manifest file (a .psd1), allows one to include scripts that execute just before a module is imported. This, whether the module is imported manually using Import-Module, or by invoking a function from within the module, when the module hasn’t yet been imported.

Let’s take a look at my SetPrompt.ps1 script file that executes when my PowerShell module is imported.

Function prompt {
    # Determine Admin; set Symbol variable.
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'} Else {$Symbol = '$'
    }

    # Create prompt.
    "[$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $($executionContext.SessionState.Path.CurrentLocation)]$Symbol "
} # End Function: prompt.

With this script in place and a ScriptsToProcess entry in the module’s manifest file, pointing to this file, I can be ensured that the user’s prompt will change the moment my module is imported. From here on out, I can rest assured that if a user — of these machines at least — sends us a screen capture that it’ll include relevant pieces of information I would have had to ask for, had this prompt not been in place.

There’s a final thought here. When the user is done with this PowerShell module, they’re still going to have this prompt. In my case, it’s perfectly suitable because my users won’t be in PowerShell, unless they’re issuing commands from the module. At least, I can’t imagine they will be. The only other thoughts I’ve had about this “problem” would be to (1) teach users to remove the module, and have code in the prompt monitor whether the module is loaded or not, and revert the prompt if the module is removed, or (2) revert the prompt if a command outside of my module is invoked.

That’s it for today. I don’t have it shown here, but it’s neat to see the prompt alter itself when the module is loaded. Something to keep in mind, if you find yourself in a similar situation.

Update: I was annoyed that $HOME, or C:\Users\tommymaynard, was being displayed as the full path, so I made some additional modifications to the prompt that’s being used for this project. It’ll now look like this, when at C:\Users\tommymaynard (or another user’s home directory).

Here’s the new prompt function, to include a better layout.

Function prompt {
    # Determine Admin; set Symbol variable.
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'
    } Else {
        $Symbol = '$'
    }

    If ((Get-Location).Path -eq $env:USERPROFILE) {
        $Path = '~'
    } Else {
        $Path = (Get-Location).Path
    }

    # Create prompt.
    "[$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Path]$Symbol "
} # End Function: prompt.

Self-Destruction Script

As a part of some work I’m doing, I’ve decided that I need a specific script to do some final configuration for me. More to come on that in time, perhaps. The point here, however, is that a PowerShell script completes my final configuration, and when this final configuration is complete, I have no need for my script any longer. Can a PowerShell script delete itself? Sure it can.

How does a script delete itself? Easy. It’s a single line, really. However, I have a mildly more interesting example. Before you take a look at the included gif — this really does lend itself to a visual — here are the contents of my self-destruction script. It echos an indication that the script will self-destruct. At this point, it begins a countdown in seconds, from 5 to 1, before removing itself — the script file — from the file system. Take a look, and maybe, just maybe, you’ll find yourself in need of something like this sometime, too.

Write-Output -InputObject 'This script will self-destruct in 5 seconds.'

5..1 | ForEach-Object {
    If ($_ -gt 1) {
        "$_ seconds"
    } Else {
        "$_ second"
    } # End If.
    Start-Sleep -Seconds 1
} # End ForEach-Object.

Write-Output -InputObject 'Self destruction.'

# Here's the command to delete itself.
Remove-Item -Path $MyInvocation.MyCommand.Source

And here’s, the visual representation. It’s a bit small, but you should be able to determine what’s happening. The script executes on the left, and once the countdown is over, DeleteSelfScript.ps1 is removed on the right. It’s deleted itself.

Enjoy the week, and learn something new!

Planet PowerShell Content Aggregator

At first, I remember wondering, why do we even need such a thing, but in time, I’ve come to appreciate and respect the efforts behind Planet PowerShell and its Twitter feed. As I’ve stated previously, I’ve used Twitter for years for the PowerShell content it’s been able to provide. In many ways, the content available on Twitter via the PowerShell hashtag, has taught me a part of what I know about PowerShell. Twitter alone seemed like enough at first, but when Mike F. Robbins suggested I included my blog on Planet PowerShell, I took it as a compliment; I wasn’t about to say no. In time, I’ve come to see the benefit.

I can’t tell you exactly how long the posts on my personal site have been Tweeted by Planet PowerShell, but it has easily improved my numbers. While I’m not obsessed with my visitor count, I do appreciate to see those numbers go up for the simple fact that I’m continuing to give back to the community, and that others can benefit from my old friend — learning PowerShell in part, thanks to Twitter.

Yesterday morning, I Tweeted the following:

In a about a half of a day’s time, the follower count for Planet PowerShell was up by 50 new followers (758 to 808). As of today, it’s now at over 100. It currently has over 860 followers with the hope it would break past 1,000, total followers. Therefore, since this post will be posted to Twitter by Planet PowerShell, Tweet this post. I’m hopefully we can get 1,000 followers. Additionally, if you have a PowerShell blog, head over the the website and get yourself added. Share you secrets, tips, and tricks with as many people as you can.

Top 50 PowerShell Bloggers 2018

Hey, look at this! I’m a part of the Top 50 PowerShell bloggers of 2018.

I’m number 33, to be exact. I’m not sure what it’s worth, but it’s a neat honor nonetheless. All this mentioned, it’s wrong when it says I post once a month. Based on the years I’ve been doing this, it was 6 posts per month on average as of August 2018. You know, a few days ago.

I suspect I’ll be at this in a year’s time, so just maybe I’ll move up from 33 over the next 12 months — it’s possible!