AWS Stop-EC2Instance Needs Complimentary Cmdlet

Well, it’s time to try this again: Can a Tweet get another change to the AWSPowerShell module? Maybe you’ve read a recent post about an inconsistent parameter name between the same parameter, in two corresponding cmdlets. One tweet later and that was on its way to being fixed. You can read about that here: http://tommymaynard.com/twitter-reply-hashtag-aws-tweet-prompts-fix-to-awspowershell-module-2016.

I get that a tweet probably isn’t the preferred method of reporting problems, but it worked before, so why shouldn’t it again? That said, if there is a preferred method, then I’d be happy to use that, although it doesn’t make for as good a story.

So, here’s where we need a fix. The Stop-EC2Instance cmdlet includes a Terminate switch parameter that indicates it will terminate the instance (think, remove) in addition to stopping it. It works; it really does terminate the instance. The problem here is that the help file indicates it will prompt the user for confirmation unless the Force parameter is also included. Nope; it doesn’t prompt. Not for me at least. It does terminate the instance, however, and it does so with no questions asked. It didn’t get me, but it might ruin someone’s day someday.

I didn’t try it, but it seems that there’s some built-in protection, although it’s not the default.

aws-stop-ec2instance-needs-complimentary-cmdlet01

So you can see it yourself, here are two PowerShell commands I ran against two different EC2 instances. In the first command, I simply stop an EC2 instance. In the second, I added the Terminate switch. As I mentioned, it works, but there wasn’t a confirmation prompt. This was tested on AWS Tools for Windows PowerShell 3.1.66.0 and the newest version as of this writing, 3.1.71.0.

aws-stop-ec2instance-needs-complimentary-cmdlet02

aws-stop-ec2instance-needs-complimentary-cmdlet03

My suggestion to resolve this problem is to remove the Terminate switch from the Stop-EC2Instance cmdlet, and make a Remove-EC2Instance cmdlet. Maybe alias it with Terminate-EC2Instance, as Terminate isn’t an approved verb. In fact, when I first wanted to remove an EC2 instance, I started by hunting for a Remove-EC2Instance cmdlet. Tracking down the Terminate switch of Stop-EC2Instance was the last place I would’ve looked. It seems to go against all PowerShell conventions. Does Stop-Transcript have an option to remove a transcript as part of its usage? Does Stop-VM in Hyper-V delete the VM? Does Stop-Cluster include a way to delete the cluster? No. Terminating an instance shouldn’t be a part of stopping an instance unless Stop-EC2Instance can be piped to Remove-EC2Instance. Think about the safety that a separate cmdlet would provide (and it’s not like the AWS Tools team is shy about adding new cmdlets).

There are a couple of cmdlets out there that can be used to get the metadata from a compiled PowerShell cmdlet. We use it as a part of writing proxy functions. By using this, I was able to determine the reason why it doesn’t prompt. The problem, as best I can tell, is that Stop-EC2Instance‘s ConfirmImpact CmdletBinding attribute is set to Medium and the default value of the $ConfirmPreference variable is High.

aws-stop-ec2instance-needs-complimentary-cmdlet04

This means that no matter how the Stop-EC2Instance cmdlet is run, it’s never going to prompt for confirmation to terminate, unless someone has modified their $ConfirmPreference value to Medium, and that’s not likely at all. Since a cmdlet, or advanced function, can’t have more than one ConfirmImpact argument, we need the terminate functionality removed from Stop-EC2Instance and the development of a Remove-EC2Instance cmdlet. For comparison, here’s the CmdletBinding attribute for the Remove-ADGroupMembership Active Directory cmdlet. This one always prompts unless we include -Confirm:$false.

aws-stop-ec2instance-needs-complimentary-cmdlet05

I get this fix is much more involved than the last one I brought up (changing the name of a parameter), and would require more work. Additionally, it’s probably one of those things that have to be discussed among the AWS Tools team members and management. Before I close, I should make sure everyone knows that AWS is still new to me, and as such, there may be things that weren’t considered, or known, when this post was written.

Thanks for reading, and enjoy the upcoming weekend.

PSMonday #3: Monday, May 16, 2016

Topic: Quotes and Strings, and Their Rules

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

