Create a PowerShell array of hash tables from a file

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 by New-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}

 

 


Posted

in

, , ,

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *