PSMonday #32: December 5, 2016

Topic: Switch Statement II

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.

Okay, we’re back with more information on the Switch language construct. We’ll start where we left off: getting the Switch to act more like the If statement. In our first example we’ll do just that, and we do it with the break statement.

Switch (3) {
    1 {'One'}
    2 {'Two'}
    3 {'Three'; break}
    4 {'Four'}
    3 {'Three (again)'}
}

Three

With the inclusion of the break statement, we exit the Switch construct the moment our condition is matched and action is completed.

Typically, I’ll include several break statements in my Switch statements. This is because once my condition is matched and the action completed, I’m typically ready to move past my Switch. In the below example, we’ll exit the Switch the moment a match is made and the action complete.

Switch (3) {
    1 {'One'; break}
    2 {'Two'; break}
    3 {'Three'; break}
    4 {'Four'}
}

Notice that I didn’t use a break statement on the last condition. It doesn’t make sense to include it. If we make it down to the last possible condition, I can be certain it’s not going to check anymore conditions, as there aren’t any more to check.

The switch statement can check collections, too. In this example, we evaluate the test-value of 4, and then the test-value of 2.

Switch (4,2) {
    1 {'One'}
    2 {'Two'}
    3 {'Three'}
    4 {'Four'}
    5 {'Five'}
}

Four
Two

It’s probably a good time to indicate that the break statement can break things, as well. It’s not always a good idea that it’s used. In this next example, 2 is never evaluated because we exit the Switch the moment the action for 4 is complete.

Switch (4,2) {
    1 {'One'; break}
    2 {'Two'; break}
    3 {'Three'; break}
    4 {'Four'; break}
    5 {'Five'}
}

Four

That’s it for today. We have at least one more PSMonday dedicated to the Switch. Maybe two. Okay, probably two.

Function Logging via Write-Verbose

It wasn’t but a couple weeks ago that I wrote a PowerShell function at work. That’s nothing new — I write them all the time — but I really liked this function. It was a rewrite on something that I rewrote three years ago. Yes, you read that correctly. Back then I was asked to add error checking and logging, which makes this post all that much more interesting.

So, I’m that guy that wants all my functions to produce objects. I don’t want anything different from my functions, your functions, or any I find anywhere else, either. The script I rewrote as a function, does just that, as they all do. It writes objects to the screen, that you can pipe to an output function, or cmdlet, such as Export-Csv. There’s no need to wrap something like Export-Csv inside a function. In fact, please don’t (unless you have a really solid reason for it, such as making things extremely easy for your not-as-skilled users). Instead, just create objects, and let them be piped. Let the user decided what to do with the objects your functions return.

At some point, it dawns on me: I don’t need my function to do logging now; these objects, whether in a CSV or not, tell me what happened. See, I’m actually collecting errors, and making those an object property, too (when they appear, and they haven’t yet). As of late, I’ve been adding a Notes property to my objects in which to store these errors.

I went along with this thought for a day, or so. I’ll never have to log again, right? I mentioned this to my coworker, and he mildly bursts my bubble, “I like to know what my function is doing.” He was right. Just because I was creating thorough objects didn’t guarantee my objects would completely point to a failure. I still needed a back end file that might aid in troubleshooting the function someday. Ugh, the cheer was gone.

I mentioned Write-Verbose to my coworker, and that’s why we’re here today. I’m not going to introduce you to Write-Verbose, as much as I’m going to show you what I’ve decided to do going forward (for now). My functions will still create objects, that I can export if I want to, but now I’m going to optionally log my Write-Verbose statements. They’re already there, why should I add logging, when I can instead choose to push those verbose statements out to a file? Sounds easy, right? No, it was a pain. Unless of course I’ve overlooked something simple, as that’s always a possibility.

In PowerShell, we have streams and redirection operators. Like, take my verbose stream and send it to a file. Nothing I tried, and it’s possible I missed something, allowed me to write the Verbose stream to the host and to my file, at the same time, until I wrote this function. I’m just going to dump this test of a function here, and walk you through it, in order to show you how I got this to work for me. This is it for me: my functions will continue to create objects and my Write-Verbose statements are going to be available for logging.