Today, I’d like to share how to handle quotes in a string. A string is any group of alphanumeric characters to include any punctuation. Think of a string as a sentence, or a word. A string, at its smallest component, can be a single letter, a number, or as mentioned, punctuation. Yes, numbers can be strings too, although they lose their numeric properties.

It is recommended to always use single quotes to encapsulate a string, unless there’s a need for double quotes (they have a special purpose). This is considered best practice by the PowerShell community. The first example string has no internal, interruptive punctuation, such as another single quote, or apostrophe.

$String = 'A string without any quotes.'
$String

A string without any quotes.

The next example string uses internal, double quotes. They are non-interruptive in this instance, because the string begins and ends with single quotes.

$String = 'A string with "double" quotes.'
$String

A string with "double" quotes.

This next example includes both single and double quotes inside a string, that begins and ends with single quotes. Notice that to do this, we needed to put two side-by-side single quotes, around the word single.

$String = 'A string with ''single'' and "double" quotes.'
$String

A string with 'single' and "double" quotes.

One of the things that you might expect to work, is escaping the single quotes instead of using side-by-side single quotes. This doesn’t work.

$String = 'This is a string with `'single`' and "double" quotes.'

At line:1 char:36
+ $String = 'This is a string with `'single`' and "double" quotes.'
+                                    ~~~~~~~~
Unexpected token 'single`'' in expression or statement.

Although I said to use single quotes to encapsulate a string, you can use double quotes if you have internal single quotes, or apostrophes, and you don’t want to use side-by-side single quotes.

$String = "I'm only using 'single' quotes in this string."
$String

I'm only using 'single' quotes in this string.

The final reason you might use double quotes is when you want to expand, or display, the value stored in a variable(s) inside your string. You can’t do this with single quotes. Notice in the below example that we’re using side-by-side double quotes in this string.

$Word1 = 'string'
$Word2 = 'quotes'
$String = "This is a $Word1 with 'single' and ""double"" $Word2."
$String

This is a string with 'single' and "double" quotes.

Bonus: Back to escaping, you can escape double quotes, so the example above could have been written like the example below, and therefore, not required using side-by-side double quotes. Don’t forget about apostrophes in your contractions (I’m, don’t, aren’t, etc.). You’ll need to determine a way around those as you create strings where they’re included.

$String = "This is a $Word1 with 'single' and `"double`" $Word2."
$String

This is a string with 'single' and "double" quotes.

And with that, the third PowerShell Monday is complete. As mentioned previously, please let me know if there’s something you’d like to see discussed.

Write-Output Gets Foreground and Background Colors and More

Every once in a while a forum post comes along that really captures my interest. Take this one for instance.

In this post, the creator wanted to build out a start up menu in the ConsoleHost that has a solid white line, another white line below that, with the words “PowerShell Project 1” in it, and a final white line beneath that. That might be difficult to imagine, so here’s a visual representation, and then what his/hers looked like. I had recommended to use a here-string to fix the problem, but unfortunately we didn’t have the same results.

write-output-gets-foreground-and-background-colors-and-more01
Note: While you can’t see them, in both this and the below image, there are spaces on the lines above, below, and before and after “PowerShell Project 1.” This is how we can ensure the white rectangle around the text.

write-output-gets-foreground-and-background-colors-and-more02

I can’t say for certain why theirs rendered differently, but I suspect version, or PSReadline, or possibly console font/size. That wasn’t important; what was, was trying to figure out a way around this problematic inconsistency. We have the option to change the background color of the area where we type, and so I wondered if I could temporarily do that inside the ConsoleHost. It turns out I could. With that new piece of knowledge, I set out to write a wrapper function around Write-Output. In my version — Write-TMOutput — it includes -ForegroundColor and -BackgroundColor parameters with an option to horizontally and vertically pad the text (object).

First, here’s a few images to show my new function in action. This first image shows the commands included in the TMOutput module.

write-output-gets-foreground-and-background-colors-and-more03

The next example image shows some basic, Write-Output type usage. One where we pipe to the function, and one where we don’t. Fairly standard.

write-output-gets-foreground-and-background-colors-and-more04

Now, the next example is where this starts to get fun: the incorporation of the -ForegroundColor and -BackgroundColor parameters. Prior to running these commands, and those in the last two examples, I removed the PSReadLine module so the colors would be easier to spot.

write-output-gets-foreground-and-background-colors-and-more05

Next is a demo of two additional parameters: -HorizontalPad and -VerticalPad. These will allow you to add spaces before and after the value supplied to the -InputObject parameter (the text), and add lines above and below the text. This is the part that gets back to the request in the Microsoft Technet forum post. I should mention that my solution in the forum post would be different now that I’ve spend some time writing this function — there are better ways to do things that I hadn’t considered at the time I replied to the post.

write-output-gets-foreground-and-background-colors-and-more06

The next example shows some usage with the Get-ADUser cmdlet. Keep in mind that if you combine this function with other cmdlets, that the -HorizontalPad and -VerticalPad parameters cannot be used. In my mind, they’re just a bonus to the option of using colors with Write-Output. I should mention it now, but this function was intentionally written to only work in the ConsoleHost. Perhaps I’ll add the option of using it in the ISE in the future.

write-output-gets-foreground-and-background-colors-and-more07

I’ve written an additional function that will allow you to quickly see the available colors from which you can choose. It’s called Show-TMOutputColor, and produces the results below. This might come in handy as you’re using the modified Write-Output.

write-output-gets-foreground-and-background-colors-and-more08

Now, on to the module. It’s the first time I’ve done it, but I’ve uploaded this to the PowerShell Gallery. I believe publishing to the PowerShell Gallery was one of my PowerShell New Year’s Resolutions for 2016, so there’s one down! You can use this direct link or, one better, download the module from your console, using this command:

Install-Module -Name TMOutput

Thanks for reading this post. Hopefully these function will be a useful way to get around the need for Write-Host’s foreground and background color options while working in the console. That, and maybe the TechNet thread creator can do whatever they wanted, too.

Hashtag AWS Tweet Prompts Fix to AWSPowerShell Module

I started the Twitter Reply category so I would be able to reply to things I saw on Twitter that needed to be done outside of the 140-character limit. I’ve used it this way a few times, however, today’s usage is a little different. Although it incorporates Twitter and it’s going to take longer than 140 characters, I’m not actually replying to someone so much. Instead, I am bringing up an event that transpired on Twitter.

First, let me begin by saying how impressed I am with at least one of the developers—I’m guessing he’s a developer—working on the AWSPowerShell module. Last Wednesday, I tweeted that I had started and then stopped an EC2 instance using the Start-EC2Instance and Stop-EC2Instance cmdlets. I noticed a naming difference between identical parameters used by these two, complimentary cmdlets, as I mentioned in my Tweet.

Steve Roberts—a complete stranger to me—replies to my tweet as he must follow the #AWS hashtag. The part that sticks out is that he wrote, “…Fixing…,” as if he was going to correct this difference. Hilarious, right?

Cut to just over 24 hours later and it’s fixed. I downloaded the newest version of the module, installed it, and tested it. It was fixed. The complimentary cmdlets now use the same, InstanceId parameter.

Did that really just happen? For all I knew, this Steve guy was messing with me, but no, my Tweet really did initiate a (very minor) fix included in the newest version of the AWSPowerShell module. That’s a first. Well, I’ve found a new problem. Maybe I can get that one fixed, too. I’ll be writing about that in an upcoming post and will link it from here, as soon as it’s complete.

PSMonday #2: Monday, May 9, 2016

Topic: Best Way to Filter the Results of Your Commands

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

Today we’re going to discuss the best way to filter the results of our commands. These first two commands return the same results — the services on the local computer that begin with the letter L. Let’s discuss why one is better than the other, right after we take a look at the commands.

Get-Service | Where-Object Name -like L*

# And

Get-Service -Name L*

While piping the results of one command to another is beneficial in Windows PowerShell, it’s not always the best idea. If a cmdlet has a way to filter your results without the need to pipe it to a filtering cmdlet, such as Where-Object, then use it. The reason this is faster is because in the first above example we return ALL the services first and then filter them one by one. In the second example, we filter immediately, which means we never stop to consider services that start with any other letter than L. You might someday hear this called the filter left rule. The idea is to do as much filtering as close to the left of your commands as possible. You might also here it as filter left, format right. This is to say that formatting cmdlets (Format-Table, Format-List, etc.) should be as far to the right of a command(s) as possible (typically the last cmdlet in a command that includes piping).

Here’s an example from Active Directory (AD). In the first command we return EVERY AD user object and then filter them, whereas in the second command we filter immediately and only process the users we want to return. In the case of AD, it’s not only faster, but it’s going to be less resource intensive than peering at every AD user object in the entire domain, to only return what might be a handful of users.

Get-ADUser -Filter * | Where-Object Surname -eq 'Smith'

# And

Get-ADUser -Filter {Surname -eq 'Smith'}

I ran the two above commands through the Measure-Command cmdlet in order to measure their execution times. I did this around 10 p.m. Saturday evening using my last name. The first command, that checked every AD user object, took 6 1/2 minutes to complete. The second command took an average of 30 milliseconds. That’s a big difference.

Bonus: Here’s how to determine which cmdlets have a specific parameter. In this case, we’re checking for cmdlets that include a -Filter parameter. The below image only shows the first nine cmdlets of the sixty found on my computer that include this parameter. Remember, however, that in the case of the Get-Service cmdlet, that we didn’t filter with a -Filter parameter. In that instance, we used the -Name parameter, as it accepted wildcards. Use Get-Help to determine if a cmdlet’s parameters accept wildcards, or not.

Get-Command -ParameterName CommandType

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Add-Content                                        Microsoft.PowerShell.Management
Cmdlet          Clear-Content                                      Microsoft.PowerShell.Management
Cmdlet          Clear-Item                                         Microsoft.PowerShell.Management
Cmdlet          Clear-ItemProperty                                 Microsoft.PowerShell.Management
Cmdlet          Copy-Item                                          Microsoft.PowerShell.Management
Cmdlet          Copy-ItemProperty                                  Microsoft.PowerShell.Management
Cmdlet          Get-ADAuthenticationPolicy                         ActiveDirectory
Cmdlet          Get-ADAuthenticationPolicySilo                     ActiveDirectory
Cmdlet          Get-ADCentralAccessPolicy                          ActiveDirectory

That’s all. We’ll be back at it next Monday.

Multi-Level Menu System with a Back Option

It’s been said several times now, but while this site was designed to help people learn some Windows PowerShell, it’s also about posting things I’m going to want to find one day. Take for instance this menu system I started for someone on TechNet: https://social.technet.microsoft.com/Forums/en-US/30663446-4091-4a1c-9de0-407046ccc39f/powershell-script-with-submenus-how-to-go-back?forum=winserverpowershell.

It allows the user the ability to enter into menus and submenus with the option of backing out of them. You know, choose a number from the menu, or hit B to go back to the previous menu. Hopefully it’s helpful for the TechNet thread creator, and maybe it’s helpful for others someday, too. I’ve included both the code and an image of the code in action. Until next time.

Do {
@'

----------Software/Driver Installation----------
1. Toshiba 1
2. Acer 1
------------------------------------------------

'@

    $MainMenu = Read-Host -Prompt 'Enter 1 - 2 or Q to quit'
    Switch ($MainMenu) {
        1 {
            Do {
@'

---------Software/Driver Installation----------
1. Software
2. Drivers
------------------------------------------------

'@
                $1MainMenu = Read-Host -Prompt 'Enter 1 - 2 or B for Back'
                Switch ($1MainMenu) {
                    '1' {
                            Do {
@'

--------------Software Installation-------------
1. Package 1
2. Package 2
3. Package 3
------------------------------------------------

'@
                                $1InnerMenu = Read-Host -Prompt 'Enter 1 - 3 or B for Back'
                                Switch ($1InnerMenu) {
                                 '1' {Write-Output -InputObject '--> You chose to install package 1'; break}
                                 '2' {Write-Output -InputObject '--> You chose to install package 2'; break}
                                 '3' {Write-Output -InputObject '--> You chose to install package 3'}
                                }
                            } Until ($1InnerMenu -eq 'B')
                        }
                }
            } Until ($1MainMenu -eq 'B')
        }
    } # End Switch.
} Until (
    $MainMenu -eq 'Q'
)

multi-level-nested-menu-system-with-a-back-option01

PSMonday #1: Monday, May 2, 2016

Topic: Setting Multiple Variables at the Same Time

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

Welcome to the first PowerShell Monday.

Let’s get started: The first example shows one way to set, or assign, a value to a variable in Windows PowerShell. In this example, $p is assigned the string value ‘Welcome to Monday’.

$p = 'Welcome to Monday.'

Knowing that, what do you think this command accomplishes?

$a = $b = $c = 'string'

# Answer further below…








# Keep going…








# Keep going…








# Keep going…








After running that command, here are the values stored in $a, $b, and $c.

# The command was $a = $b = $c = 'string'.
$a
$b
$c

string
string
string

A command such as this, although not often seen, sets all three variables to the same value. Knowing this in an option, allows us to initialize a group of variables to the same value, at the same time. While you can do this in three separate commands, as the next two examples show, you can complete the same thing, as part of a single command.

# The line break is a command separator in PowerShell.
$x = 'string'
$y = 'string'
$z = 'string'
# The semicolon is a command separator, too.
$x = 'string'; $y = 'string'; $z = 'string'

Bonus: What do you think this command does?

$m,$n,$o = 'string'

You probably won’t ever see this command — I haven’t — but why not know what it does. In this example, only $m is assigned the value of ‘string’. The other two — $n and $o, in this example — lose any value they may have been storing. Again, I haven’t seen this, and I wouldn’t recommend you use it. I suspect that it would end up being confusing, compared to the traditional methods of assigning and removing values from variables. I said it was a bonus; I didn’t say it was a useful.

Second Bonus: What about this command; what do you think happens here?

$e,$f,$g = 'Monday','Tuesday','Wednesday'

I’ve never seen this command used either, but I can see how this one might be useful to use, without too much confusion. I still won’t recommend it, though. In this example, we end up assigning ‘Monday’ to $e, ‘Tuesday’ to $f, and ‘Wednesday’ to $g. If we had a fourth variable, such as $h, and it had a value in it, the value would be removed, such as we saw in the first bonus. This is of course, unless there was another value after ‘Wednesday’.

That’s it for the first PSMonday. Hope you’ve learned something new, and we’ll do this again next week.

PowerShell Monday Table of Contents

I’m trying something new at work dubbed PSMonday, as in—and, I hope you could’ve guessed this—PowerShell Monday. I’m writing up quick and easy-to-read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work are consistently learning something new about Windows PowerShell. This includes both my team, and our Operations Team (Edit: And, now our VMware Team, too. Edit2: Our Windows DBA is reading along, as well.).

Well, I thought about it over the weekend, and I can’t figure out why I wouldn’t also include the lesson right here. If you’re into PowerShell too, then I would encourage you to do a PSMonday at your place of work, as well. Help people help themselves. These posts are sorted beginning with the oldest PSMonday at the top. Scroll to the bottom to locate and read the newest PowerShell Monday, and check out the ones in between.

PSMonday #1: Monday, May 2, 2016
Topic: Setting Multiple Variables at the Same Time

PSMonday #2: Monday, May 9, 2016
Topic: Best Way to Filter the Results of Your Commands

PSMonday #3: Monday, May 16, 2016
Topic: Quotes and Strings, and Their Rules

PSMonday #4: Monday, May 23, 2016
Topic: Add and Concatenate Numbers and Strings

PSMonday #5: Monday, May 30, 2016
Topic: Splatting and Hash Tables

PSMonday #6: Monday, June 6, 2016
Topic: Splatting and Hash Tables Continued

PSMonday #7: Monday, June 13, 2016
Topic: Test-NetConnection

PSMonday #8: Monday, June 20, 2016
Topic: Less Used Variable Properties

PSMonday #9: Monday, June 27, 2016
Topic: Less Used Variable Properties Continued I

PSMonday #10: Monday, July 4, 2016
Topic: Less Used Variable Properties Continued II

PSMonday #11: Monday, July 11, 2016
Topic: PowerShell Remoting

PSMonday #12: Monday, July 18, 2016
Topic: PowerShell Remoting Continued

PSMonday #13: Monday, July 25, 2016
Topic: PowerShell Remoting Continued II

PSMonday #14: Monday, August 1, 2016
Topic: PowerShell Remoting Continued III

PSMonday #15: Monday, August 8, 2016
Topic: PowerShell Remoting Continued IV

PSMonday #16: Monday, August 15, 2016
Topic: PowerShell Remoting Continued V

PSMonday #17: Monday, August 22, 2016
Topic: PowerShell Remoting Continued VI

PSMonday #18: Monday, August 29, 2016
Topic: Get-Member

PSMonday #19: Monday, September 5, 2016
Topic: Get-Member Continued

PSMonday #20: Monday, September 12, 2016
Topic: Get-Member Continued II

PSMonday #21: Monday, September 19, 2016
Topic: Get-Member Continued III (and Arrays)

PSMonday #22: Monday, September 26, 2016
Topic: Get-Member Continued IV

PSMonday #23: October 3, 2016
Topic: Commands, Cmdlets, and Functions

PSMonday #24: October 10, 2016
Topic: $PSDefaultParameterValues

PSMonday #25: October 17, 2016
Topic: $PSDefaultParameterValues II

PSMonday #26: October 24, 2016
Topic: PowerShell’s Importance

PSMonday #27: October 31, 2016
Topic: Introduction to the Language Constructs

PSMonday #28: November 7, 2016
Topic: If, If-Else, If-ElseIf

PSMonday #29: November 14, 2016
Topic: If, If-Else, If-Else II

PSMonday #30: November 21, 2016
Topic: If, If-Else, If-Else III

PSMonday #31: November 28, 2016
Topic: Switch Statement

PSMonday #32: December 5, 2016
Topic: Switch Statement II

PSMonday #33: December 12, 2016
Topic: Switch Statement III

PSMonday #34: December 19, 2016
Topic: Switch Statement IV

PSMonday #35: December 26, 2016
Topic: Foreach

PSMonday #36: January 2, 2017
Topic: Foreach II

PSMonday #37: January 9, 2017
Topic: ForEach-Object

PSMonday #38: January 16, 2017
Topic: ForEach-Object II

PSMonday #39: January 23, 2017
Topic: For

PSMonday #40: January 30, 2017
Topic: Do-While

PSMonday #41: February 6, 2017
Topic: Do-Until

PSMonday #42: February 13, 2017
Topic: While

PSMonday #43: February 20, 2017
Topic: Reusable Code I

PSMonday #44: February 27, 2017
Topic: Reusable Code II

PSMonday #45: March 6, 2017
Topic: Reusable Code III

PSMonday #46: March 13, 2017
Topic: Reusable Code IV

PSMonday #47: March 20, 2017
Topic: Reusable Code V

PSMonday #48: March 27, 2017
The (Online) End of PowerShell Monday

PowerShell Nested Switch Statements

There was a Reddit post recently that gave me an opportunity to try something I’m not sure I’ve tried before: nested switch statements—-switch statements inside switch statements. So we’re on the same page, I’ve written an example of a basic, non-nested switch. It provides some of the same functionality as an If-ElseIf. I’ll typically use a switch statement instead of an If-ElseIf when there’s three or more outcomes. It’s easier to read, and it looks cleaner.

In the example below, a specific phrase will be written if the value in the $Fruit variable is apple, banana, or pear. If it’s not one of those, it’ll take the default action. In the case below, the default action indicates that an apple, banana, or pear wasn’t chosen. Switch statements will sometimes be called case statements in other languages.

$Fruit = 'banana'
Switch ($Fruit) {
    'apple' {'You chose apple.'; break}
    'banana' {'You chose banana.'; break}
    'pear' {'You chose pear.'; break}
    default {'You didn''t choose an apple, banana, or pear.'}
}

As a part of my nested switch statement testing, I wrote this example using automobile makes and models. The outermost switch statement makes a determination of which nested switch statement to execute.

$Make = 'Ford'
$Model = 'Escape'
 
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {Write-Output -InputObject 'You selected the Ford Mustang.'; break}
            'Escape' {Write-Output -InputObject 'You selected the Ford Escape.'; break}
            default {Write-Output -InputObject "You selected Ford, however, the $Model isn't a valid model for this make."; break}
        }
    }
    'Chevy' {
        Switch ($Model) {
            'Corvette' {Write-Output -InputObject 'You selected the Chevy Corvette.'; break}
            'Camaro' {Write-Output -InputObject 'You selected the Chevy Camaro.'; break}
            default {Write-Output -InputObject "You selected Chevy, however, the $Model isn't a valid model for this make."; break}
        }
    }
    'Dodge' {
        Switch ($Model) {
            'Charger' {Write-Output -InputObject 'You selected the Dodge Charger.'; break}
            'Viper' {Write-Output -InputObject 'You selected the Dodge Viper.'; break}
            default {Write-Output -InputObject "You selected Dodge, however, the $Model isn't a valid model for this make."; break}
        }
    }
}

Should we take this further—-a switch statement inside a switch statement inside a switch statement? I’m not going to lie, it started to get confusing and so my first recommendation would be to try and steer clear of the multi-nesting this deep (3 levels). I’ve removed some of the example above—-Chevy and Dodge—-in my example below and focused on the Ford (pun, absolutely intended). To help, I’ve included comments on the closing brackets. This multi-nested switch statement will return different values based on the make, the model, and the year, too.

$Make = 'Ford'
$Model = 'Escape'
$Year = '2016'
  
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Mustang."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Mustang"; break}
                    default {Write-Output -InputObject "You selected the Ford Mustang with a non-matching year."; break}
                } # End Mustang Year Switch.
            } # End Mustang Switch.
            'Escape' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Escape."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Escape."; break}
                    default {Write-Output -InputObject "You selected the Ford Escape with a non-matching year."; break}
                } # End Escape Year Switch.
            } # End Escape Switch.
            default {Write-Output -InputObject "You selected a Ford; however, the $Model isn't a valid model for this make."}
        } # End Ford Model Switch.
    } # End Ford Switch.
    default {Write-Output -InputObject 'Sorry, we''re only dealing with Fords (at the moment).'}
} # End Make Switch.

So I can track it down later, here’s the post I read on Reddit that influenced this post. It includes the link to my possible, partial solution; however, that direct link is here: http://pastebin.com/KRxRb1VM.

Update: It has occurred to me that it might be easier to follow this thrice nested switch without all the comments. I’ve removed those below.

$Make = 'Ford'
$Model = 'Escape'
$Year = '2016'
  
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Mustang."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Mustang"; break}
                    default {Write-Output -InputObject "You selected the Ford Mustang with a non-matching year."; break}
                }
            }
            'Escape' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Escape."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Escape."; break}
                    default {Write-Output -InputObject "You selected the Ford Escape with a non-matching year."; break}
                }
            }
            default {Write-Output -InputObject "You selected a Ford; however, the $Model isn't a valid model for this make."}
        }
    }
    default {Write-Output -InputObject 'Sorry, we''re only dealing with Fords (at the moment).'}
}

Linux Prompt on Windows – Part I

I already have an answer to the question, “Why?” Because, I can. At some point in the last few days, I decided I would attempt to replicate the Linux prompt in PowerShell. So I did it, and I thought I would share it. I’ve broken this up a little with the full function toward the bottom of this evening’s post.

In this first section, we’ve created the prompt function’s basic structure and added our first bit of code. This code determines if the user that started the Windows PowerShell session is an administrator or not. If they are, they get the # symbol as part of the prompt, and if they’re not, they get the $ symbol.

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

The next addition includes the logic to determine how to write the path inside the prompt. If the current path is C:\Users\<CurrentUser>, it enters a tilde (~). If the path includes the C:\Users\<CurrentUser> path, in addition to one or more directories, it will enter a tilde and the other directory or directories, such as ~/Desktop, or ~/Favorites/Links. In the last case, it’ll simply list the current path after replacing the backslashes with forward slashes and dumping the drive letter and colon. In all instances, in fact, all (Windows) backslashes, will be replaced with (Unix/Linux) forward slashes.

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

    If ($PWD.Path -eq $env:USERPROFILE) {
        $Location = '~'
    } ElseIf ($PWD.Path -like "*$env:USERPROFILE*") {
        $Location = $PWD.Path -replace ($env:USERPROFILE -replace '\\','\\'),'~' -replace '\\','/'
    } Else {
        $Location = "$(($PWD.Path -replace '\\','/' -split ':')[-1])"
    }
}

The last section is where the prompt function actually writes the prompt to the screen. It’s the simple combination of the current user, an @ character, the computer name, the path, and the proper symbol (# or $). As you may notice, I’ve written the code to force the case of the current user and computer to lowercase.

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

    If ($PWD.Path -eq $env:USERPROFILE) {
        $Location = '~'
    } ElseIf ($PWD.Path -like "*$env:USERPROFILE*") {
        $Location = $PWD.Path -replace ($env:USERPROFILE -replace '\\','\\'),'~' -replace '\\','/'
    } Else {
        $Location = "$(($PWD.Path -replace '\\','/' -split ':')[-1])"
    }

    "$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Location $Symbol "
}

In closing, I’ve included a gif of the prompt and an example of moving though the directory structure. While I typically only use full cmdlet names in my posts, this example does include aliases for the Set-Location cmdlet (cd, sl, and even chdir). Due to this function being saved in my profile script, this prompt displays immediately when a new console is opened, and every time after that. As someone that’s written a few different prompt functions, this one’s a keeper. I already can’t imaging switching back. On that note, you might not want to test it out.

duplicate-the-linux-prompt01

tommymaynard@srv01 / # $PWD.Path
C:\
tommymaynard@srv01 / # cd C:\Windows\
tommymaynard@srv01 /Windows # cd .\System32\
tommymaynard@srv01 /Windows/System32 # chdir ..
tommymaynard@srv01 /Windows # chdir ..
tommymaynard@srv01 / # sl /users
tommymaynard@srv01 /users # sl /users/tommymaynard2 # Not my current user.
tommymaynard@srv01 /users/tommymaynard2 # cd ..
tommymaynard@srv01 /users # cd C:\Users\tommymaynard\ # My current user.
tommymaynard@srv01 ~ # cd .\Desktop\
tommymaynard@srv01 ~/Desktop # cd ..
tommymaynard@srv01 ~ # cd .\Favorites\Links\
tommymaynard@srv01 ~/Favorites/Links # sl /
tommymaynard@srv01 / # cd /users
tommymaynard@srv01 /users # chdir \
tommymaynard@srv01 / #

Update1: A day later and I still love my new PowerShell prompt. I’ve made a minor change, however. The last line in the function is now the following three lines. It stores the prompt in a variable called $Prompt, modifies the title of the window to the $Prompt value, and then writes the prompt. In the past my window’s title indicate the computer name. Now it indicates that information, as well as the current user, the location on the current drive, and whether or not I’m running the session as an administrator.

...
	$Prompt = "$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Location $Symbol "
	$Host.UI.RawUI.WindowTitle = $Prompt
	$Prompt
}

Update2: It occurred to me today that by entering the tilde character, such as Set-Location -Path ~, it moves you to the location stored in the Home property of the FileSystem PSProvider. Because of this, I opted to add another line, just before the beginning of the prompt function. Setting this property to $env:USERPROFILE, allows me to quickly move to C:\Users\<CurrentUser>. I should note that this property is sometimes already set. In those cases, this is just a precaution that the ~ character will move you back home.

(Get-PSProvider -PSProvider FileSystem).Home = $env:USERPROFILE
Function Prompt {
...
}

In case you want to take this prompt function for a spin, and you do, here’s everything. Copy and paste it to your console and hit Enter a couple times and try it out. If you hate it, close and reopen the PowerShell console and it never existed. Cheers.

(Get-PSProvider -PSProvider FileSystem).Home = $env:USERPROFILE
Function Prompt {
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'
    } Else {
        $Symbol = '$'
    }
 
    If ($PWD.Path -eq $env:USERPROFILE) {
        $Location = '~'
    } ElseIf ($PWD.Path -like "*$env:USERPROFILE*") {
        $Location = $PWD.Path -replace ($env:USERPROFILE -replace '\\','\\'),'~' -replace '\\','/'
    } Else {
        $Location = "$(($PWD.Path -replace '\\','/' -split ':')[-1])"
    }

	$Prompt = "$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Location $Symbol "
	$Host.UI.RawUI.WindowTitle = $Prompt
	$Prompt
}