AWS EC2 – How to Tag an Instance on Creation

amazon ec2amazon-web-servicespowershell

Deploying EC2 instances using AWSPowerShell, I find myself unable to apply tags on instance creation using the -TagSpecification parameter in New-EC2Instance. I am generating tags dynamically and the best I've been able to come up with so far still requires that I deploy the instance(s) without tags and then apply New-EC2Tag to the captured InstanceIDs. My construction currently feels clunky and I can't shake the suspicion that waiting for the post-deploy tagging is just deferring poor object construction to a function with better error handling; even if that is the sample AWS guidance.

How do I clean up my implementation and allow on-deploy tagging?

Sample

Given Tag Spec construction like the following…

$tags = @'
{
    "foo":"bar"
   ,"bing":"bong"
}
'@ | ConvertFrom-Json

$TagSpecification = @() 

$tags.PSObject.Properties | ForEach-Object {
    $tag = New-Object -TypeName Amazon.EC2.Model.Tag -ArgumentList @('Key', 'Value')
    $tag.Key   = $_.Name
    $tag.Value = $_.Value
    $TagSpecification += $tag
    Remove-Variable tag
} 

…attempting to apply tags on deploy in this way…

$newHost = New-EC2Instance ... -TagSpecification $TagSpecification ...

…results in the error:

New-EC2Instance : Cannot bind parameter 'TagSpecification'. Cannot convert the "Amazon.EC2.Model.Tag" value of type
"Amazon.EC2.Model.Tag" to type "Amazon.EC2.Model.TagSpecification".

However, if I omit the -TagSpecification parameter at New-EC2Instance invocation, I can use the $TagSpecification object as constructed subsequently in the call:

New-EC2Tag -ResourceId $newHost.Instances.InstanceId -Tags $TagSpecification

So clearly, New-EC2Tag has better handling for approximate object construction, but I have to assume there's a way to apply my tags in one call instead of two.

What am I doing wrong?

Best Answer

This seems to be the best way to go about it:

$TagSpecification = [Amazon.EC2.Model.TagSpecification]::new()
$TagSpecification.ResourceType = 'Instance'

$tags.PSObject.Properties | ForEach-Object {
    $tag = [Amazon.EC2.Model.Tag]@{
        Key   = $_.Name
        Value = $_.Value
    }
    $TagSpecification.Tags.Add($tag)
} 

Basically you create the TagSpecification object, then you need to add the ResourceType you're targeting (see the below link to that type's doc page for a full list of valid ResourceType values).

You can create the tags like this using the hashtable cast method, as it has a default (parameterless) constructor, and has publically settable properties. You could also create it first using a similar ::new() method to the creation of the TagSpecification object and then manually fill in the Key and Value before adding it to the list of tags.

This is also an option when it comes to tag creation:

$tag = [Amazon.EC2.Model.Tag]::new($_.Name, $_.Value)

Then, you add tags into the generic List referenced in the TagSpecification object's .Tags property.

I pulled from the docs pages here in figuring this one out: https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TTag.html https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TTagSpecification.html