If you want to download this function inside of a .ps1 file, click below. I don’t typically make my functions available this way; however, it may be easier to follow if you have your own copy. The download includes both the 1.0 and 1.1 version. Follow along in the 1.1 version.

Function Logging via Write-Verbose Examples (28 downloads)

 

Function Write-VerboseToLogExample {
# Version: 1.1
# 1.0 : Basic example of Write-Verbose to Log file. It doesn't create objects, too, as I was getting this part working.
# 1.1 : Added objects, too, using Get-Service.
    [CmdletBinding()]
    Param (
    )

    DynamicParam {
        # Create dynamic, Log parameter.
        If ($PSBoundParameters.Verbose) {
            $SingleAttribute = New-Object System.Management.Automation.ParameterAttribute
            $SingleAttribute.Position = 1

            $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $AttributeCollection.Add($SingleAttribute)

            $LogParameter = New-Object System.Management.Automation.RuntimeDefinedParameter('Log',[switch],$AttributeCollection)
            $LogParameter.Value = $true
 
            $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
            $ParamDictionary.Add('Log',$LogParameter)
            return $ParamDictionary
        } # End If.
    } # End DynamicParam.

    Begin {
        If ($PSBoundParameters.Verbose -and $PSBoundParameters.Log) {
            $Path = "$(Get-Date -Format 'DyyyyMMddThhmmsstt').txt"
            Function Write-Verbose {
                Param ($Message)
                Microsoft.PowerShell.Utility\Write-Verbose -Message "[$(Get-Date)]: $Message"
                Microsoft.PowerShell.Utility\Write-Verbose -Message "[$(Get-Date)]: $Message" 4>> $Path
            }
        }
        Write-Verbose -Message 'Entering the Begin block.'
    } # End Begin.

    Process {
        Write-Verbose -Message 'Entering the Process block.'
        $Services = Get-Service | Select-Object -First 3
        Foreach ($Service in $Services) {
            Write-Verbose -Message "Starting loop/object creation ($($Service.Name))."

            [PSCustomObject]@{
                Status = $Service.Status
                Name = $Service.Name
                DisplayName = $Service.DisplayName 
            }
        }
    } # End Process.

    End {
        Write-Verbose -Message 'Entering the End block.'
    } # End End.
} # End Function: Write-VerboseToLogExample.

# Write-VerboseToLogExample

# Write-VerboseToLogExample -Verbose

# Write-VerboseToLogExample -Log

# Write-VerboseToLogExample -Verbose -Log

Line 1: Declare (or create) the Write-VerboseToLogExample advanced function.

Line 2 – 4: Function info. Help wasn’t included, as this is a Proof of Concept function.

Line 5 – 7: CmdletBinding attribute and Parameter attribute.

Line 9 – 25: Dynamic, Log parameter. This parameter is only available, if the Verbose parameter is included when the function is invoked.

Line 27 – 37: Begin block: This block includes an If statement that results in True if both the Verbose and Log parameters are included when the function is invoked. Providing both are True, we create a $Path variable that holds the name of a text file that’s based on the current date and time. Next, we write a nested function to memory called Write-Verbose. This means that if Write-Verbose is used later in the function, it’ll use our newly defined function instead of the builtin Write-Verbose cmdlet. The function contains two calls to the original Write-Verbose cmdlet, by using its full path. One command writes the Write-Verbose message to the host and the other redirects to a file. This is the entire “magic” behind writing to both the host and a file at the same time. At the end of this Begin block we have a single Write-Verbose statement, that again, will use the new Write-Verbose function, if both parameters were included.

Line 39 – 51: Process block: This block has a opening Write-Verbose statement. Following this, we return the first three services using Get-Service. Then we use the results to create our own custom object from the results. This is simply for demonstration purposes. It’s proof we’re creating objects and writing our Write-Verbose message to two places.

Line 53 – 55: End block: This block has a single Write-Verbose command.

Line 56: Close function declaration from line 1.

The next several lines run the function in one of four ways. The first option runs the function in a standard fashion (no parameters). The second option includes the Verbose parameter which indicates that verbose statements will be written to the host. The third option include a Log parameter without the Verbose parameter. This fails, as the Log parameter is dynamic: It doesn’t exist unless the Verbose parameter is included. The final option includes both the Verbose and Log parameters. Because of our custom, nested function it’ll write verbose messages in our host and in a log file at nearly the same time.

Simple, yet powerful. Here’s the results of those four different function invocations.

PS > Write-VerboseToLogExample

 Status Name                      DisplayName                      
 ------ ----                      -----------                      
Running AdobeARMservice           Adobe Acrobat Update Service     
Stopped AdobeFlashPlayerUpdateSvc Adobe Flash Player Update Service
Stopped AeLookupSvc               Application Experience

PS > Write-VerboseToLogExample -Verbose

VERBOSE: Entering the Begin block.
VERBOSE: Entering the Process block.
VERBOSE: Starting loop/object creation (AdobeARMservice).

VERBOSE: Starting loop/object creation (AdobeFlashPlayerUpdateSvc).
VERBOSE: Starting loop/object creation (AeLookupSvc).
VERBOSE: Entering the End block.
 Status Name                      DisplayName                      
 ------ ----                      -----------                      
Running AdobeARMservice           Adobe Acrobat Update Service     
Stopped AdobeFlashPlayerUpdateSvc Adobe Flash Player Update Service
Stopped AeLookupSvc               Application Experience 

PS > Write-VerboseToLogExample -Log
Write-VerboseToLogExample : A parameter cannot be found that matches parameter name 'Log'.
At line:1 char:27
+ Write-VerboseToLogExample -Log
+                           ~~~~
    + CategoryInfo          : InvalidArgument: (:) [Write-VerboseToLogExample], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Write-VerboseToLogExample

PS > Write-VerboseToLogExample -Verbose -Log
VERBOSE: [11/23/2016 23:07:30]: Entering the Begin block.
VERBOSE: [11/23/2016 23:07:30]: Entering the Process block.
VERBOSE: [11/23/2016 23:07:30]: Starting loop/object creation (AdobeARMservice).

VERBOSE: [11/23/2016 23:07:30]: Starting loop/object creation (AdobeFlashPlayerUpdateSvc).
VERBOSE: [11/23/2016 23:07:30]: Starting loop/object creation (AeLookupSvc).
VERBOSE: [11/23/2016 23:07:30]: Entering the End block.
 Status Name                      DisplayName                      
 ------ ----                      -----------                      
Running AdobeARMservice           Adobe Acrobat Update Service     
Stopped AdobeFlashPlayerUpdateSvc Adobe Flash Player Update Service
Stopped AeLookupSvc               Application Experience

This final example also creates our log file in my current directory.

function-logging-via-write-verbose01

PSMonday #31: November 28, 2016

Topic: Switch Statement

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.

The Switch language construct, also called the case statement in other languages, is a close relative to the If statement and its variations. As you work with If, be sure to consider Switch, as it can assist in a cleaner layout, and therefore, more easily read code. As mentioned previously, if your If statements are going past three levels, consider the Switch.

To start, here’s the basic, conceptual format. We consider a test-value in the parenthesis, determine the matching condition on the left, and then execute its corresponding action on the right.

Switch (<test-value>) {
    <condition> {<action>}
    <condition> {<action>}
    <condition> {<action>}
}

To begin, here’s a very basic example. We evaluate the test-value — it’s just a numeric 3 — and then find the matching condition. Once found (the 3 in our list of conditions), we’ll take the corresponding action, and write the word “Three” to the screen. Simple.

Switch (3) {
    1 {'One'}
    2 {'Two'}
    3 {'Three'}
    4 {'Four'}
}

Three

Our test-value can also be an equation: 2 + 2 (= 4) and then down through the possible conditions, and take the appropriate action.

Switch (2 + 2) {
    1 {'One'}
    2 {'Two'}
    3 {'Three'}
    4 {'Four'}
}

Four

There’s something we need to understand about the Switch construct, and that is that it is designed to look at all the conditions, no matter if it’s found one already, or not. Take a look at this example for clarification.

Switch (3) {
    1 {'One'}
    2 {'Two'}
    3 {'Three'}
    4 {'Four'}
    3 {'Three (again).'}
    5 {'Five'}
}

Three
Three (again).

In the above example, we can easily see that although it found 3 in the list of conditions, it continued looking through the additional conditions, only to find another one that matched. What we should have done, is just included multiple commands inside the action, like the following two examples. Notice that in the first example, we use the semi-colon as a command separator, and in the second example, we use the line break (Enter).

Switch (3) {
    1 {'One'}
    2 {'Two'}
    3 {'Three'; 'Three (again, but not really).'}
    4 {'Four'}
}

Three
Three (again, but not really).

Switch (3) {
    1 {'One'}
    2 {'Two'}
    3 {'Three'
       'Three (again, but not really).'
    }
    4 {'Four'}
}

Three
Three (again, but not really).

Make sure you fully understand these examples before next week. We’ll start that PSMonday in an effort to show how to get our Switch statements to act a bit more like the If statement, to exit when our condition is matched, and action taken.

PSMonday #30: November 21, 2016

Topic: If, If-Else, If-ElseIf III

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.

I failed to mention something last week. While I wouldn’t recommend you do it, you can rewrite this:

If (-Not(<condition>)) {
    <action>
}

to be this:

If (!(<condition>)) {
    <action>
}

I’m not mentioning this because you should use ! over -Not (ever, honestly), but just in case you see it being used. As I believed we’ve mentioned before, we only use aliases in onetime use scenarios, such as when you’re entering commands in your ConsoleHost or the ISE and have no intention of saving what you’ve entered. The ! is not a true alias, but it’s close enough to consider it one. This is a PowerShell community best practice, and one I’ll continue to pound in, at every opportunity I have.

Okay, on with this week’s If wrap-up.

Let’s get away from these conceptual examples. In this first example, we’ll assign values to two variables: $y will hold the numeric value of 5, and $z will hold the numeric value of 3. When we enter the If statement, we check both values. If both — and that’s key — of their values match, we’ll echo the word “If.”

$y = 5; $z = 3

If ($y -eq 5 -and $z -eq 3) {
    Write-Output -InputObject 'If'
}

If

Had we used the -or comparison operator, it would’ve echoed “If,” as well. Or, means that only one of the conditions would have to result In True.

If ($y -eq 5 -or $z -eq 10) {
    Write-Output -InputObject 'If'
}

If

In the above example, only $y -eq 5 was True. As mentioned, since we used the -or operator, only one of those conditions needed to result in True for the condition to pass. The below example uses the -and comparison operator again; however, the end results is False, so the Else portion of the If-Else is executed.

$y = 5; $z = 3

If ($y -eq 5 -and $z -eq 2) {
    Write-Output -InputObject 'If'
} Else {
    Write-Output -InputObject 'Else'
}

Else

Pretty straightforward. Let’s modify the above examples and make our Else, and ElseIf. When this example executes, the If portion will be False ($z was not assigned a numeric value of 2), so the ElseIf condition will be checked. This will result in True, as $z was assigned a numeric value of 3.

$y = 5; $z = 3

If ($y -eq 5 -and $z -eq 2) {
    Write-Output -InputObject 'If'
} ElseIf ($y -eq 5 -and $z -eq 3) {
    Write-Output -InputObject 'ElseIf'
}

ElseIf

In our final example we’ll include two ElseIf conditions. Notice in this example, that once the first one results in True, we exit the construct. That final ElseIf, while it would have also resulted in True, isn’t even considered. While we won’t go into it for now, you can nest language constructs. This is to say, we could’ve added another If statement inside the action portion of the ElseIf, and tested for $z -eq 3.

$y = 5; $z = 3

If ($y -eq 5 -and $z -eq 2) {
    Write-Output -InputObject 'If'
} ElseIf ($y -eq 5) {
    Write-Output -InputObject 'ElseIf 1' # Exits If-ElseIf.
} ElseIf ($z -eq 3) {
    Write-Output -InputObject 'ElseIf 2'
}

ElseIf 1

We’re going to end here with the If language construct, and start fresh with the Switch statement next Monday. I encourage you to ask any questions you might have about the If statement, as it’s vital you have a good understanding of this introductory language command.

Keep in mind the examples could’ve been much different than just comparing numbers. We could have return services and taken action based on whether a service was located, or not, or running or stopped. We could have returned a specific Active Directory user and taken action based on whether we found the user, or not. The list goes on.

Get the AWS Noun Prefixes

One of the nice things about the Get-AWSPowerShellVersion cmdlet is the ListServiceVersionInfo switch parameter. It returns properties for the Service (as in the AWS Service offering name), the “Noun Prefix,” and “API Version.” Yes, there really are spaces in those last two property names; however, they’ve been fixed in my function. I had been hoping for an easier way to determine the prefixes used in the AWS cmdlets, and here we have it. I actually considered parsing cmdlet names myself, so a huge thanks to AWS, for making sure that wasn’t necessary.

It’s almost as though this should have been its own cmdlet though — a get the version cmdlet and a get the noun prefixes cmdlet. Therefore, I’ve wrapped this command and its parameter in a quick and easy to use function for my user base. Copy, paste, try it out; it’s all yours. Super simple.

Function Get-AWSNounPrefix {
<#
.SYNOPSIS
    The function returns the AWS PowerShell cmdlet noun prefixes, along with the corresponding AWS Service.

.DESCRIPTION
    The function returns the AWS PowerShell cmdlet noun prefixes, along with the corresponding AWS Service name. This function utilizes the AWSPowerShell module's Get-AWSPowerShellVersion function.

.EXAMPLE
    -------------------------- EXAMPLE 1 --------------------------
    PS > Get-AWSNounPrefix
    This examples returns all the noun prefixes and their corresponding AWS Service name.

.EXAMPLE
    -------------------------- EXAMPLE 2 --------------------------
    PS > Get-AWSNounPrefix | Where-Object NounPrefix -match 'cf'
    NounPrefix Service            APIVersion
    ---------- -------            ----------
    CF         Amazon CloudFront  2016-09-29
    CFG        AWS Config         2014-11-12
    CFN        AWS CloudFormation 2010-05-15

    This example uses the Where-Object cmdlet and the Match operator to filter the results by the NounPrefix.

.EXAMPLE
    -------------------------- EXAMPLE 3 --------------------------
    PS > Get-AWSNounPrefix | Where-Object Service -like '*formation*'
    NounPrefix Service            APIVersion
    ---------- -------            ----------
    CFN        AWS CloudFormation 2010-05-15

    This example uses the Where-Object cmdlet and the Like operator to filter the results by the service name.

.EXAMPLE
    -------------------------- EXAMPLE 4 --------------------------
    PS > Get-AWSNounPrefix | Where-Object -Property APIVersion -like '2016*' | Sort-Object -Property APIVersion -Descending
    NounPrefix Service                          APIVersion
    ---------- -------                          ----------
    SMS        Amazon Server Migration Service  2016-10-24
    BGT        AWS Budgets                      2016-10-20
    CF         Amazon CloudFront                2016-09-29
    EC2        Amazon Elastic Compute Cloud     2016-09-15
    SNOW       AWS Import/Export Snowball       2016-06-30
    CGIP       Amazon Cognito Identity Provider 2016-04-18
    INS        Amazon Inspector                 2016-02-16
    AAS        Application Auto Scaling         2016-02-06
    MM         AWS Marketplace Metering         2016-01-14
    DMS        AWS Database Migration Service   2016-01-01

    This example uses the Where-Object, and Sort-Object cmdlet, to find services updated in 2016 and sorts by the most recently added and updated.

.NOTES
    Name: Get-AWSNounPrefix
    Author: Tommy Maynard
    Comments: Current version of AWSPowerShell module at the first, last edit: 3.3.20.0.
    Last Edit: 11/18/2016
    Version 1.0
#>
    [CmdletBinding()]
    Param (
    )

    Begin {
        $AWSServices = Get-AWSPowerShellVersion -ListServiceVersionInfo | Sort-Object -Property 'Noun Prefix'
    } # End Begin.

    Process {
        Foreach ($AWSService in $AWSServices) {
            [PSCustomObject]@{
                NounPrefix = $AWSService.'Noun Prefix'
                Service = $AWSService.Service
                APIVersion = $AWSService.'API Version'
            }
        } # End Foreach.
    } # End Process.

    End {
    } # End End.
} # End Function: Get-AWSNounPrefix.

 

