Author Archives: tommymaynard

On Disk Credentials

This article has been written by plenty of people, plenty of times. But even so, I’m writing it again. This is partly because I want to have a place to find this information written by myself. That said, if it helps anyone else too, then it’s doing the other part, and that’s helping those around me.

What I want to do today, is create an on disk credential file. I’ve done it a few times now, for a few projects, but there’s something about this topic, I can’t forever nail down in my own brain. Well, as best as I see it, those days are numbered.

Once my on disk credential file is created, I want to run an automated task(s) as a user other than that which is running my outlying automation. It’s kind of like this: User1 runs an automated task and it needs to complete some work it can’t do on it’s own. Therefore, it does a part of what it needs as User2. That’s the user that can do what User1 can’t.

The first step is creating the credential file. Now, when you think of credentials, you should think of the combination of a username and a corresponding password, together. That said, this credential file is actually only going to hold the password. While I’m going to continue to call it a credential file, keep in mind that we’re only storing the password inside this file.

In the below example, we’re taking a password, as a standard string, converting it to a secure string, converting that (the secure string) into an encrypted standard string, and writing it out to an on disk file. Before we begin, we need to open our PowerShell host (the ConsoleHost, the ISE, etc.). Based on User1 and User2, we’d open PowerShell as User1. Remember, User1 is going to run our script, function, automation, etc., but it’s going to require creating a credential object — a username and password — for User2.

One other consideration to keep in mind is the computer on which this password file will be used. You have to use the credential file on the computer in which it’s created, and with the user that created it.

$PasswordAsString = '1234)(#DGTh@ppYMedixM.'
$PassOut = ConvertTo-SecureString -String $PasswordAsString -AsPlainText -Force
$PassOut | ConvertFrom-SecureString | Out-File -FilePath 'C:\Users\tommymaynard\Desktop\cred.txt'

Here’s what each line in the above example accomplishes:

Line 1: Creates a variable to hold the password as a standard string. A standard string is like any other string, such as the word dog, cat, or house. Each of those is a standard string, and most often just called a string. We’re including the word “standard,” as a way to differentiate among the other string types we’ll discuss.
Line 2: Creates a variable to hold the password once it’s been converted to a secure string. The AsPlainText and Force parameters are required for ConvertTo-SecureString to accept a standard sting for conversion.
Line 3: Converts the password as stored in the variable created in Line 2 as a secure string, to an encrypted standard string and saves it in an on disk file called cred.txt on my desktop.

Here’s the thing: The above process is a one time thing. Once the on disk credential file is created, then that step will never have to be duplicated (unless you change User2’s password). Remember, you have to open PowerShell as the user that will use the password, not as the account to which the password corresponds.

The next bit of code is how we use the on disk credential file.

$User = 'domain\User2'
$PassIn = Get-Content -Path 'C:\Users\tommymaynard\Desktop\cred.txt' | ConvertTo-SecureString
$Credential = New-Object System.Management.Automation.PSCredential -ArgumentList $User,$PassIn

Here’s what each line in this example accomplishes:

Line 1: Creates a variable to hold the username of the user that corresponds to this password.
Line 2: Creates a variable to hold the password. This command will read in the contents of our cred.txt file from our first example, and convert the encrypted standard string in the file to secure string.
Line 3: Creates a variable to store the credential object. This includes both the username from line 1, and the password we brought in from line 2.

That’s it. Again, this is a two step process. The first example was how to prestage our credential file, and the second example allows us to make use of it. The first example would likely be done once, manually, and as part of a prestaging effort. The second example would be done inside of the script, etc., to make use of running something as a user (User2) other than the one running the script, etc. itself (User1).

Okay Tommy, you now have a place to turn once you’re forgotten how to do this. You too, Internet.

Technical Fact at PowerShell Launch

When I read books, or websites, and find worthy facts, I aim to try and keep them. If I read a book, my bookmark is often a few pieces of paper stapled together with info and page numbers from the book. Well, I’m scrapping that technique, which went hand-in-hand with folding down page corners. I’m also ditching the small pieces of paper that litter my desk with random bits of information: “PowerShell objects come from classes,” and “LastLogonDate is the converted version of LastLogonTimeStamp.” Now, it’s all slated to go in a single file called InfoLines.txt in Dropbox, here: C:\Users\tommymaynard\Dropbox\PowerShell\Profile.

If you’re wondering why I use a Dropbox folder it’s because I want this file and its worthy facts to be available regardless of whether I’m on my work, or home computer. You can read more in a post I wrote that uses Dropbox to sync my profile script between work and home, here: http://tommymaynard.com/sync-profile-script-from-work-to-home-2017. It may help make what I’m doing here make more sense.

For now, because I just started this today, I only have a few lines of worthy information. Here’s the contents of my InfoLines.txt file so far. If you can’t tell, I’m finishing up Amazon Web Services in Action. I only have 70 more pages to go!

The auto-scaling group is responsible for connecting a newly launched EC2 instance with the load balancer (ELB). (AWS in Action, p.315)
DevOps is an approach driven by software development to bring development and operations closer together. (AWS in Action, p.93)
Auto-scaling is a part of the EC2 service and helps you to ensure that a specified number of virtual servers are running. (AWS in Action, p.294)

Each time I open the ConsoleHost, the ISE, or Visual Studio Code, I want a random line from the file to be shared with me. The below code will return a single, random line with asterisks both above and below it. This is in order to help separate it from the (totally unnecessary, unwelcome, and shouldn’t even be there) message that tells me how long my “personal and system profiles” took to load, and my prompt. They need to put that message in a variable and not on my screen without permission. Don’t tell us what you think we need to know, guys.

'**************'
Get-Content -Path "$env:USERPROFILE\Dropbox\PowerShell\Profile\InfoLines.txt" | Get-Random
'**************'

That’s it. Now, whenever I open one of these PowerShell hosts, I’ll get a quick reminder about something I found important, and want to keep fresh in my mind.

Update: I decided I wanted the asterisks above and below my technical fact to go from one end of the PowerShell host to other. Here’s how I did that.

'*' * ($Host.UI.RawUI.BufferSize.Width - 1)
Get-Content -Path "$env:USERPROFILE\Dropbox\PowerShell\Profile\InfoLines.txt" | Get-Random
'*' * ($Host.UI.RawUI.BufferSize.Width - 1)

AWS EC2 Instance CSV File: Let’s Have it, Amazon

Update: I’ve found something that AWS is already offering that may put this whole thing to bed. I’ll be back soon with more information!

I’ve wanted it for some time now… an always up-to-date CSV file that I can programmatically download — using PowerShell, duh — directly from AWS. But not just any CSV. I need a continually updated CSV that includes all the possible information on every AWS EC2 instance type. It can even include the previous generation instances, providing there’s a column that indicates whether it’s current or not. It would probably contain information I’ll never need, or want, and I’d still want the file, as I can easily filter against the contents of the document. I’d love to write the website post that shows how to do that, if we can get this file to fruition. So come on Amazon, make it happen. And then let me write about it on your blog! 😉

A few years ago I set out to write a PowerShell function that would extract the domain from an email address string — think com in emailaddress@somedomain.com — and check it against a list to determine whether or not it was a valid Top Level Domain (TLD). To do that, I needed an authoritative source, so I went to IANA, the Internet Assigned Numbers Authority.

Now, the only reason this PowerShell function was written, worked, and was distributed, was because IANA keeps an updated text file on their website of all the valid TLDs. My function would download the file, clean it up as needed, and then check my extracted TLD against the contents from the downloaded file. Oh, here’s that text file by the way: http://data.iana.org/TLD/tlds-alpha-by-domain.txt. One of the neat features was that my function wouldn’t try and download the file, if the file had already been downloaded in the last 24 hours. Computers, man; they’re fun!

For nostalgia’s sake, here’s a few (slightly modified) lines from the function that downloads the file’s contents and cleans them up. They really work, so you can safely run these. No, this wasn’t a CSV file, as I’m hoping we’ll see from AWS, but instead a TXT file. The file had a couple lines I had to remove, as you can see from second to last line in the below code.

$FileLocation = "$env:USERPROFILE\tlds.txt"
$FileURL = 'http://data.iana.org/TLD/tlds-alpha-by-domain.txt'
$TLDList = (Invoke-WebRequest -Uri $FileURL).Content
Set-Content -Path $FileLocation -Value $TLDList
$TLDList = Get-Content -Path $FileLocation | Where-Object {$_ -notlike '#*' -and $_ -notlike ''}
$TLDList

