Recently, I posted a sample PowerShell script using AWS PowerShell cmdlets you can use to create a VPC using CloudFormation. I also included a PowerShell script that creates a CloudFormation stack because repeatedly launching templates in the AWS Console gets tedious, especially during testing.
That PowerShell script is fine as far as it goes. But it has one big drawback: the call to New-CFNStack
uses override parameters embedded in the script. I posted the script that way because sometimes I like to keep the script “as run” to document the infrastructure.
But when you write a generalized CloudFormation template and want to run it repeatedly with different parameters, embedding override parameters in the script doesn’t work very well.
Having decided to separate out the parameters to a text file, I figured it’d be easy as pie to just load ’em up from a text file and let ‘er rip.
Ha! It took me a while to figure out how to:
- Create a hash table from a text file
- Create an array of hash tables of type
Amazon.CloudFormation.Model.Parameter.
That’s the input object required byNew-CFNTemplate
.
The solution I came up with is below (it’s a modified version of the script in the original post. Also, check out this CloudFormation template, which creates a VPC with a Linux jump server. You can use the script below to launch either template or any other template you wish.)
I’ve written before about hash tables and arrays of hash tables in PowerShell. That post is a good how-to on using PowerShell hash tables in a script. But it doesn’t help if you need to create a hash table from key=value pairs stored in a file.
Fortunately, importing hash tables from a file is very easy to do in PowerShell. First, you need to get the contents of the file into a variable:
$str = Get-Content -Path myparms.txt -Raw
Note what without -Raw
, the variable will contain an array of strings. That’s not what we want here; we need a plain old string. Next, you need to convert the input to a hash table. For that, we use ConvertFrom-StringData
:
$ht = $str | ConvertFrom-StringData
Or, better:
$ht = Get-Content -Path myparms.txt -Raw | ConvertFrom-StringData
The input text file (myparms.txt
) must be in key=value format, for example:
…
parm1 = value1
parm2 = value2
This is an easy way to create a hash table. But what New-CFNStack
expects is an array of hash tables of a specific type. That type is Amazon.CloudFormation.Model.Parameter
.
That means we need to loop through the hash table of key=value pairs and create an array of hash tables of the proper type.
Of course, one thinks first of a foreach
loop. But how can you loop through the keys of a PowerShell hash table? Simple: use the Get-Enumerator()
method on the hash table. Now, we can iterate through the hash table, retrieve both key and value entries from the hash table and create a new array of hash tables of type Amazon.CloudFormation.Model.Parameter
to be specified in the call to New-CFNStack
.
Here’s an excerpt from the script below that puts these two techniques together:
$hashtable = Get-Content $StackParms -Raw | ConvertFrom-StringData
$parms = @()
foreach ($h in $hashtable.GetEnumerator()) {
$o = New-Object Amazon.CloudFormation.Model.Parameter -Property `
@{Key="$($h.Name)";Value="$($h.Value)";UsePreviousValue=$false }
$parms += $o
}
Note the use of a subexpression in the loop — "$($h.Name)"
— to retrieve the current key and value using GetEnumerator()
.
I realize this might be a lot to digest at first. But if you think about it long enough, I think you’ll agree that PowerShell’s implementation of arrays, hash tables and arrays of hash tables is very powerful and elegant. You might enjoy reading more about hash tables in the PowerShell doc.
I hope this discussion of how PowerShell hash tables, AWS PowerShell cmdlets and CloudFormation can be used together is useful for you.
<# .SYNOPSIS Launches a CloudFormation template from a local disk file and inputs template parameters from a local file .DESCRIPTION Using a template file on the local disk along with parameters stored in a text file in 'key = value' pairs, calls Test-CFNTemplate to validate a single YAML or JSON CloudFormation template and, if valid, launches that stack into the current AWS account and region. Also shows elapsed stack creation time and stack creation status. .PARAMETER Template -Template [String] Path to CloudFormation template .PARAMETER Template -StackParms [String] Path to template parameters to be overridden. Text file with key = value pairs .EXAMPLE PS C:> ./Launch-CloudFormation-template-from-file-with-parameters -Template .MyCloudFormationTemplate.yaml -StackParms .stackparms.txt .NOTES (c) 2017 Air11 Technology LLC -- licensed under the Apache OpenSource 2.0 license, https://opensource.org/licenses/Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Author's blog: https://yobyot.com #> # Name of file containing stack definition Param( [Parameter(Mandatory=$true, Position=0, HelpMessage="Path to stack file.")] [Alias("ST")] [ValidateNotNullOrEmpty()] [string] $Template, # Name of file containing stack paramters [Parameter(Mandatory=$true, Position=1, HelpMessage="Path to stack parameters file.")] [Alias("SP")] [ValidateNotNullOrEmpty()] [string] $StackParms ) $hashtable = Get-Content $StackParms -Raw | ConvertFrom-StringData # Read "raw" text time and convert it to a hashtable $parms = @() #Initialize array containing hash tables of parameters foreach ($h in $hashtable.GetEnumerator()) { $o = New-Object -TypeName Amazon.CloudFormation.Model.Parameter -Property @{Key = "$($h.Name)"; Value = "$($h.Value)"; UsePreviousValue = $false } $parms += $o } $templateBody = Get-Content -Path $Template -Raw Test-CFNTemplate -TemplateBody $templateBody $AWSAPIResponse = (($AWSHistory.LastServiceResponse).GetType()).Name $start = Get-Date switch ($AWSAPIResponse) { "ValidateTemplateResponse" { $timestamp = Get-Date -Format yyyy-MM-dd-HH-mm $stack = New-CFNStack -TemplateBody $templateBody ` -StackName "Stack-test-$timestamp" ` -DisableRollback $true ` -Parameter $parms do { Start-Sleep -Seconds 30 $status = (Get-CFNStackSummary | Where-Object -Property StackID -EQ $stack).StackStatus "Stack status: $status Elapsed time: $( New-TimeSpan -Start $start -End (Get-Date) )" -f {g} } until ( ($status -eq "CREATE_COMPLETE") -or ($status -eq "CREATE_FAILED") ) } default { "New-CFNStack failure: $AWSAPIResponse" } } "Last stack creation status $status" "Total elapsed time: $( New-TimeSpan -Start $start -End (Get-Date) )" -f {g}
Leave a Reply