PSMonday #29: November 14, 2016

Topic: If, If-Else, If-ElseIf II

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.

Let’s start by discussing the fact that we can reverse the way an If statement works. Last week, we discussed that we take a specific action(s) when conditions result in true, such as in the below, conceptual example.

If (<condition>) {
    <action>
}

This can be changed for the times it’s necessary. In the next example, we’ll complete the action if the condition is false — if it’s not true. The Not logical operator negates whatever follows it. Only use the Not operator when it’s absolutely necessary. If it’s not necessary, then there’s no reason to make your conditionals any more difficult to think through.

If (-Not(<condition>)) {
    <action>
}

The below examples help show how the Not logical operator changes the condition, and therefore, the action. In the first below example, the If statement will output the word “If,” because $true evaluates to True.

If ($true) {'If'} Else {'Else'}
If

In the next example, we negate $true, making it $false, so “Else” will be written to the screen.

If (-Not($true)) {'If'} Else {'Else'}
Else

In the next example, it’ll write “If” again. You can actually walk backwards through the conditions, saying it aloud: “True, becomes False, becomes True. It’ll write If.” See if you can do that with the last two examples. While these may be helpful for learning, there likely won’t ever be a time you’re negating a Not logical operator.

If (-Not(-Not($true))) {'If'} Else {'Else'}
If

If (-Not(-Not(-Not($true)))) {'If'} Else {'Else'}

If (-Not(-Not(-Not(-Not(-Not(-Not($true))))))) {'If'} Else {'Else'}

In closing, we’ll take a look at a final example. On the first line in the below example, we assign the variable x a value of 9. When we only put $x inside the If statement’s condition, it only evaluates whether or not the variable x exists. It pays no attention to the value that’s been assign to the variable x. In the If statement following that one, we evaluate the value assigned to the variable x. If that value is equal to 9, and it is, we’ll write the word  “If” to the screen.

$x = 9

If ($x) {
    Write-Output -InputObject 'If'
}
If

If ($x -eq 9) {
    Write-Output -InputObject 'If'
}
If

We’ll close up the If topic next week. Please let me know if there are any questions on today’s PSMonday.

PSMonday #28: November 7, 2016

Topic: If, If-Else, If-ElseIf

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.

Time to start learning, or reviewing, those language constructs — those “language commands,” as is the term used in the If statement’s, about topic help file. This file can be read by using the below command.

Get-Help -Name about_If

The If statement is typically the introductory control structure that someone encounters when they begin with a programming, or scripting, language. It is also the one people likely think of first when they need to start checking conditions and taking appropriate actions.

Here’s the basic, PowerShell If structure. We check a condition, and if it’s true, then we take the included action, or actions.

If (<condition>) {
    <action>
}

There are several variations of the If statement: Here’s the If-Else. This is much like the If, except that if the condition results in false, it would complete action2 instead of exiting the construct and ultimately doing nothing. Had the condition been true, it would’ve completed action1, as we saw in the first example.

If (<condition>) {
    <action>
} Else {
    <action2>
}

Here’s the If-ElseIf. The way this works is if condition1 is false, it will try condition2. If that’s true, it’ll run action2, but if it’s false, it’ll exit and take no action.

If (<condition1>) {
     <action1>
} ElseIf (<condition2>) {
    <action2>
}

