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 part two. This is one of these using-PowerShell-to-create-PowerShell posts. I just love these.
A 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 instance 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, 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 are 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>.
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" [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, 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 to be included in your CloudFormation document.
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" [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 of 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
.
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" [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&gt;&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" [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.