Category Archives: Quick Learn

Comfortably Save a Pester Test

I’ve spent some time with Pester this week. While exploring the OutputFile parameter, it quickly became clear that the best I seemed to be able to output was some form of XML, and honestly, that wasn’t good enough for me in my moment of discovery. While I intend to make myself a Pester expert in the coming year (2018), there’s somethings I don’t know 100% yet, and so I understand it’s possible that I sound like an idiot, as I may not know about x in regard to Pester.

While working with Invoke-Pester, I came across the -PassThru parameter. Its purpose in life, in relation to the Invoke-Pester command, is to create a PSCustomObject. Now that’s, something I can work with. The idea here is to invoke Pester against an AWS instance, export my object (to an XML standard I can deal with [thank you Export-Clixml]), and write it to S3. Then, I can download the file to a computer that’s not my newly configured AWS instance, and check for what tests passed and failed. This, without the need to RDP (Remote Desktop), to the instance, and visually and manually check to see what did and didn’t work from there. We’re getting closer and closer to RDP being a security incident.

My example is not going to include an actual Pester run, so instead we’ll jump directly to the Invoke-Pester command and what we’ll do after that command has executed. This first example is a fairly standard way of invoking Pester (with parameters) and creating an output file. Again, this isn’t the output I’m after.

PS > Invoke-Pester -Script @{Path = 'C:\WkDir\HostAcceptance.Tests.ps1'; Parameters = @{Project = 'trailking'; Environment = 'tst'}} -OutputFile 'C:\WkDir\PesterOutputXml.xml'

Instead we’re going to include some other parameters, to include PassThru, Show, and OutVariable. PassThru will provide us an object that contains all of the Pester results, Show with the None value will hide the Pester test results, and OutVariable will get that object (that again, contains all of the Pester results) into the PesterResults variable.

PS > Invoke-Pester -Script @{Path = 'C:\WkDir\HostAcceptance.Tests.ps1'; Parameters = @{Project = 'trailking'; Environment = 'tst'}} -PassThru -Show None -OutVariable PesterResults

I was mainly okay with the above command; however, it was still writing my object to the host program, and I wanted nothing to be displayed, at all. It’s too bad that option isn’t in there by default, but I’m okay with improvisation. Again, I know PowerShell better than Pester, so there is the possibility I just don’t know enough at the immediate moment to not have done this a potentially better way. Anyway, the below example removes all the output to the host, but still stuffs my results into the PesterResults variable.

PS > [System.Void](Invoke-Pester -Script @{Path = 'C:\WkDir\HostAcceptance.Tests.ps1'; Parameters = @{Project = 'trailking'; Environment = 'tst'}} -PassThru -Show None -OutVariable PesterResults)

Okay, now what? Everything I need and more is now in the $PesterResults variable. Next, we’ll export it into an XML format I (as in Import-Clixml), can deal with.

PS > $PesterResults | Export-Clixml -Path 'C:\WkDir\PesterExport.xml'

Now that it’s in a usable format, I’m going to read it back in. You can go ahead and pretend that I’ve moved my exported XML file from the computer on which it was created, and now I’m about to read it in on a different computer. This visualization is as though it was uploaded to AWS S3, and downloaded from S3 on a different device. We’ll say it’s on my Desktop.