If-ElseIf-Else is just like the above example; however, if condition2 resulted in false, it would complete action3 as the default action.

If (<condition1>) {
    <action1>
} ElseIf (<condition2>) {
    <action2>
} Else {
    <action3>
}

There’s really no limit to how many ElseIfs you include. Just keep in mind that Else completes a default action, as there’s no condition to check, while ElseIf requires a condition be checked (and be true), before the listed action is completed.

On a final note, if you’re going past three levels in your If constructs, then you may want to use the Switch statement. We’ll get to that one once we’ve completely knocked out the If.

Copy the Last Command to the Clipboard

As a PowerShell blogger, I’ll often write a command in the PowerShell ConsoleHost and then, after it’s been executed, want to copy it out and paste it into a post. What this typically means is that I’ll press the up arrow to recall the last command — the one I want — and then selectively highlight the code I want and right-click to copy. Inevitably, I’ll end up copying my prompt and other unwanted lines, or parts of lines, and have to clean that up once it’s been pasted elsewhere.

Well, with a quick minute, those days are over. Type the function name Copy-LastCommand (once the function is in your PowerShell session), press Enter, and boom, the last command you entered is on the clipboard and ready to go elsewhere. Simple. Now all I do, is paste it where I want it and save myself some seconds. They add up.

Function Copy-LastCommand {
    (Get-History (Get-History | Select-Object -Last 1).Id).CommandLine | clip
}

Update: I made some changes to this little function and need to share those. Something I didn’t consider right away, was what happens if there isn’t a last command to copy? This is to say, what if the Copy-LastCommand is the first command entered into someone’s PowerShell session? Well, it won’t have anything to copy. I’ve added a fix for those times — a simple Write-Warning command. I also edited this to work using the newer, Set-Clipboard, cmdlet that was introduced in PowerShell 5.1 (I believe).

Function Copy-LastCommand {
    If (Get-History) {
        If ($PSVersionTable.PSVersion.ToString() -match '5.1') {
            Set-Clipboard -Value (Get-History (Get-History | Select-Object -Last 1).Id -ErrorAction SilentlyContinue).CommandLine
        } Else {
            (Get-History (Get-History | Select-Object -Last 1).Id).CommandLine | clip
        }
    } Else {
        Write-Warning -Message 'No command history to copy.'
    }
}

Bulk Remove Variables, Carefully

Ever have one of those situations where you need to remove, or perhaps reinitialize, a set of variables and have to deal with remembering them all? This doesn’t happen often, but there are times when I need to clear, or remove, variables between loop iterations. You either need to keep track of all your variable names, or — and I wouldn’t recommend this so much — do a Remove-Variable -Name *. While the help on PowerShell 5.1.14394.1000 (5.1 preview version), says that Remove-Variable doesn’t accept wildcards, it does, and I think it always has.

Let’s start with the below example. These few lines do the following: 1. Return the number of variables in the PowerShell session, 2. Add a new variable x, 3. Return the number of variables again, 4. Remove all the variables I can (some can’t be removed), 5. Return the number of variables again (again), and 6. See if I can return my variable x.

[tommymaynard@srv01 c/~]$ (Get-Variable).Count
51
[tommymaynard@srv01 c/~]$ New-Variable -Name x -Value 'string'
[tommymaynard@srv01 c/~]$ (Get-Variable).Count
52
[tommymaynard@srv01 c/~]$ Remove-Variable -Name * -ErrorAction SilentlyContinue
[tommymaynard@srv01 c/~]$ (Get-Variable).Count
29
[tommymaynard@srv01 c/~]$ Get-Variable -Name x
Get-Variable : Cannot find a variable with the name 'x'...

While we can do this to remove the variables we’ve created in the session, we end up removing variables that weren’t necessary to remove. We’re better than this.

In the next example, we’ll add a variable prefix “dog” to all of our variables. This will allow us to find all of the variables by prefix, and then delete just those. Not a bad idea really, but it wasn’t my first thought.

[tommymaynard@srv01 c/~]$ New-Variable -Name dog1 -Value 'string1'
[tommymaynard@srv01 c/~]$ New-Variable -Name dog2 -Value 'string2'
[tommymaynard@srv01 c/~]$ New-Variable -Name dog3 -Value 'string3'
[tommymaynard@srv01 c/~]$ Get-Variable -Name dog*

Name                           Value
----                           -----
dog1                           string1
dog2                           string2
dog3                           string3

[tommymaynard@srv01 c/~]$ Remove-Variable -Name dog*
[tommymaynard@srv01 c/~]$ Get-Variable -Name dog*
[tommymaynard@srv01 c/~]$ # No results, as they've been deleted.

Had I thought of the above option first, I might’ve just gone with that idea, but let me share what I was really thinking. It’s involves the description property. When we create our variables with New-Variable, we have the option to set more than just the Name and Value. Let’s take a look at the below example, and then discuss it.

[tommymaynard@srv01 c/~]$ New-Variable -Name a -Value 'stringA' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ $StartDate = Get-Date
[tommymaynard@srv01 c/~]$ New-Variable -Name b -Value 'stringB' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ New-Variable -Name c -Value 'stringC' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c

Name                           Value
----                           -----
a                              stringA
b                              stringB
c                              stringC

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Select-Object Name,Value,Description

Name Value   Description
---- -----   -----------
a    stringA 11/03/2016 20:52:41
b    stringB 11/03/2016 20:53:05
c    stringc 11/03/2016 20:53:20

Now, it’s important to remember that a variable’s description property is a string. This means that when we put the current date in the property, it was converted to a string. That means that Thursday, November 3, 2016 20:53:20 PM became this: 11/03/2016 20:53:20. What we’ll need to do is convert it back to a datetime before we compare. We do that in the below example by casting the description property as a datetime object. If it’s not clear yet, our end goal is to remove variables that were created after a specific point in time.

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {[datetime]$_.Description -gt $StartDate}

Name                           Value
----                           -----
b                              stringB
c                              stringC

We can also use the Get-Date cmdlet to do this, as well.

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {(Get-Date -Format $_.Description) -gt $StartDate}

Name                           Value
----                           -----
b                              stringB
c                              stringC

Since we can now isolate the variables we created after a specific time, we can go ahead and blow them away. Here goes:

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {(Get-Date -Format $_.Description) -gt $StartDate} | Remove-Variable
[tommymaynard@srv01 c/~]$ Get-Variable a,b,c

Name                           Value
----                           -----
a                              stringA
Get-Variable : Cannot find a variable with the name 'b'.
At line:1 char:1
+ Get-Variable a,b,c
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (b:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Get-Variable : Cannot find a variable with the name 'c'.
At line:1 char:1
+ Get-Variable a,b,c
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (c:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Well, that’s it for today. Now you can use description property of your variables to hold the time in which they were created. This will allow you to selective remove any of them based on a start time. I like it, although the whole prefix thing might be an easier option.

PSMonday #27: October 31, 2016

Topic: Introduction to the Language Constructs

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.

I’ve long had this feeling that people that learn and use PowerShell don’t really ever get  a fair opportunity, to learn the language constructs. Instead, they just learn them as necessary. In an effort to correct this, I believe we should spend some time in the coming weeks, covering each of them.

Well then, what’s a language construct, right? Well, it’s a control structure, of course. Okay, well then what’s that?

Within a language — PowerShell in our instance — we have to have a way to handle conditional situations, in order to take appropriate action(s). Language constructs are the If statement and its variations, it’s the switch statement, it’s the foreach loop and ForEach-Object, the for, the Do variations, and the While loop, too.

In the coming weeks, we’ll spend some time with each of the constructs, so that you’ve seen and used them all, or simply reviewed them, if you’ve used them before. Knowing which to use when, is a vital skill, and best left to second nature once you’ve had experience with them all. I’ll be sure to include some of the times you might choose one over the other, when you can do the same thing with more than just one of the constructs. We’ll start next Monday with If, If-Else, and If-ElseIf.