Retrieve Amazon EC2 instance tags using PowerShell
Tags: aws, powershell, ec2
Published: 2022-01-12 20:04:04

In a [recent What’s New post](https://aws.amazon.com/about-aws/whats-new/2022/01/instance-tags-amazon-ec2-instance-metadata-service/), the [Amazon EC2](https://aws.amazon.com/ec2/) team announced that you can now retrieve, from the instance metadata service (IMDS), tags that you’ve applied to your EC2 instances (virtual machines) if you opt-in to making them available via IMDS during instance launch. You can opt-in when launching instances from the AWS Management Console, programmatically using [EC2’s `RunInstances` API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html), or other tooling you might use to launch instances, such as [CloudFormation](https://aws.amazon.com/cloudformation/) templates. Below is a screenshot of the management console option to in the EC2 launch wizard to make tag data available on the instance:

![Enabling tags in instance metadata using the management console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/srlz50n663a4itfkr7ud.png)

The IMDS is a private endpoint that’s only accessible using a non-routable IPv4 or IPv6 address from within the instance, and provides a variety of metadata related to the instance. Some examples are the ID of the Amazon Machine Image (AMI) used to launch the instance, the ID of the instance, security role information, the region in which the instance is running, block device mappings, and much more. The [instance metadata categories topic in the EC2 User Guide](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html) details the kinds of data available from IMDS on an instance, which can be queried on the instance without needing credentials.

Tags are an additional form of metadata that you can apply to your resources in the AWS cloud. A tag is simply a label that consists of two parts, a user-defined key and its associated value. [Tagging resources is considered a best practice](https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html), as it can help with identifying, grouping, and categorizing related resources. For example, let’s say I have code running on an instance but the code needs to know what staging environment (dev, beta, gamma, prod, etc.) the instance is in. I can apply a tag to the instance that denotes the staging environment and then, from within the code on the instance, query the stage and adjust what the code is doing appropriate to that environment. By the way, EC2 instances are just one type of AWS resource that you can apply tags to.

## Reading tags in the "before times"
Prior to EC2 making tags available from IMDS, to access them from code running on an instance you needed to make use of [EC2’s `DescribeTags` API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeTags.html). This API is exposed by the [Get-EC2Tag cmdlet](https://docs.aws.amazon.com/powershell/latest/reference/items/Get-EC2Tag.html) in the [AWS Tools for PowerShell](https://aws.amazon.com/powershell/) modules.

You’ll find the monolithic _AWSPowerShell_ module pre-installed on Windows images provided by Amazon EC2. For Linux and macOS there's an equivalent cross-platform (and also monolithic) _AWSPowerShell.NetCore_ module. If you're using an image that doesn't have the modules pre-installed it's easy to add them yourself and, if you go this route, I recommend using the modular (_AWS.Tools.*_) variant instead of the monolithic modules. In the modular variant, each AWS service has its own PowerShell module making the individual modules much faster to load (the monoliths each contain thousands of cmdlets). So, for example, there are _AWS.Tools.EC2_, _AWS.Tools.S3_, and other modules. The modular variant can be used on Windows, Linux, and macOS systems. Note that the AWSPowerShell, AWSPowerShell.NetCore, and AWS.Tools.* modules are all just packaging variations, and provide access to the same set of cmdlets.

The example below shows how I can retrieve the tags attached to a given instance using `Get-EC2Tag` (this code runs on the instance):

```powershell
PS C:\> Get-EC2Tag -Filter @{Name="resource-type";Values="instance"},@{Name="resource-id";Values=" i-0f53ee144573513c5"}

Key     ResourceId          ResourceType Value
---     ----------          ------------ -----
Name    i-0f53ee144573513c5 instance     App1
Project i-0f53ee144573513c5 instance     App1Project
Purpose i-0f53ee144573513c5 instance     Dev team app testing
Stage   i-0f53ee144573513c5 instance     Beta
Team    i-0f53ee144573513c5 instance     WinDev
```

The output echoes the five keys, _Name_, _Project_, _Purpose_, _Stage_, and _Team_, and their associated values, of the tags I attached to my instance. The filters used in the command restrict the lookup to, firstly, tags applied to EC2 instances, and secondly the specific instance in question. This is because the underlying API is able to return tags for other resources types. I can run this cmdlet anywhere, not just on the instance. It also, by the way, works irrespective of whether I elect to make tags available from IMDS.

There are, however, two problems with this approach when it comes to running it on an instance. First, for the cmdlet to be able to access the `DescribeTags` API, it needs credentials so I’d have to launch my EC2 instances with a role that has a trust relationship with ec2.amazonaws.com to allow the tools to obtain temporary credentials on demand. This role would also have needed to grant permissions to call the `DescribeTags` API. This isn’t so bad, as launching instances with an attached role is a fairly standard practice.

The second problem relates to the need to specify the instance ID and this makes it much more awkward – how do I get the ID of the instance I’m running the cmdlet on? In this scenario, I was connected to my instance using RDP and so was able to paste the instance ID into the command. This won’t work from automation though so we need another way.

###Retrieving the instance ID

I noted earlier that one of the items of data available from IMDS is the instance ID. The question is, how to get it. If you read the AWS documentation, you’ll find examples showing how to ‘curl’ the IMDS endpoint (_http://169.254.169.254_ in IPv4, or _http://[fd00:ec2::254]_ in IPv6) for data. However, there’s a simpler way using the `Get-EC2InstanceMetadata` cmdlet, present in all variants of the AWS Tools for PowerShell modules (in the modular variant, you’ll find it in the _AWS.Tools.EC2_ module).

`Get-EC2InstanceMetadata` handles the work of accessing the underlying IMDS endpoint for you, and outputs the requested metadata. The cmdlet accepts a `-Category` parameter that you use to select which data you want output. There’s also a switch, `-ListCategory`, that displays the set of categories that whatever version of the cmdlet had built-in support for when it was released (new categories are added over time).

For the earlier example, we could rewrite it to use `Get-EC2InstanceMetadata` cmdlet with the `InstanceId` category value to get the same output:

```powershell
PS C:\> Get-EC2Tag -Filter @{Name="resource-type";Values="instance"},@{Name="resource-id";Values=(Get-EC2InstanceMetadata -Category InstanceId)}

Key     ResourceId          ResourceType Value
---     ----------          ------------ -----
Name    i-0f53ee144573513c5 instance     App1
Project i-0f53ee144573513c5 instance     App1Project
Purpose i-0f53ee144573513c5 instance     Dev team app testing
Stage   i-0f53ee144573513c5 instance     Beta
Team    i-0f53ee144573513c5 instance     WinDev
```

This is better, and works with automation scenarios on the instance, but we still need credentials to be able to run `Get-EC2Tag`. So, let’s look at solving that problem using the new support for retrieving tags from the instance metadata service.

##Retrieving tags from IMDS

If you run the `Get-EC2InstanceMetadata` cmdlet with the `-ListCategory` switch (or check the help docs), you’ll see the set of category values the cmdlet is aware of in whatever version you have available:

```powershell
PS C:\> Get-EC2InstanceMetadata -ListCategory
AmiId
LaunchIndex
ManifestPath
AncestorAmiId
BlockDeviceMapping
InstanceId
InstanceType
LocalHostname
LocalIpv4
KernelId
AvailabilityZone
ProductCode
PublicHostname
PublicIpv4
PublicKey
RamdiskId
Region
ReservationId
SecurityGroup
UserData
InstanceMonitoring
IdentityDocument
IdentitySignature
IdentityPkcs7
```

These built-in category values exist simply to make retrieving certain data more convenient at the command line. You’ll notice though that there’s no mention of tags. Not a problem! The cmdlet also accepts a -Path parameter and using this, you can supply the path of any metadata item and retrieve the value. This is exactly what happens when you specify a category value - the cmdlet knows the path associated with the requested category and internally requests the data using the path. So, for our purposes, we can retrieve instance tags using the _/tags/instance_ path, shown below:

```powershell
PS C:\> Get-EC2InstanceMetadata -Path /tags/instance
Name
Project
Purpose
Stage
Team
```

That gives us the tag keys. To retrieve the value for a tag, we simply append the key to the path (note that the keys are case-sensitive):

```powershell
PS C:\> Get-EC2InstanceMetadata -Path /tags/instance/Name
App1

PS C:\> Get-EC2InstanceMetadata -Path /tags/instance/Stage
Beta

PS C:\> Get-EC2InstanceMetadata -Path /tags/instance/Purpose
Dev team app testing
```

If you’re already using the AWS cmdlets on your instances, then accessing metadata and tags using the Get-EC2InstanceMetadata cmdlet is simple and convenient. However, as I noted earlier the monolithic variants of the modules (AWSPowerShell and AWSPowerShell.NetCore) contain thousands of cmdlets and do take some time to load (this is not a problem with the AWS.Tools.EC2 module, which is why I recommend you choose that if you can). If all you need to do is access the tag data and not make use of the other AWS cmdlets, you might prefer to retrieve tags using `Invoke-RestMethod`, shown in the example below:

```powershell
PS C:\> Invoke-RestMethod -UseBasicParsing -Uri '169.254.169.254/latest/meta-data/tags/instance'
Name
Project
Purpose
Stage
Team

PS C:\> Invoke-RestMethod -UseBasicParsing -Uri '169.254.169.254/latest/meta-data/tags/instance/Name'
App1
```

Whichever approach you use, automation scripts running on your instances can make use of tags to reason about the runtime environment and adjust behavior accordingly. Happy tagging!