$PesterResults = Import-Clixml -Path 'C:\Users\tommymaynard\Desktop\PesterStuff\PesterExport.xml'
Foreach ($Result in $PesterResults.TestResult) {
    [System.Array]$Object += [PSCustomObject]@{
        Describe = $Result.Describe.TrimEnd(':')
        Context = $Result.Context.TrimEnd(':')
        It = $Result.Name.TrimEnd(':')
        Should = $Result.Result
        Time = $Result.Time

# $Object | Format-Table -Autosize
# $Object | Export-Csv -Path 'C:\Users\tommymaynard\Desktop\PesterStuff\PesterExport.xml' -NoTypeInformation

In the above example, we do a few things. We import the file we created out in AWS into a variable on my local computer, and then begin to iterate through its contents. For each entry, we add other object to my Object variable. Each object will contain a Describe, Context, It, Should, and Time property which is all obtained from the TestResult property of my PesterResults variable.

The last three lines are a few ways to handle the output (2 and 3 are commented out): (1) display it in the host, (2) display it in the host in an easier to read format, and (3) write the objects to CSV. Neat, right. As I continue to better learn Pester… I might just be back here. I can’t be 100% that this is the best way to save off and deal with the results of the Pester tests, but we’ll see!

Parse Net.exe Accounts (for Pester)

In a recent Pester test, I needed to verify that three settings in net.exe accounts were properly set. These included Lockout threshold, Lockout duration (minutes), and Lockout observation window (minutes). Well, now that I have my answer, I thought I would document it here. Before I show you how I handled this task, based on varying versions of PowerShell, I’ll show you the default output that I needed to parse.

PS > net.exe accounts
Force user logoff how long after time expires?:       Never
Minimum password age (days):                          0
Maximum password age (days):                          42
Minimum password length:                              0
Length of password history maintained:                None
Lockout threshold:                                    3
Lockout duration (minutes):                           30
Lockout observation window (minutes):                 30
Computer role:                                        SERVER
The command completed successfully.

I needed the above output to be parsed, and when that was done, I only needed the values of the three previously mentioned Lockout settings to be displayed. The below code indicates that if PowerShell is a version greater than 4.0 the ConvertFrom-String cmdlet can be used. It’s not necessary, but it was good to practice using a cmdlet I hardly every use. If the PowerShell version isn’t greater than 4.0, we’ll use a temporary variable and do the parsing ourselves. In the end and regardless of version, we’ll get our results. I’m using [PSCustomObject], but I am confident this test will never run with a version of PowerShell less than that of 3.0. This is happening in AWS with a Server 2012 R2 AMI and as we know, 2012 R2 includes PowerShell 4.0 by default.

If ($PSVersionTable.PSVersion.Major -gt 4) {
    $AcctSettings = net.exe accounts | ForEach-Object {
        ConvertFrom-String -InputObject $_ -Delimiter ': +' -PropertyNames Setting,Value
} Else {
    $AcctSettings = net.exe accounts | ForEach-Object {
        $TempVar = $_ -split ': +'
        [PSCustomObject]@{Setting = $TempVar[0]; Value = $TempVar[1]}
($AcctSettings | Where-Object {$_.Setting -eq 'Lockout threshold'}).Value
($AcctSettings | Where-Object {$_.Setting -eq 'Lockout duration (minutes)'}).Value
($AcctSettings | Where-Object {$_.Setting -eq 'Lockout observation window (minutes)'}).Value

This task was being done for Pester, so while we’re here, let me show it to you inside the Pester It Block.

# Account lockout policies.
It 'Checking the account lockout threshold, duration, observation window settings:' {
    If ($PSVersionTable.PSVersion.Major -gt 4) {
        $AcctSettings = net.exe accounts | ForEach-Object {
            ConvertFrom-String -InputObject $_ -Delimiter ': +' -PropertyNames Setting,Value
    } Else {
        $AcctSettings = net.exe accounts | ForEach-Object {
            $TempVar = $_ -split ': +'
            [PSCustomObject]@{Setting = $TempVar[0]; Value = $TempVar[1]}
    ($AcctSettings | Where-Object {$_.Setting -eq 'Lockout threshold'}).Value | Should -Be 3
    ($AcctSettings | Where-Object {$_.Setting -eq 'Lockout duration (minutes)'}).Value | Should -Be 30
    ($AcctSettings | Where-Object {$_.Setting -eq 'Lockout observation window (minutes)'}).Value | Should -Be 30
} # End It.

That’s it! Now you can parse net.exe accounts, too!!

Parse Computer Name for Project Name

On some days… I just want full on project redo. It’s amazing how many decisions you’d make differently in those initial project meetings, once you’ve begun delivering results. Why, oh why, did we allow for hyphens in project names!?

Here’s my problem and how I fixed it. Let’s say we have four projects and their names are those listed below.


Now, let’s consider that we use these as our host names, or computer names; however, we append some information onto these four strings. For our terminal hosts, we’ll add -TH-01 and for our compute hosts, we add -CH-01. Therefore, we’d have two computer names for each project. For the TRAILKING and ARF-SOIL projects, we’d have the following four computers.


Now, let’s consider we need to parse these computer names later on to help determine the project name. Can you see the problem, because I didn’t initially. The little extra coding I had to do is why we’re here today. You know, someone might need it one day, too.

If I split the full computer name at the first hyphen, and assume index 0 is the project name, then the ARF-SOIL project, is only going to be the ARF project. That’s not going to work. Take a look at my one-off solution. I hate these, but sometimes, it’s just too late to fix a project problem. Hindsight, man.

$String1 = 'TRAILKING-TH-01'
$String2 = 'TRAILKING-CH-01'
$String3 = 'ARF-SOIL-TH-01'
$String4 = 'ARF-SOIL-CH-01'
$String5 = '-ARF-SOIL-CH-01'

$String = Get-Random -InputObject $String1,$String2,$String3,$String4,$String5
$TempArray = $String.Split('-')

If ($TempArray.Count -eq 3) {
    $Project = $TempArray[0]

} ElseIf ($TempArray.Count -eq 4) {
    $Project = "$($TempArray[0])-$($TempArray[1])"

} Else {
    Write-Warning -Message "Unable to properly parse computer name: $String."
    Write-Verbose -Message "$BlockLocation Unable to properly parse computer name: $String."


As we repeatedly run this code in the ISE, or Visual Code, it’ll properly parse our computer names. If the string is split at its hyphens, and we’re left with three parts (TRAILKING, CH, and 01), then we know the first part is the project name. If the string is split at its hyphens, and we’re left with four parts (ARF, SOIL, TH, and 01), then we know the first two parts, combined with a hyphen, is the project name.

That was it. Happy Thanksgiving!

Determine if AWS EC2 Instance is in Test or Prod Account

It recently became apparent that I need a way to determine if I’m on an AWS TST (Test) EC2 instance, or a PRD (production) EC2 instance. The reason this is necessary is so that I can include a function to upload a file, or a folder, to an S3 bucket and ensure the portion of the bucket name that includes the environment, is included, and is correct. Therefore, I needed a function to be able to determine where it was running: in TST or PRD. Had I known I would need this information earlier, I would’ve had the CloudFormation template write this information to the Windows Registry, or a flat file, so I could pick it up when needed. Had I considered an option such as this, I wouldn’t be wracking my brain now to try to determine a way to gather this information, when I didn’t leave it somewhere.

The computer names are the same in both environments (in both AWS accounts), so any comparison there doesn’t work. The TST servers and the PRD servers have the same name. After some thought, I came up with my three options:

1. Return the ARN from the meta data, and use that to determine whether I am on a TST or PRD EC2 instance, by extracting the AWS account number from the ARN and comparing it to two known values. This would allow me to determine which account I’m in: TST or PRD.

2. Look at the folders in C:\support\Logs. My functions, that are invoked inside the UserData section of a CloudFormation template, include logging that creates folders in this location named “TST” on a test EC2 instance, and named “PRD” on a production EC2 instance.

3. Read in the UserScript.ps1 file (the UserData section in the CloudFormation template), and compare the number of the ‘tst’ and ‘prd’ strings in the file’s contents. Based on my UserData section, if there are more ‘tst’ strings, I’m on a TST EC2 instance, and if there are more ‘prd’ strings, I’m on a PRD EC2 instance.

Seriously Tommy, leave yourself some information on the system somewhere already. You never know when you might need it.

I decided I would use two of three above options, providing myself a fallback if it were ever necessary. Who knows, I may not retire from my place of employment, and I’d like my code to continue to work as long as possible, even if I’m not around to fix it. I was worried about hard coding in those AWS account numbers, but less so, when I added a check against my UserScript.ps1 file (again, the UserData section of a CloudFormation document) for the count of ‘tst’ vs. ‘prd’ strings. Maybe it’ll never be used, but if needed, it’ll be there for me.

Here’s my first check in order to determine if my code is running on a TST, or PRD, EC2 instance. Feel free to look past my Write-Verbose statements. I used these for logging. So you have a touch of information, I’m creating the name of an S3 bucket, such as <projectname>-<environment>. Right now, we’re after the <environment> section, which I’m out to store in $Env. The <projectname> portion comes from parsing the EC2 instance’s assigned host name.

#region Determine S3 Bucket using ARN and Account Number.
Write-Verbose -Message "$BlockLocation Determing the S3 Bucket Name using the ARN and account number."
$WebRequestResults = (Invoke-WebRequest -Uri '' -Verbose:$false).Content
$InstanceProfileArn = (ConvertFrom-Json -InputObject $WebRequestResults).InstanceProfileArn
$AccountNumber = ($InstanceProfileArn.Split(':'))[4]
Write-Verbose -Message "$BlockLocation ARN: $InstanceProfileArn."
Write-Verbose -Message "$BlockLocation Account Number: $AccountNumber."`

If ($AccountNumber -eq '615613892375') {
    $Env = 'tst'
} ElseIf ($AccountNumber -eq '368125857028') {
    $Env = 'prd'
} Else {
    $Env = 'unknown'
    Write-Verbose -Message "$BlockLocation Unable to determine S3 Bucket Name using the ARN and account number."
} # End If.

The following region’s code is wrapped in an If statement that will only fire if the $Env variable is equal to the string “unknown”. Notice that in the above code, that $Env will be set to this value, if neither of the AWS account numbers match my hard coded values. No, those aren’t real AWS account numbers — well, not mine at least, and yes, they could’ve come in via function parameters.

#region If necessary, determine using 'tst' vs. 'prd' in UserScript.ps1.
If ($Env -eq 'unknown') {
    Write-Verbose -Message "$BlockLocation Determing S3 Bucket Name by comparing specific strings in the UserScript.ps1 file."
    $FileContent = Get-Content -Path "$env:ProgramFiles\Amazon\EC2ConfigService\Scripts\UserScript.ps1"
    $MatchesTst = Select-String -InputObject $FileContent -Pattern 'tst' -AllMatches
    $MatchesPrd = Select-String -InputObject $FileContent -Pattern 'prd' -AllMatches
    Write-Verbose -Message "$BlockLocation Comparing ."

    If ($MatchesTst.Matches.Count -gt $MatchesPrd.Matches.Count) {
        $Env = 'tst'
    } ElseIf ($MatchesTst.Matches.Count -lt $MatchesPrd.Matches.Count)  {
        $Env = 'prd'
    } Else {
        Write-Warning -Message "Unable to determine account number by comparing specific strings in the UserScript.ps1 file."
        Write-Verbose -Message "$BlockLocation Unable to determine account number by comparing specific strings in the UserScript.ps1 file."
} # End If.

If the above code fires, it will read in the contents of my UserScript.ps1 file. Again, this script file contains exactly what’s in the UserData section of my instance’s CloudFormation template. Once we have the file’s contents in a variable, we’ll scan it two times. On the first check, we’ll record the matches for the string ‘tst’. On the second check, we’ll record the matches for the string ‘prd’. The way I’ve written my UserData section, if there’s more ‘tst’ strings, I’m on a TST EC2 instance, and if there’s more ‘prd’ strings, I’m on a PRD EC2 instance. There’s a nested If statement that does this comparison and tells me which it is by assigning the proper value to that $Env variable.

So after all that, I think I’ll just try to predetermine what information might be needed in the future and drop that into a flat file, or put it into the registry… just in case it’s ever needed. This task was obnoxious, but worthy of sharing.

Potentially Avoid Logging Plain Text Passwords

A few weeks ago I spoke at the Arizona PowerShell Saturday event where I introduced the newest version of my Advanced Function template — the 2.0 version. You can check it out here: It’s headed toward 200 downloads — not bad. Well, there’s going to need to be a newer version sooner rather than later, and here’s why.

The Advanced Function template writes out a few informational lines at the top of its log files. If you didn’t know, a big part of my template is the logging it performs. These lines indicate what, when, who, and from where, a function was invoked. In addition, it also logs out every parameter name and every parameter value, or values, that are included, as well. This was all very helpful until I pulled a password out of AWS’ EC2 parameter store — it’s secure — and fed it to the function with the logging enabled. It was a parameter value to a parameter named password, and it ended up in the clear, in a text file.

I know. I know. The function should only accept secure strings, and it will, but until then, I took a few minutes to write something I’ll be adding to the 2.1 version of my Advanced Function template. You see, I can’t always assume other people will use secure strings. My update will recognize if a parameter name has the word password, and if it does, it will replace its value in the logging with asterisks.

More or less, let’s start with the code that failed me. For each key-value pair in the $PSBoundParameters hash table, we write the key and its corresponding value to the screen.

Alright, now that we have our function in memory, let’s invoke the function and check out the results.

In the above results, my password is included on the screen. That means it could end up inside of an on disk file. We can’t have that.

Now, here’s the updated code concept I’ll likely add to my Advanced Function template. In this example, if the key includes the word password, then we’re going to replace its value with asterisks. The results of this function are further below.

In these results, our password parameter value isn’t written in plain text. With that, I guess I’ll need to add this protection. By the way, if you didn’t notice, Password is underlined in green in the first and third image. This is neat; it’s actually PSScriptAnalyzer recognizing that I should use a SecureString based on the fact that the parameter is named Password. I can’t predict what everyone will do, so what’s a couple more lines to potentially protect someone from storing something secure, in an insecure manner.

An Advanced Function Template (2.0 Version)

Welcome. If you’re here for the download, it’s toward the bottom of this post.

Today’s post goes hand in hand with a session I gave at the Arizona PowerShell Saturday event on Saturday, October 14, 2017. I didn’t do it previously, but this year especially, it made sense to have a post at, as a part of my session at the event. I wanted a place to offer my advanced function template for download, and so this, is it. If you couldn’t attend the event and be a part of the session yourself, then this may be the next best alternative. Well, for my session anyway. This event included sessions from Jason Yoder, Will Anderson, and Jason Helmick. While we’re at it — naming names — many thanks to Thom Schumacher for his role in organizing this event.

Toward the end of 2016, I spent some nights and weekends, and moments in the office too, writing a PowerShell advanced function template. Its main purpose was to include built-in function logging. You see, I wanted logging, but I didn’t want an external logging function to do it, and so I decided that every one of my functions would use the same template, and therefore, I could offer consistent logging capabilities across all my functions. These include my own functions, and even those written and put in place for my coworkers. At last check, I’ve snuck 40 plus tools into production. These include PowerShell functions for Active Directory, Group Policy, Exchange, SharePoint, Office 365, Amazon Web Services, VMware, and general operating system and management needs. There’s always something to automate, and now, when they do get automated, each includes the same base functionality.

I liked it, I use it, and I even made my advanced function template available for download on its original post. After some use, I came to realize that it could’ve been better. If you’ve been at this scripting and automation game for awhile, then you understand that automation, even when it’s done, is never really done. There’s always room for improvement, even if there isn’t always time to execute that mental list of changes, fixes, and increased functionality you want to add to already written automation.

The first thing I needed, which I didn’t even know I needed at first, was a function, and I’m not talking about the template code. I’m talking about a way to demonstrate both advanced function template versions (1.0 and 2.0), using the same non-template code. At first, I thought I’d just walk though the code in my 2.0 version of my advanced function template, but really, it made sense to use an easy to understand previously written function as an example, running in both the 1.0 and 2.0 versions of my advanced function template. At nearly the same time I was prepping for PowerShell Saturday, I had written a function that created random passwords — I know, I know… there’s a bunch of these already. I took that function’s code and wrapped it in my 2.0 version, as it had already been written with my 1.0 version, for use in my session at the PowerShell Saturday event.

All the files I used, are included in the below, free from viruses, zip file. This includes the ArizonaPowerShellSaturday.ps1 file that I used to run all the various commands, the New-RandomPassword1.0.ps1 file (uses the 1.0 version of advanced function template), and New-RandomPassword2.0.ps1 file (uses the 2.0 version of advanced function template), and the blank AdvancedFunctionTemplate2.0.ps1 — this is the one you’re likely after. If you attempt to use the first file mentioned, ArizonaPowerShellSaturday.ps1, then you’ll need to modify the first region, where the variables are assigned, so that they point to the other three files, wherever you decided to save them. Also, there’s a couple references to an alias I use, called code. I don’t believe this is a built-in alias, so the line won’t work as expected on other people’s systems. Know that the idea behind those lines is to open the referenced file inside of Visual Studio Code.

ArizonaPowerShellSaturday2017AllFiles (256 downloads)

Update: I was asked at the PowerShell Saturday event, what kind of license I had. Ugh, none. But, for the sake of those that need it, let’s distribute this under the MIT License further below.

Update: The built-in ability to do logging that’s in my Advanced Function template writes all parameter names and associated values to the screen, file, or to both the screen and file. This means that if you’re passing secure data as a parameter value, that it needs to be done in a secure manner, or it’s going to appear in the logs. I intend to put in a stopgap for this, but it may not be perfect. You can read more here: Watch for a link on this post, and that one, to the newest post that’ll include the 2.1 versions!

Copyright 2017

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.


Add the ISE’s Ctrl + M to Visual Studio Code

As suspected, by me at least, the more I use Microsoft Visual Studio Code, the more I’m going to want to modify it. Remember, I just came into the light with the recent addition of Region support in version 1.17. This desire to modify is no more true than seen in an edit I made today to the keybindings.json file. This file allows one to override the default keyboard shortcuts in order to implement “advanced customizations.” It’s pretty awesome, and well appreciated.

Today I added the section for the Ctrl + M keyboard combination, such as can be seen in the last, or third, section of the below JSON. What’s this do, right? If we think back to the ISE (Microsoft’s Integrated Scripting Environment) you may remember that Ctrl + M collapsed all collapsible sections in the current script, or function. With this change in Visual Studio Code, I can now continue to use Ctrl + M to quickly collapse all the sections in my active function, or script. This is until I realize what the default action of Ctrl + M — Toggle Tab Key Moves Focus — actually does, and I find it necessary. I do want to mention, that I could’ve just went with the new keyboard combination of Ctrl + K Ctrl + 0. Ugh, no thanks for now.

    { "key": "ctrl+`",      "command": "workbench.action.terminal.focus",
                               "when": "!terminalFocus"},
    { "key": "ctrl+`",      "command": "workbench.action.focusActiveEditorGroup",
                               "when": "terminalFocus"},
    { "key": "ctrl+m",      "command": "editor.foldAll",
                               "when": "editorTextFocus"} 

While we’re here, I might as well mention (a.k.a. help myself remember), what the first two sections in my keybindings.json file do, as well. These allow me to use Ctrl + ` to switch focus between the editor on top, where I write my code, and the terminal below, where I can run PowerShell commands interactively. While that’s all for this post, I won’t be surprised if I’m back here updating it with new additions I make to my keybindings.json file.

As a newbie to Visual Studio Code for PowerShell development, I already don’t like even seeing the ISE. It’s pretty amazing what Region support in Visual Studio Code did to me.

Visual Studio Code Regions

It’s happened.

Microsoft has figured out how to get regions to work in Visual Studio Code. I thought it, and I may have even said it too, but it’s been my holdout for not using Microsoft’s code editor for PowerShell. While I’ve been using Visual Studio Code for my AWS YAML creation without regions, I hadn’t been ready to give up the ISE (Integrated Scripting Environment). Well, as of today, those days are over.

So what’s a region, right? It’s an easy way to collapse a section of code that isn’t collapsible by default. I greatly suspect I first learned about them from Ed Wilson — the original scripting guy. Here’s a couple examples from my old friend, the ISE. In the first example you can see the region sections aren’t collapsed and therefore display the commands. In the second image, you can’t see the commands at all. This becomes quite helpful when the region is loaded full with code and commands that simply don’t always need to be seen.

Here’s the same examples in Visual Studio Code.

What a glorious day! After being all set to use the ISE for the upcoming Arizona PowerShell Saturday event (2017), due to a lack of region support in VS Code, I’m glad to report that I’m going to go ahead and use it for my session. I didn’t see that one coming!

Finally, here’s an example of nested regions. I often do this as well, and these seem to work as expected.

Three Ways to Set $PSDefaultParameterValues Part II

In part one of this post, I indicated three different ways to populate the $PSDefaultParameterValues preference variable. The $PSDefaultParameterValues variable allow one to assign default values to the parameters of cmdlets and functions without ever needing to type them when they invoke the cmdlet or function. Here’s the two sets of examples from that post.


$PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true}

$PSDefaultParameterValues['Get-Help:ShowWindow'] = $true


$PSDefaultParameterValues = @{'Out-Default:OutVariable' = '__'}

$PSDefaultParameterValues['Out-Default:OutVariable'] = '__'

In the first section of the above example, we set the ShowWindow parameter of Get-Help to $true. Now, whenever I use Get-Help, it will always include the ShowWindow parameter, and I don’t have to type a thing. Keep in mind, if it’s not clear, that the first three commands all do the same thing. You’d only need to run one of them. The same goes for the second section of the above example.

So, I recently wrote a function at work. It’s another PowerShell random password generator, as if the world needed one more. In my version, there’s a CharacterType parameter that can accept one or more of four predefined values: Lowercase, Number, Symbol, and Uppercase. These values can be used alone, or in combination with each other. As I have a coworker that didn’t want symbols, I obliged by adding this character type feature. I should mention that if the CharacterType parameter isn’t included at all, that it will default to use all four character types. As it should.

Now, my coworker doesn’t have to type the below command over and over in order to include three of the four possible values, every time.

PS > New-RandomPassword -CharacterType Lowercase,Number,Uppercase

That’s when it dawned on me. I’m not sure I’ve ever used the $PSDefaultParameterValues preference variable with multiple parameter values. It’s up there, but to review, the function is New-RandomPassword, and the parameter where we want default values is called CharacterType. The default values we always want included are Lowercase, Number, and Uppercase. As you’d expect, we’re avoiding symbols in our random passwords for this example.

$PSDefaultParameterValues = @{'New-RandomPassword:CharacterType'='Number','Uppercase','Lowercase'}

$PSDefaultParameterValues['New-RandomPassword:CharacterType'] = 'Number','Uppercase','Lowercase'


Although you’d only need to run one of them, each of the above examples will modify $PSDefaultParameterValues the identical way.

PS > $PSDefaultParameterValues | Format-Table -AutoSize

Name                             Value
----                             -----
New-RandomPassword:CharacterType {Number, Uppercase, Lowercase}

Now, whenever New-RandomPassword is invoked (on a system where this entry is a part of $PSDefaultParameterValues, obviously), it’ll include those values, for that parameter, without the need to actually type the parameter name and the values — neat. As of now, I can say I’ve include multiple values for a single parameter using the $PSDefaultParameterValues variable. You too, or at least you can say you know it’s an option.

AWS UserData Multiple Run Framework Part III

At the bottom of the below post (Part II), was a function that created code for what I called the AWS UserData Multiple Run Framework. The code produced by this function can be added to the AWS CloudFormation UserData section allowing for Windows EC2 instance configuration between a controlled number of restarts. You know… configure, restart, configure, restart, configure, restart, etc. Without this in place, or some other configuration tool, you only get one time to utilize the UserData section, and that’s when an EC2 instance launches for the first time.

That version, however, required the use of text files at the root of the C:\ drive. These were used so that the UserData code would know which section of the code to run after each restart. I always worried that someone wouldn’t know the importance of the text files at the root of the C:\ drive and remove them. Therefore, I wrote a newer version. I believe I previously mentioned that as a possibility, in one of the two other related posts.

AWS UserData Multiple Run Framework Part II

This version — the newest version — makes use of the Windows Registry to make the determination of what code to run next. There’s no more scattered files on the root of the C:\, and instead, everything is better protected, and hidden, in the Windows Registry. I’ll put the entire function at the bottom of today’s post, but let’s briefly discuss each section first. Remember, the function at the bottom of this post is the function that creates the code I’ll discuss. It creates the code that you would add to the AWS CloudFormation UserData section. It isn’t the code you’d add to the UserData section. It’s still PowerShell creating PowerShell. For reference, think of the Microsoft function New-IseSnippet of old, and the New-ModuleManifest cmdlet. They’re both PowerShell, that create PowerShell.

Function Set-SystemForNextRun {
    Param (
    If ($Pass) {
        [System.Void](New-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT' -Name "Pass$Pass" -Value 'Complete')
    If ($UserData) {
        $Path = "$env:ProgramFiles\Amazon\Ec2ConfigService\Settings\config.xml"
        $ConfigXml = Get-Content -Path $Path
        ($ConfigXml.Ec2ConfigurationSettings.Plugins.Plugin |
            Where-Object -Property Name -eq 'Ec2HandleUserData').State = 'Enabled'
    If ($Restart) {
        Restart-Computer -Force

The Set-SystemForNextRun function — the first part of the code that’s created — has some minor changes. First, the PassFile parameter is now just Pass. This was changed because, well, we’re not using files any more. Second, the code inside the If ($Pass) section no longer creates text files and instead, creates registry values. Remember, as this code runs inside the AWS CloudFormation UserData section, this function is written to memory before it’s ever used. The two upcoming sections are actually run, or rather, do things we can see, first.

# Check if Registry Subkey does not exist.
If (-Not(Get-Item -Path 'HKLM:\SOFTWARE\DEPT' -ErrorAction SilentlyContinue)) {
    # Create Registry Subkey.
    [System.Void](New-Item -Path 'HKLM:\SOFTWARE\' -Name 'DEPT')

The above code is actually the first run code in UserData, as again our Set-SystemForNextRun function is written to memory and yet to be invoked. Its purpose is to create a Windows Registry subkey DEPT if it doesn’t already exist. You can change DEPT to whatever makes the most sense for your use. This is the location where we’ll store our values that indicate which section in the upcoming If-ElseIf (ElseIf, ElseIf, etc.) statement we’ll run.

If (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT').Pass1 -eq 'Complete')) {

    # Place code here (1).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -Pass '1' -UserData -Restart

} ElseIf (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT').Pass2 -eq 'Complete')) {

    # Place code here (2).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -Pass '2'


This is the meat of what’s produced by the upcoming function. It’s the If-ElseIf statement. On the first run of the UserData, the If statement will fire. This is because we won’t have a value called Pass1 set to the string “Complete” in the HKLM:\SOFTWARE\DEPT subkey. After its code is done, it’ll invoke the Set-SystemForNextRun function which will add the Pass1 value to the Registry, set UserData to enabled, and restart the instance. On the next run, it’ll execute the code in the first (and only) ElseIf section because the value Pass1 will have been created, but the value Pass2 will not have been created. It gets created after the code in this section is complete.

That’s it. Hopefully this can be helpful for more than just me and those around me at work.

Function New-AWSMultiRunTemplate {
    Param (
        [int]$CodeSectionCount = 2,

        [string]$EnableUserData = 'AllButLast',

        [string]$EnableRestart = 'AllButLast'

    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]

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

    Begin {
        #region Create logs directory and Write-Verbose function.
        If ($PSBoundParameters['Verbose'] -and $PSBoundParameters['Log']) {
            $LogDirectory = "$($MyInvocation.MyCommand.Name)"
            $LogPath = "$env:SystemDrive\support\Logs\$LogDirectory"
            If (-Not(Test-Path -Path $LogPath)) {
                [System.Void](New-Item -Path $LogPath -ItemType Directory)
            $LogFilePath = "$LogPath\$(Get-Date -Format 'DyyyyMMddThhmmsstt').txt"

            Function Write-Verbose {
                Param ($Message)
                Microsoft.PowerShell.Utility\Write-Verbose -Message $Message
                Microsoft.PowerShell.Utility\Write-Verbose -Message "[$(Get-Date -Format G)]: $Message" 4>> $LogFilePath

        # Set Write-Verbose block location.
        $BlockLocation = '[BEGIN  ]'
        Write-Verbose -Message "$BlockLocation Entering the Begin block [Function: $($MyInvocation.MyCommand.Name)]."

        Write-Verbose -Message "$BlockLocation Storing the template's function to memory."
        $TemplateFunction = @"
Function Set-SystemForNextRun {
    Param (
    If (`$Pass) {
        [System.Void](New-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT' -Name "Pass`$Pass" -Value 'Complete')
    If (`$UserData) {
        `$Path = "`$env:ProgramFiles\Amazon\Ec2ConfigService\Settings\config.xml"
        `$ConfigXml = Get-Content -Path `$Path
        (`$ConfigXml.Ec2ConfigurationSettings.Plugins.Plugin |
            Where-Object -Property Name -eq 'Ec2HandleUserData').State = 'Enabled'
    If (`$Restart) {
        Restart-Computer -Force

# Check if Registry Subkey does not exist.
If (-Not(Get-Item -Path 'HKLM:\SOFTWARE\DEPT' -ErrorAction SilentlyContinue)) {
    # Create Registry Subkey.
    [System.Void](New-Item -Path 'HKLM:\SOFTWARE\' -Name 'DEPT')

    } # End Begin.

    Process {
        #region Set Write-Verbose block location.
        $BlockLocation = '[PROCESS]'
        Write-Verbose -Message "$BlockLocation Entering the Process block [Function: $($MyInvocation.MyCommand.Name)]."

        Write-Verbose -Message "$BlockLocation Beginning to create the If-ElseIf code for the template."
        1..$CodeSectionCount | ForEach-Object {
            If ($_ -eq 1) {
                $Start = 'If'
            } Else {
                $Start = 'ElseIf'

            If ($EnableUserData -eq 'All') {
                $UserData = '-UserData '
            } ElseIf ($_ -eq $CodeSectionCount) {
                $UserData = $null
            } Else {
                $UserData = '-UserData '

            If ($EnableRestart -eq 'All') {
                $Restart = '-Restart'
            } ElseIf ($_ -eq $CodeSectionCount) {
                $Restart = $null
            } Else {
                $Restart = '-Restart'

            $TemplateIfElseIf += @"
$Start (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT').Pass$_ -eq 'Complete')) {
    # Place code here ($_).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -Pass '$_' $UserData$Restart

} $End
        } # End ForEach-Object.
    } # End Process.

    End {
        #region Set Write-Verbose block location.
        $BlockLocation = '[END    ]'
        Write-Verbose -Message "$BlockLocation Entering the End block [Function: $($MyInvocation.MyCommand.Name)]."

        Write-Verbose -Message "$BlockLocation Displaying the DEPT AWS mulitple run code with $CodeSectionCount code section(s)."
    } # End End.
} # End Function: New-AWSMultiRunTemplate.