Tag Archives: Amazon Web Services

AWS UserData Multiple Run Framework Part IV (b)


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on September 13, 2019.


This article began with Part a. Read that, if you haven’t already.

First off, the code-producing function has been renamed from New-AWSMultiRunTemplate to New-AWSUserDataMultipleRunTemplate. Sure, it’s longer in name, but it’s more clear (to me at least) when I see its name. Other small changes were the removal of unnecessary code comments. While these may have been in the New-AWSUserDataMultipleRunTemplate function itself, they were definitely changed in the produced code. Additionally, I’ve added a ProjectName parameter. The value, supplied to this parameter, is used throughout the generated code for naming purposes within the Registry. There are code changes to the produced code to run against Server 2016 and greater, as well. Therefore, it runs against Windows Server 2012 and 2012 R2 (and probably older), as well as Server 2016 and 2019. Hopefully, it will be extended further, but only AWS knows that for sure. Server 2012 R2 and earlier used EC2Config to configure a Windows instance, while 2016 and 2019 used EC2Launch.

If (test1) {
    {<statement list 1>}
} # End If.

As seen above, I tend to comment on my closing language construct brackets (see # End If.). These are now included in the produced code, both statically and dynamically. It’s a personal preference (that you’ll have to deal with if you use this free, code offering).

This is the new, New-AWSUserDataMultipleRunTemplate code producing function. This isn’t in the PowerShell Gallery, or anywhere else. It probably will never be, either. In fact, this is likely the last time I’ll prep it for public consumption. So again, this is the code you run to create the code you enter in UserData. We won’t discuss what’s in this code; however, we’ll run it further below and discuss that at a minimum.

Function New-AWSUserDataMultipleRunTemplate {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$ProjectName,

        [Parameter()]
        [ValidateRange(1,10)]
        [int]$CodeSectionCount = 2,

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

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

    Begin { 
        #region 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 $ProjectName template's function to memory."
        Write-Verbose -Message "$BlockLocation Ensure the server where the code will reside does not already have the ""HKLM:\SOFTWARE\$ProjectName"" path."
        $TemplateFunction = @"
# >> Add function to memory.
Function Set-SystemForNextRun {
    Param (
        [string]`$CodeSectionComplete,
        [switch]`$ResetUserData,
        [switch]`$RestartInstance
    )
    If (`$CodeSectionComplete) {
        [System.Void](New-ItemProperty -Path 'HKLM:\SOFTWARE\$ProjectName' -Name "CodeSection`$CodeSectionComplete" -Value 'Complete')
    } # End If.
    If (`$ResetUserData) {
        try {
            `$Path = 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml'
            [xml]`$ConfigXml = Get-Content -Path `$Path -ErrorAction Stop
            (`$ConfigXml.Ec2ConfigurationSettings.Plugins.Plugin |
                Where-Object -Property Name -eq 'Ec2HandleUserData').State = 'Enabled'
            `$ConfigXml.Save(`$Path)
        } catch {
            C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
        } # End try-catch.
    } # End If.
    If (`$RestartInstance) {
        Restart-Computer -Force
    } # End If.
} # End Function: Set-SystemForNextRun.

# >> Create/Check for Registry Subkey.
If (-Not(Get-Item -Path 'HKLM:\SOFTWARE\$ProjectName' -ErrorAction SilentlyContinue)) {
    [System.Void](New-Item -Path 'HKLM:\SOFTWARE\' -Name '$ProjectName')
} # End If.

# >> Run user code/invoke Set-SystemForNextRun function.

"@
    } # 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'
                If ($CodeSectionCount -eq 1) {
                    $End = '# End If.'
                } # End If.
            } ElseIf ($_ -eq $CodeSectionCount -and $CodeSectionCount -eq 2) {
                $Start = 'ElseIf'; $End = '# End If-ElseIf.'
            } ElseIf ($_ -eq $CodeSectionCount -and $CodeSectionCount -ne 2) {
                $End = "# End If-ElseIf x$($CodeSectionCount - 1)."
            } Else {
                $Start = 'ElseIf'
            } # End If.

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

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

            $TemplateIfElseIf += @"
$Start (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\$ProjectName').CodeSection$_ -eq 'Complete')) {

    # CodeSection $_.

    Set-SystemForNextRun -CodeSectionComplete $_ $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 Creating the AWS UserData Mulitple Run Framework code with $CodeSectionCount code section$(If ($CodeSectionCount -gt 1) {'s'})."
        "$TemplateFunction$TemplateIfElseIf"
    } # End End.
} # End Function: New-AWSUserDataMultipleRunTemplate.

Copy and paste the above, New-AWSUserDataMultipleRunTemplate function into VS Code, or another preferred PowerShell development environment, if there is such a thing. You’re not still using the ISE, are you? Then, add the function to memory for use (in VS Code, that’s Ctrl + A to select all, then F8 to run the selection). Once the function is sitting in memory, we can use it to create our UserData code, as seen below. In this version—it doesn’t even really have a version number, which is weird—there’s now a mandatory ProjectName parameter. Keep this short and simple, and if it were me, I keep spaces and odd characters out of it. This is the value that will be used in the Windows Registry, and within the code that’s produced for UserData.

PS > New-AWSUserDataMultipleRunTemplate -ProjectName MistFit
# >> Add function to memory.
Function Set-SystemForNextRun {
    Param (
       [string]$CodeSectionComplete,
       [switch]$ResetUserData,
        [switch]$RestartInstance
    )
    If ($CodeSectionComplete) {
        [System.Void](New-ItemProperty -Path 'HKLM:\SOFTWARE\MistFit' -Name "CodeSection$CodeSectionComplete" -Value 'Complete')
    } # End If.
    If ($ResetUserData) {
        try {
            $Path = 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml'
            [xml]$ConfigXml = Get-Content -Path $Path -ErrorAction Stop
            ($ConfigXml.Ec2ConfigurationSettings.Plugins.Plugin |
                Where-Object -Property Name -eq 'Ec2HandleUserData').State = 'Enabled'
            $ConfigXml.Save($Path)
        } catch {
            C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
        } # End try-catch.
    } # End If.
    If ($RestartInstance) {
        Restart-Computer -Force
    } # End If.
} # End Function: Set-SystemForNextRun.

# >> Create/Check for Registry Subkey.
If (-Not(Get-Item -Path 'HKLM:\SOFTWARE\MistFit' -ErrorAction SilentlyContinue)) {
    [System.Void](New-Item -Path 'HKLM:\SOFTWARE\' -Name 'MistFit')
} # End If.

# >> Run user code/invoke Set-SystemForNextRun function.
If (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\MistFit').CodeSection1 -eq 'Complete')) {

    # CodeSection 1.

Set-SystemForNextRun -CodeSectionComplete 1 -ResetUserData -RestartInstance
} ElseIf (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\MistFit').CodeSection2 -eq 'Complete')) {

    # CodeSection 2.

Set-SystemForNextRun -CodeSectionComplete 2
} # End If-ElseIf.
PS C:>

By default, it still creates two code sections, as can be seen just above in the If-ElseIf statement. It can create just one, although that makes less sense for something that can provide multiple opportunities for configuration between restarts. Even if you only need one, this may still be the framework for you. Maybe you need a restart after a single configuration pass. It will do up to 10 code sections if, for some reason, you need that many opportunities to configure a single instance. Not me. I’ve only ever needed three or four total. The New-AWSUserDataMultipleRunTemplate function still includes the EnableUserData and EnableRestart switch parameters. Both have the default parameter value AllButLast, however, both parameters can accept All as the value, too. If this is used for the EnableRestart switch parameter, the EC2 instance will restart after the last time it’s configured (the last code section). In my experience, it’s not always necessary to restart an instance after its final configuration, but this would allow for the time it’s needed. Once you get to know the produced code well, you can manually edit it if necessary.

The produced code has three definitive sections. We’ll start with the below, third section. By default, the New-AWSUserDataMultipleRunTemplate function creates two code sections within the third section. Read that a few times; it’s confusing. It’s like this: The produced code consists of three sections (they each start with # >>). Inside the third one, is where you can have multiple code sections, where user code, that someone else provides, maybe you, is executed against an instance. Notice that there are two comments inside our If-ElseIf statement. On the first pass, it’ll run whatever the code that the user enters to replace “# CodeSection 1.” After the UserData is enabled and the instance is restarted, it’ll run whatever code is entered to replace “# CodeSection 2.” We’ll see more about how it does this shortly.

# >> Run user code/invoke Set-SystemForNextRun function.
If (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\MistFit').CodeSection1 -eq 'Complete')) {

    # CodeSection 1.

    Set-SystemForNextRun -CodeSectionComplete 1 -ResetUserData -RestartInstance
} ElseIf (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\MistFit').CodeSection2 -eq 'Complete')) {

    # CodeSection 2.

    Set-SystemForNextRun -CodeSectionComplete 2
} # End If-ElseIf.

Now that we’ve spent some time with the third section of our produced code, let’s move upward and focus on the middle, or second section, of the code that’s been produced. Here’s that second section, now. It’s real simple. If a specific Windows Registry Subkey doesn’t exist, it’s created. The need to create this will only happen once (the first run). Every other time this If statement fires, it’ll be false and therefore, it won’t attempt to create (something that’s already been created).

# >> Create/Check for Registry Subkey.
If (-Not(Get-Item -Path 'HKLM:\SOFTWARE\MistFit' -ErrorAction SilentlyContinue)) {
    [System.Void](New-Item -Path 'HKLM:\SOFTWARE\' -Name 'MistFit')
} # End If.

And, here’s the first section, last. This function, Set-SystemForNextRun, is placed into memory after this portion of the UserData is executed. It’s invoked by the third section. If you go back up to where we discussed the third section, you’ll see where.

# >> Add function to memory.
Function Set-SystemForNextRun {
    Param (
        [string]$CodeSectionComplete,
        [switch]$ResetUserData,
        [switch]$RestartInstance
    )
    If ($CodeSectionComplete) {
        [System.Void](New-ItemProperty -Path 'HKLM:\SOFTWARE\MistFit' -Name "CodeSection$CodeSectionComplete" -Value 'Complete')
    } # End If.
    If ($ResetUserData) {
        try {
            $Path = 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml'
            [xml]$ConfigXml = Get-Content -Path $Path -ErrorAction Stop
            ($ConfigXml.Ec2ConfigurationSettings.Plugins.Plugin |
                Where-Object -Property Name -eq 'Ec2HandleUserData').State = 'Enabled'
            $ConfigXml.Save($Path)
        } catch {
            C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule
        } # End try-catch.
    } # End If.
    If ($RestartInstance) {
        Restart-Computer -Force
    } # End If.
} # End Function: Set-SystemForNextRun.

In the first code section of the third overall section, Set-SystemForNextRun was first invoked with all three possible parameters. The first parameter is sent in a numeric 1 to the CodeSectionComplete parameter. This created a new Registry value in the “HKLM:\SOFTWARE\MistFit” path. It coerced the number into a string, creating a Registry value named CodeSection1 that had a string value of Complete. These values are how the code knows what’s been done before, and what still needs to be completed. The second parameter was the ResetUserData switch parameter, indicating to enable UserData to run again, the next time the computer is restarted. The third parameter, RestartInstance, as seen below, restarts the computer, right then and there.

Set-SystemForNextRun -CodeSectionComplete 1 -ResetUserData -RestartInstance

In the second code section, it was invoked differently. This time it only updated the Registry. As it was the last code section to execute against the instance, we didn’t opt to enable UserData again or restart the instance. This won’t always be the case. It’s Windows; we may want a final, nerve-calming restart to take place.

Set-SystemForNextRun -CodeSectionComplete 2

This is a good amount of information in which to wrap your head around. The whole PowerShell function to create PowerShell can make it difficult. I did a bunch of renaming of parameters in this version and tripped myself up a few times. Luckily, it was only briefly. Had it not been, I wouldn’t be able to finish writing now and go to bed. All that said, if you have any questions, I shouldn’t be too difficult to track down.

AWS UserData Multiple Run Framework Part IV (a)


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on September 12, 2019.


This is the fourth installment of this series. I never thought I’d write another article about it and yet, here I am. One thing before we recap, I’m breaking this fourth installment into two parts: a and b. It’s too much to read at once. Well, it might be for me at least, if I wasn’t the one writing it. The link for Part b will be at the bottom of this post when it’s published, too.

This series of articles began in 2017. First, I’ll explain what each article brought to this series. Then, I’ll discuss the newest changes—the purpose of this fourth article. You know how it goes, though. You get a chance to see your old code, and you almost immediately find things you would’ve done differently. That’s what this is partially about. That and the fact that I may use this framework again here soon.

When you provision a new Windows virtual server (an EC2 Instance) in AWS or Amazon Web Services, you get an option to run batch and/or PowerShell against the instance at the first launch. This allows you, by default, a one-time shot to configure your server as you see fit. In my AWS UserData Multiple Run Framework, you’ve long been able to configure an instance multiple times between multiple restarts. Follow along, as I quickly catch you up and introduce the changes. This is partially due to how AWS has changed things from Server 2012 R2 and earlier, and Server 2016 and later. There’s also some of that traditional code clean-up, to which I eluded, and the addition of new features, as well.

In the first article, I introduced the AWS UserData Multiple Run Framework. It used text files, stored at the root of the C:\ drive, to determine where in the UserData code it should proceed after each restart.

In the second article, I picked up where the first one left off. This article was the first one to include code to create the code that would be used in the EC2 instance’s UserData. It was, and still is, PowerShell creating PowerShell. It still used text files, as it ran each code section against the server.

In the third article, things changed. Here I introduced using the Windows Registry to maintain what had and hadn’t yet been run in UserData. Additionally, I included an updated New-AWSMultiRunTemplate (as you’ll see, the name has changed) function to create the UserData code. As stated in that article and above, this isn’t the code you place inside the UserData section—it’s the code that creates the code you place inside the UserData section. It’s an important distinction.

In this fourth article—as you’ll see in part b—I’ve added and corrected a good deal. I’ve made additions, removals, and changes in both the function that produces the UserData code and within the UserData produced code, as well. In part b we’ll do a quick rundown on the modifications I remember making. Then, we’ll include and invoke our code-producing code. Following that, we’ll cover the produced code for those unfamiliar with this project. This is backward from the previous articles; however, it’s more of a logical flow for moving forward.

Part b

Add Tags to Existing AWS Parameter Store Entries

While I work with AWS, it’s unfortunately not a regular, I’m-going-to-be-in-there-a-few-times-a-week kind of a thing. Perhaps that’ll change in time, but for now, I take myself in there occasionally when there’s something I want to try, use, fix, or experiment with. Today started when I ran up against a handful of Systems Manager Parameter Store entries I had created where I didn’t include any Tags. I didn’t want to be that guy, but I also didn’t want to be the guy that manually updated each of the 12 entries by hand. Yeah, that’s not me. There were two tags per entry. It’s not that many, but still.

The documentation to do this is available in the AWS PowerShell documentation. Even so, I’m going to share what I wrote and briefly discuss it. Perhaps someone will find it instead of, or in addition to, what’s already been written about it. Anyway, let’s get started. The below code is mostly one large, continuous code block, however, I’m breaking it up to discuss it.

The first code block creates and assigns a $CommonParams variable. This holds values that each of the AWS commands needs to have included. This is just an easier way to include these parameters and parameter values in each command. This variable, when included with an AWS command (as @CommonParams), will include the Region in which I’m working and the local AWS profile (on my computer) that I’m using to run these commands. Behind this profile is an Access Key Id and corresponding Secret Access Key.

$CommonParams = @{
	Region = 'us-west-2'
	ProfileName = 'tommymaynard_api@ecs-nonprod'
}

Using the below PowerShell we can isolate the Systems Manager Parameter Store entries that we want to modify. Each entry begins with a forward slash (/), but doesn’t begin with a forward-slash followed by fdn.

$Parameters = Get-SSMParameterList @CommonParams | Where-Object -FilterScript {
	$_.Name -like '/*' -and $_.Name -notlike '/fdn*'} | Select-Object -Property Name

This next section isn’t a part of the code. It’s being included, however, so that you’re able to view the values stored in the $Parameters variable. The entire code is included at the bottom of this evening’s post so that it’s easy to capture for those that are interested.

$Parameters

Name
----
/PowerShell/FunctionTemplate/Splunk/Logging/HEC_Token  
/PowerShell/FunctionTemplate/Splunk/Logging/HEC_URL    
/PowerShell/FunctionTemplate/Splunk/Telemetry/HEC_Token
/PowerShell/FunctionTemplate/Splunk/Telemetry/HEC_URL  
/ad_join/domain  
/ad_join/password
/ad_join/user    
/agents/duo/host 
/agents/duo/ikey 
/agents/duo/skey
/agents/omsagent/primarykey 
/agents/omsagent/workspaceid
/agents/sophos/linux_url        
/agents/tenable/nessus/host     
/agents/tenable/nessus/key      
/agents/tenable/nessus/linux_url

In order to apply Tags to each of the above Parameter Store entries, we need to first create them. In $Tag01 we’ll store the createdby key and its corresponding value. In $Tag02 we’ll store the contactid and its corresponding value.

$Tag01 = New-Object Amazon.SimpleSystemsManagement.Model.Tag
$Tag02 = New-Object Amazon.SimpleSystemsManagement.Model.Tag
$Tag01.Key = 'createdby'; $Tag01.Value = 'tommymaynard'
$Tag02.Key = 'contactid'; $Tag02.Value = $Tag01.Value

This section also isn’t a part of the code. It’s been included as verification that our Tags have been properly assigned, prior to being applied to each of the Parameter Store entries.

$Tag01; $Tag02

Key       Value
---       -----
createdby tommymaynard
contactid tommymaynard

This next code block returns the current Tags for each of the Parameter Store entries. Running this code here allows us to view the current Tags, if there are any, prior to adding the Tags we know we want on each of the entries.

$Parameters.Name | ForEach-Object {
	$_; Get-SSMResourceTag @CommonParams -ResourceType 'Parameter' -ResourceId $_; '---'
} # End ForEach-Object.

This is the code block that adds our Tags to each of the Parameter Store entries. We ensured we were working with the correct entries, we created our Tags—both the keys and the corresponding values—and now we’re applying them to each entry.

$Parameters.Name | ForEach-Object {
	Add-SSMResourceTag @CommonParams -ResourceType 'Parameter' -ResourceId $_ -Tag $Tag01,$Tag02
} # End ForEach-Object.

This is the same code block we saw two blocks earlier. All it does is return the Tags for each of the Parameter Store entries. It’s included again in order to review the changes since running the Add-SSMResourceTag command. While we might normally output this as PowerShell objects, I didn’t find that to be necessary since it was simple output that only I would see and then disregard.

$Parameters.Name | ForEach-Object {
	$_; Get-SSMResourceTag @CommonParams -ResourceType 'Parameter' -ResourceId $_; '---'
} # End ForEach-Object.

As mentioned, and since I stuck some not to be included code within the code, here’s the full code for adding new Parameter Store Tags entries.

$CommonParams = @{
	Region = 'us-west-2'
	ProfileName = 'tommymaynard_api@ecs-nonprod'
}

$Parameters = Get-SSMParameterList @CommonParams | Where-Object -FilterScript {
	$_.Name -like '/*' -and $_.Name -notlike '/fdn*'} | Select-Object -Property Name

$Tag01 = New-Object Amazon.SimpleSystemsManagement.Model.Tag
$Tag02 = New-Object Amazon.SimpleSystemsManagement.Model.Tag
$Tag01.Key = 'createdby'; $Tag01.Value = 'tommymaynard'
$Tag02.Key = 'contactid'; $Tag02.Value = $Tag01.Value

$Parameters.Name | ForEach-Object {
	$_; Get-SSMResourceTag @CommonParams -ResourceType 'Parameter' -ResourceId $_; '---'
} # End ForEach-Object.

$Parameters.Name | ForEach-Object {
	Add-SSMResourceTag @CommonParams -ResourceType 'Parameter' -ResourceId $_ -Tag $Tag01,$Tag02
} # End ForEach-Object.

$Parameters.Name | ForEach-Object {
	$_; Get-SSMResourceTag @CommonParams -ResourceType 'Parameter' -ResourceId $_; '---'
} # End ForEach-Object.

I didn’t specifically test whether this overwrote Tags of the same name that existed on each entry. That said, I believe it did in fact appear to overwrite them without a care, concern, or prompt. If this is important, then it would probably be wise to test this and/or write the conditional code to do something different, if that’s what you want.

AWS re:Invent (and PowerShell, of Course)

It’s 11:30 p.m. where I’m from, so just perhaps that’s why I’m showered, in my hotel room, and ready to study for an AWS certification I’ll be taking in a couple of days. It’s 10:30 p.m. here in Las Vegas. Or maybe, while there’s a bunch of people set to do some sort of chicken wing eating contest a hotel or two over, I’m in my room and preparing to go to sleep, just before I quickly write out a new blog post, because I’m getting old. That’s probably it. There are a couple of things, I wanted to quickly mention.

One, it’s been a few years since I’ve been to one of these gigantic conferences. I’ve forgotten what it’s like, and honestly, I’m not even sure if those Microsoft TechEd conferences of the past, match up to the 45,000-person event this is set to be.

While I won’t be at the PowerShell and DevOps Global Summit 2019, I’ve been to a couple and after returning to something this big, I have something to say. The smaller ones have something these don’t. Or at least, don’t seem to have at the moment. I get it; it’s early still; chicken wings or not, the thing hasn’t really begun. But anyway, for those on the fence about going to the PowerShell conference, if that’s even possible for you, do it! Well, unless you want to ruin the ones with a billion people.

Two, a PowerShell community member contacted me on Twitter today and we’re meeting up for lunch tomorrow. How great is that!? And after that, we’re off to a PowerShell session. You read that right. Just because I’m at an AWS conference doesn’t mean I haven’t found some PowerShell content to devour. It’s called “Hands-On: Automating AWS Infrastructure with PowerShell.” To me, there’s no question that AWS has long accepted PowerShell. There are 4,499 PowerShell cmdlets in their AWSPowerShell module, and PowerShell was recently added to Lambda. There’s some commitment there. Oh! And one time, they were highly responsive and even created the Remove-EC2Instance cmdlet on my suggestion: https://tommymaynard.com/more-aws-powershell-changes-due-to-twitter-and-me-2016.

Looking forward to tomorrow and that sweet combination of AWS, PowerShell, and PowerShell community member meetups. Now to bed, and maybe I’ll still study.