I’ve gone a bit off course in talking about this old project, but I think in doing so I’ve actually made my case better for why we need AWS to drop a CSV file of all the current and previous EC2 instances and their information. Give us the direct URL to said file and use one of your smart employees, or some automation, to regularly update this file, as it’s vital that AWS can guarantee the file is always accurate. Once that’s done, we can start to code around it. I already know I’d provide my team at work a function to grab the file and manipulate it as needed. That said, I wouldn’t be surprised if the team behind the AWSPowerShell module would do the same thing. What’s one more cmdlet at AWS, when there’s already well over 2,500?

I truly hope AWS is listening. I just know I can’t be the only one that would appreciate this information being delivered in this fashion, and directly from AWS.

Forcing a Switch Parameter to be False

The coworker that sits next to me at the office has upped his PowerShell game tremendously since he’s moved up from Operations to the Windows Team. He often has well-thought-out questions and he’s long shown a great desire to learn about PowerShell. It’s addictive and rewarding, so there’s no question as to why. He may not know it, but I look forward to the questions. The need for PowerShell answers here and there keeps me fresh, and so while it benefits him, it benefits me too.

As he does, he asked a question recently. It was in regard to the Confirm parameter. Why does it need the colon and $false to not be true? Why does it need to be written like this: -Confirm:$false? Why can’t it be written like this: -Confirm $false?

If a switch parameter is included, then its value is $true. If it’s not included, its value is $false. Knowing that alone, makes it clear that we’d have to force something to be false if its default is $true. Okay, knowing that, why can’t we do this then: -Confirm $false?

It’s got to do with the PowerShell parser. It’s how the engine evaluates the statement. Remember, it’s a switch parameter. The parameter is either included or it’s not. The switch parameter is inherently never dependent on a value to be included with it. It doesn’t work that way. The PowerShell engine will see -Confirm and make it $true, long before it even sees the $false value. The colon and value being attached to the Confirm parameter ensures the parser knows you want it to be of that specific value.

AWS UserData Multiple Run Framework Part II

My first post on the topic of using AWS UserData (via CloudFormation and PowerShell), was posted recently (http://tommymaynard.com/aws-userdata-multiple-run-framework-2017). While it didn’t light up Twitter and Facebook, where it was shared, there was still some value to it for me, and at least one other person. Therefore, I’ve decided to do a part two. This is one of these using-PowerShell-to-create-PowerShell posts. I just love these.

Quick recap on the first post. I use the below PowerShell function in all the UserData sections of all my CloudFormation documents (when building Windows systems). Additionally, I also include an If-ElseIf language construct to do specific actions against an EC2 instance, as it’s coming online. The best part here, is that I can do instance restarts between configuration steps. While I’ll include the full example (actually put together), from Part I below, it might be best to read that first post (link above).

Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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'
        $ConfigXml.Save($Path)
    }
    If ($Restart) {
        Restart-Computer -Force
    }
}

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {
 
    # Place code here (1).
 
    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '1' -UserData -Restart
 
} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile2.txt")) {
 
    # Place code here (2).
 
    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '2' -UserData -Restart
 
} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile3.txt")) {
 
    # Place code here (3).
 
    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '3' -UserData -Restart
 
} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile4.txt")) {
 
    # Place code here (4).
 
    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '4'
 
}

Here’s the thing, the above example is going to restart my computer three times: after the If portion, after the first ElseIf, and after the second ElseIf portion. These three restarts effectively give me four areas in which to configure an EC2 instances in sequence and again, between restarts.

There’s no way for me to predict how many code runs you or I are going to need with each new project, and whether or not we’ll want to enable UserData after the final code run, or even if we’ll need a final, final restart. Therefore, I’ve written a PowerShell function to help with this process. Why manually edit this If-ElseIf statement if you don’t have to, right? With this newer function, you can decide how many code runs you need and whether you need to enable UserData or issue a restart after your final code section executes. Here’s a few examples with the full function, New-AWSMultiRunTemplate, at the end of today’s post.

In this first example, we’ll just run the function with its default settings. This will create the Set-SystemForNextRun function and an If-ElseIf statement (with two places to configure the instance, and a single restart in between them. If this is what you needed, you’d take the output of the New-AWSMultiRunTemplate function invocation and paste it into your AWS CloudFormation’s UserData section, between an open and close PowerShell tag: <powershell> and </powershell>.

PS > New-AWSMultiRunTemplate
Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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'
        $ConfigXml.Save($Path)
    }
    If ($Restart) {
        Restart-Computer -Force
    }
}

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {

    # Place code here (1).

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

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile2.txt")) {

    # Place code here (2).

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

}

This next run will create a single If statement. I suppose if you’re only going to do a single configuration pass, that you don’t need this function at all. Again, I said I use it for all my CloudFormation templates. Say you edit your CloudFormation document later and add more configuration and restarts, you’ll already have most of what you need included in your CloudFormation document.

PS > New-AWSMultiRunTemplate -CodeSectionCount 1
Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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'
        $ConfigXml.Save($Path)
    }
    If ($Restart) {
        Restart-Computer -Force
    }
}

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {

    # Place code here (1).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '1'

}

Now, let’s get crazy! We’re going to add a bunch of ElseIf statements in this example. Additionally, we’re going to highlight a couple other features. There are parameters to use if you wish to include a final UserData reset, and a final restart. Take a look at the below parameters used with the New-AWSMultiRunTemplate.

PS > New-AWSMultiRunTemplate -CodeSectionCount 6 -EnableUserData All -EnableRestart All
Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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'
        $ConfigXml.Save($Path)
    }
    If ($Restart) {
        Restart-Computer -Force
    }
}

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {

    # Place code here (1).

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

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile2.txt")) {

    # Place code here (2).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '2' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile3.txt")) {

    # Place code here (3).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '3' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile4.txt")) {

    # Place code here (4).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '4' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile5.txt")) {

    # Place code here (5).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '5' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile6.txt")) {

    # Place code here (6).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '6' -UserData -Restart

}

Notice that in the final ElseIf above, we have the UserData and Restart parameters. These were added by including the EnableUserData and EnableRestart parameters, and All as the parameters’ value. By default, they’re both set to the AllButLast values. And that it’s! Here’s the New-AWSMultiRunTemplate function, in case it’s something you might find helpful! Enjoy!!

Function New-AWSMultiRunTemplate {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [ValidateRange(1,10)]
        [int]$CodeSectionCount = 2,

        [Parameter()]
        [ValidateSet('All','AllButLast')]
        [string]$EnableUserData = 'AllButLast',

        [Parameter()]
        [ValidateSet('All','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]
            $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 {
        #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&amp;gt;&amp;gt; 
$LogFilePath
            }
        }

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

        Write-Verbose -Message "$BlockLocation Storing the template's function to memory."
        $TemplateFunction = @"
Function Set-SystemForNextRun {
    Param (
        [string]`$PassFile,
        [switch]`$UserData,
        [switch]`$Restart
    )
    If (`$PassFile) {
        [System.Void](New-Item -Path "`$env:SystemDrive\passfile`$PassFile.txt" -ItemType File)
    }
    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'
        `$ConfigXml.Save(`$Path)
    }
    If (`$Restart) {
        Restart-Computer -Force
    }
}


"@
    } # End Begin.

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

        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(Test-Path -Path "`$env:SystemDrive\passfile$_.txt")) {
    
    # Place code here ($_).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '$_' $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)]."
        #endregion

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

 

AWS UserData Multiple Run Framework

In AWS, we can utilize the UserData section in EC2 to run PowerShell against our EC2 instances at launch. I’ve said it before; I love this option. As someone that speaks PowerShell with what likely amounts to first language fluency, there’s so much I do to automate my machine builds with CloudFormation, UserData, and PowerShell.

I’ve begun to have a need to do various pieces of automation at different times. This is to say I need to have multiple instance restarts, as an instance is coming online, in order to separate different pieces of configuration and installation. You’ll figure out when you need that, too. And, when you do, you can use the what I’ve dubbed the “multiple run framework for AWS.” But really, you call it want you want. That hardly matters.

We have to remember, that by default, UserData only runs once. It’s when the EC2 instance launches for the first time. In the below example, we’re going to do three restarts and four separate code runs.

Our UserData section first needs to add a function to memory. I’ve called it Set-SystemForNextRun and its purpose is to (1) create what I call a “passfile” to help indicate where we are in the automation process, (2) enable UserData to run the next time the service is restarted (this happens at instance restart, obviously), and (3) restart the EC2 instance. Let’s have a look. It’s three parameters and three If statements; simple stuff.

Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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'
        $ConfigXml.Save($Path)
    }
    If ($Restart) {
        Restart-Computer -Force
    }
}

This above function accepts three parameters: PassFile, UserData, and Restart. PassFile accepts a string value. You’ll see how this works in the upcoming If-ElseIf example. UserData and Restart are switch parameters. If they’re included when the function is invoked, they’re True ($true), and if they’re not included, they’re False ($false).

Each of the three parameters has its own If statement within the Set-SystemforNextRun function. If PassFile is included, it creates a text file called C:\passfile<ValuePassedIn>.txt. If UserData is included, it resets UserData to enabled (it effectively, checks the check box in the Ec2Config GUI). If Restart is included, it restarts the instance, right then and there.

Now let’s take a look at the If-ElseIf statement that completes four code runs and three restarts. We’ll discuss it further below, but before we do, a little reminder. Our CloudFormation UserData PowerShell is going to contain the above Set-SystemForNextRun function, and something like you’ll see below, after you’ve edited it for your needs.

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {

    # Place code here (1).

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

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile2.txt")) {

    # Place code here (2).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '2' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile3.txt")) {

    # Place code here (3).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '3' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile4.txt")) {

    # Place code here (4).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '4'

}

In line 1, we test whether or not the file C:\passfile1.txt exists. If it doesn’t exist, we run the code in the If portion. This will run whatever PowerShell we add to that section. Then it’ll pass 1 to the Set-SystemForNextRun function to have C:\passfile01.txt created. Additionally, because the UserData and Restart parameters are included, it’ll reset UserData to enabled, and restart the EC2 instance. Because the C:\passfile1.txt file now exists, the next time the UserData runs, it’ll skip the If portion and evaluate the first ElseIf statement.

This ElseIf statement determines whether or not the C:\passfile2.txt file exists, or not. If it doesn’t, and it won’t after the first restart, then the code in this ElseIf will run. When it’s done, it’ll create the passfile2.txt file, reset UserData, and restart the instance. It’ll do this for second ElseIf (third code run), and the final ElseIf (fourth code run), as well. Notice that the final invocation of the Set-SystemForNextRun function doesn’t enable UserData or Restart the instance. Be sure to add those if you need either completed after the final ElseIf completes.

And that’s it. At this point in time, I always use my Set-SystemForNextRun function and a properly written If-ElseIf statement to separate the configuration and installation around the necessary amount of instance restarts. In closing, keep in mind that deleting those pass files from the root of the C:\ drive is not something you’ll likely want to do. In time, I may do a rewrite that stores entries in the Registry perhaps, so there’s less probability that one of these files might be removed by someone.

Either way, I hope this is helpful for someone! If you’re in this space — AWS, CloudFormation, UserData, and PowerShell — then chances are good that at some point you’re going to want to restart an instance, and then continue to configure it.

Linux Prompt on Windows – Part V

The last time I wrote about my Linux prompt, we were on post IV. Now it’s V, and all because I’m tired of not knowing if I’m in the debugger or not. The standard PowerShell prompt, when in the debugger, will add [DBG]: to the beginning of the prompt and an extra right angle bracket to the end. Therefore, the standard PowerShell ends up looking like it does toward the bottom of this first example.

PS C:\Users\tommymaynard> Set-PSBreakpoint -Script .\Desktop\NewScript.ps1 -Line 7
ID Script                Line Command               Variable             Action
-- ------                ---- -------               --------             ------
0 NewScript.ps1            7

PS C:\Users\tommymaynard> .\Desktop\NewScript.ps1
[DBG]: PS C:\Users\tommymaynard>> q
PS C:\Users\tommymaynard>

I’ll include my entire prompt at the end of today’s post, but before we do that, let’s focus on the new part. It’s going to add these same two things to the prompt, when I’m debugging a script. If the path, Variable:/PSDebugContext exists, we can safety assume we’re in the debugger. Therefore, when we are, we’ll assign two new variables as $DebugStart and $DebugEnd.

If (Test-Path -Path Variable:/PSDebugContext) {
    $DebugStart = '[DBG]: '
    $DebugEnd = ']'
}

Again, the above If statement is stuffed between a bunch of other PowerShell that makes up the entire prompt. Before we get there, here’s an example of what my prompt looks like now when we are, and aren’t in the debugger.

[tommymaynard@server01 c/~]$ .\Desktop\NewScript.ps1
[DBG]: [tommymaynard@server01 c/~]]$ q
[tommymaynard@server01 c/~]$ 

Excellent! Now I can continue to use my own prompt function, and know when I’m in the debugger. All this, without hitting an error to remind me. In the full prompt below, we also update the WindowTitle to reflect when we’re in the debugger, too.

# Create Linux prompt.
Function Prompt {
    (Get-PSProvider -PSProvider FileSystem).Home = $env:USERPROFILE

    # Determine if Admin and set Symbol variable.
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'
    } Else {
        $Symbol = '$'
    }
	 
    # Write Path to Location Variable as /.../...
    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])"
    }

    # Determine Host for WindowTitle.
    Switch ($Host.Name) {
        'ConsoleHost' {$HostName = 'consolehost'; break}
        'Windows PowerShell ISE Host' {$HostName = 'ise'; break}
        default {}
    }

    # Create and write Prompt; Write WindowTitle.
    $UserComputer = "$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower())" 
    $Location = "$((Get-Location).Drive.Name.ToLower())$Location"

    # Check if in the debugger.
    If (Test-Path -Path Variable:/PSDebugContext) {
        $DebugStart = '[DBG]: '
        $DebugEnd = ']'
    }

    # Actual prompt and title.
    $Host.UI.RawUI.WindowTitle = "$HostName`: $DebugStart[$UserComputer $Location]$DebugEnd$Symbol"
    "$DebugStart[$UserComputer $Location]$DebugEnd$Symbol "
}

Break From a Nested Loop

I’m building a new version of my “Multi-Level Menu System with a Back Option.” Here’s the URL from the May 2016 post: http://tommymaynard.com/script-sharing-multi-level-menu-system-with-back-option-2016. This is a post where I wrote about creating a text based nested menu system. It was neat, but the nested Switch statements got a little confusing, and so it was never used by me, or potentially anyone else. I have no idea.

What I do know, is that a few times this past weekend I was able to work on a redesign for this menu system. The problem was this: From the main menu, you can press Q to quit. In the nested menus you either choose an option (1 through whatever), or press B to go back a menu. This means that if you’re three menus deep, you have to press B until you’re back at the main menu in order to press Q to quit. You can’t quit from a nested menu. Well, you couldn’t before this weekend, anyway.

We won’t go into the menu system for now, but I do want to leave an example of how to break out of nested loops. I seriously, learned something I’ve yet to ever see, and so maybe this will be a first for you, as well. We’ve all seen, and likely used break. You can read more at about_break using Get-Help: Get-Help -Name about_Break -ShowWindow.

In this first example, we’ll write the string “This is a test.” until we stop the loop’s execution. There’s nothing about this loop that’s ever going to make it stop without our help.

While ($true) {
    'This is a test.'
}

'This is a test.'
'This is a test.'
'This is a test.'
...

In this next example, we’ll immediately break out of the While loop by using the break statement. Prior to breaking out, we’ll write “This is a test.” to the host program, but this time, it’ll only be written once and then the execution will end.

While ($true) {
    'This is a test.'
    break
}
'This is a test.'

Let’s start our next example by nesting a While loop, inside of a While loop. In this example, we’ll write “Outer While loop” once, and then continually write “Nested While loop” until we manually end the execution. We can’t get back to the outer While loop, when we’re forever stuck in the inner While loop.

While ($true) {
    'Outer While loop'

    While ($true) {
        'Nested While loop'
    }
}
'Outer While loop'
'Nested While loop'
'Nested While loop'
'Nested While loop'
...

This next example includes a break statement inside the nested While loop. This means we’ll write “Outer While loop” and “Nested While loop” over and over, forever. We’ll at least until we stop the execution. In this example, we can get back to the outer While loop.

While ($true) {
    'Outer While loop'

    While ($true) {
        'Nested While loop'
        break
    }
}
'Outer While loop'
'Nested While loop'
'Outer While loop'
'Nested While loop'
'Outer While loop'
'Nested While loop'
...

Our final example, will include the word outer, after the break statement. In this example, we’ll execute the outer While loop, execute the inner While loop, and then break out of both of the While loops. Yes both, from inside the inner loop.

I didn’t even know this was possible before the weekend. My nested menu system is absolutely going to need this! Now, I can allow my users to quit, no matter how deep their level nestation — yes, I totally just made up that word. Enjoy, and maybe it’s helpful for you one day!

While ($true) {
    'Outer While loop'

    While ($true) {
        'Nested While loop'
        break outer
    }
}
'Outer While loop'
'Nested While loop'

Mike Robbins’ PowerShell 101

If you haven’t heard of Leanpub, then that’s about to happen. I only know so much about it, but I can already see the benefits to it. It allows authors to write, publish, and distribute eBooks as they’re being written, and of course at completion. The times are changing, as is technology, and so good authors can’t always be expected to print their books. Trust me, I’m currently reading an AWS book from 2015.

I briefly want to mention a project by Mike F Robbins on Leanpub entitled, PowerShell 101. If you’re not following him on Twitter, you ought to be. In January of this year (2017), he introduced his project to the community; he wanted to write and author an entry-level PowerShell book for anyone that wants to learn PowerShell. The neat thing here, is that he desired to share things he wish he would’ve been told, when he was just starting out. The book discusses the help system, objects, using the pipeline, and more topics you’d expect in a book of this type.

So, I sent Mike a message on Twitter. I told him I would be willing to read the book, as he was writing it, and try and help his publishing efforts by offering comments, critiques, and edits. I wasn’t implying he couldn’t do it alone, but did want to offer my writing and editing skills, and PowerShell knowledge toward his project.

Mike agreed, and so over the last several months, I’ve read chapter by chapter, as Mike’s been pumping them out. I’ve provided what I can to help his endeavor — not that he really needed me — but so he had another set of eyes. I feel it’s important that there’s been another person reading along, and thinking about his instruction, as a newcomer to PowerShell.

So with that, share the book as you can and as appropriate. PowerShell is fun. It’s even more fun, when you have those basics deep in your pocket, and so I’m glad to have been a part of this project. I truly hope this book can bring clarity to someone’s learning, as they move themselves into being a part of our PowerShell community.

If you didn’t catch the above link, here it is again: PowerShell 101.

 

PowerShell Saturday Returns to Phoenix

Phoenix PowerShell Saturday 2017

For the second year in a row, I’ve agreed to speak at the Phoenix PowerShell Saturday. Last year was a great opportunity for me in regard to both speaking — my first go at that, when combined with PowerShell — and learning. Thanks so much to Jason Helmick (who gave an amazing talk), and the others involved. Speaking is a small price to pay for a free, full day of PowerShell discussion and learning.

Based on the feedback from Thom Schumacher, both a speaker and event organizer, I get the feeling I did an adequate job. I may never really believe that, but I’ve agreed to head north again this October and talk about my favorite topic, with those that choose to attend. Yes, it’s PowerShell.

As of now, I’ve decided to share my newest I-wrote-it-mostly-at-home work project. What I’ve done is written a function template with built-in logging, and it’s much better than my 1.x versions (link). This could change, I suppose, but for now, I’m proud of what I’ve written and I’d like an opportunity to talk about and hand it off to the PowerShell community. The only thing I know thus far, is that’s it’s in Phoenix in October 2017. Watch the #PowerShell hashtag on Twitter for more information.

Phoenix PowerShell Saturday 2016

Here’s a few links from the 2016 event. The first below link is from one session where I discussed the fundamental three (cmdlets): Get-Help, Get-Command, and Get-Member. The second link is from a second session where I discussed how to get into writing reusable code. In this session, I additionally walked though various language constructs (If, If-Else, Do, and more). The final below link is a link to my speaker profile for 2016. I needed a place to put these links, and with the upcoming PowerShell Saturday for 2017, this seemed like as good place as any.

http://powershellsaturday.com/012/presentation/new-to-powershell-session-4/
http://powershellsaturday.com/012/presentation/new-to-powershell-session-6/
http://powershellsaturday.com/012/speaker/tommy-maynard/