<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Steve Roberts</title>
    <description>The latest articles on DEV Community by Steve Roberts (@steveroberts).</description>
    <link>https://dev.to/steveroberts</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F789750%2Fe189ff5b-7414-4b0b-a639-696b801a9fc5.jpeg</url>
      <title>DEV Community: Steve Roberts</title>
      <link>https://dev.to/steveroberts</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/steveroberts"/>
    <language>en</language>
    <item>
      <title>Adopting the Lambda Annotations Framework</title>
      <dc:creator>Steve Roberts</dc:creator>
      <pubDate>Fri, 03 Mar 2023 04:03:56 +0000</pubDate>
      <link>https://dev.to/steveroberts/adopting-the-lambda-annotations-framework-4k8</link>
      <guid>https://dev.to/steveroberts/adopting-the-lambda-annotations-framework-4k8</guid>
      <description>&lt;p&gt;If you're a .NET developer working with &lt;a href="https://aws.amazon.com/lambda" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; I hope you've heard about the new Lambda Annotations Framework. It's an open-source framework for .NET-based Lambda functions which (in my opinion) simplifies your function code, reducing boilerplate. In &lt;a href="https://aws.amazon.com/blogs/developer/introducing-net-annotations-lambda-framework-preview/" rel="noopener noreferrer"&gt;this blog post&lt;/a&gt;, published with the release of the original preview, you'll find the ideas behind the framework discussed in more detail.&lt;/p&gt;

&lt;p&gt;The development team's been hard at work since that initial release, and recently added support to make it really simple to return HTTP status data. So, I thought the time was right to look at how simple it is to update an existing function that has a traditional Lambda function signature to use the framework.&lt;/p&gt;

&lt;p&gt;The function I'm going to work on, called &lt;em&gt;Backmask&lt;/em&gt;, is part of a sample serverless application used in the &lt;em&gt;AMster &amp;amp; the Brit's Code Corner&lt;/em&gt; show, broadcast on &lt;a href="https://aws.amazon.com/developer/community/live-video/aws-on-air" rel="noopener noreferrer"&gt;AWS on Air&lt;/a&gt;. The application contains an API and backing Lambda function that accepts a text string message, converts it to audio using &lt;a href="https://aws.amazon.com/polly" rel="noopener noreferrer"&gt;Amazon Polly&lt;/a&gt;, then reverses the audio and finally returns the Base64-encoded text version of the audio. You might be familiar with this idea from music, where an artist embeds reversed messages inside the main audio (or so my co-host, AM, on Code Corner tells me!). In the latest episode of Code Corner, I work through the steps of converting the function to use the new framework live. This post represents the condensed version without all the chit-chat.&lt;/p&gt;

&lt;p&gt;You can find the sample application in &lt;a href="https://github.com/allenmichael/CodeCornerProjects/tree/main/projects/s1e2/backmask.net" rel="noopener noreferrer"&gt;this GitHub repo&lt;/a&gt;. It comprises a Serverless Application Model (SAM) template, a Lambda function using .NET 6, and an &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt; endpoint. The function runs when the endpoint receives a request. A Lambda layer carries the dependency on &lt;a href="https://ffmpeg.org/" rel="noopener noreferrer"&gt;ffmpeg&lt;/a&gt; for processing of the audio created by Polly. You can also find the original Python version, used in an earlier Code Corner episode, there too. &lt;/p&gt;

&lt;p&gt;Below is the function signature, and, while it's perfectly good C# it could be more expressive in terms of what the function expects and returns. As written, it returns an object of type &lt;em&gt;&lt;code&gt;APIGatewayHttpApiV2ProxyResponse&lt;/code&gt;&lt;/em&gt; that will contain the encoded text and status code, and accepts two objects as function parameters. One is the Lambda context, providing information about the running environment of the function. This includes a &lt;code&gt;Logger&lt;/code&gt; object that the function makes use of to log activity. The other, and more important parameter, is an API Gateway event object, &lt;code&gt;APIGatewayHttpApiV2ProxyRequest&lt;/code&gt;. This contains the event payload from the API - the request body, headers, and any query and path parameters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3tovrgqlpqgdyek0w3t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3tovrgqlpqgdyek0w3t.png" alt="Original function signature"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The text to backmask is supplied in the request body. An optional query string parameter, &lt;em&gt;voiceOverride&lt;/em&gt;, can select the voice that Polly should use to create the audio. Inside the function body, there's a lot of boilerplate code processing the event data sent to the function from the API endpoint, before we actually get to anything useful, highlighted below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5bi43frqk8b9zygzi6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5bi43frqk8b9zygzi6i.png" alt="Original function code"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Converting this function to use the new annotations framework will make it easier to see what the function accepts as parameters, and what the function is actually doing, and it only takes a few steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Add the annotations package
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.nuget.org/packages/Amazon.Lambda.Annotations" rel="noopener noreferrer"&gt;Amazon.Lambda.Annotations NuGet package&lt;/a&gt; contains the new framework, so I start by adding it to the project. If you're following along, open a command line shell and run the command below from the folder containing the project file, or use your favorite environment to add the package:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

dotnet add package Amazon.Lambda.Annotations


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the function's code file (function.cs, if you're following along), add a couple of &lt;code&gt;using&lt;/code&gt; statements for the namespaces in this new package. The first is for the attributes we'll use from the framework, the second relates to the &lt;code&gt;HttpApi&lt;/code&gt; types used with the function's API Gateway resource.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Amazon.Lambda.Annotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Amazon.Lambda.Annotations.APIGateway&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Step 2: Annotate the function&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Before changing the function signature to better represent what the function accepts, first add the &lt;code&gt;LambdaFunction&lt;/code&gt; attribute above the function. Note that I'm using this attribute with no arguments, but you can use some to set custom data such as desired timeout, memory size, role, policies, and more.&lt;/p&gt;

&lt;p&gt;Second, the function runs when a request arrives at a &lt;em&gt;HttpApi&lt;/em&gt; endpoint in API Gateway, so I add the &lt;code&gt;HttpApi&lt;/code&gt; attribute too. For this function, I specify the verb (POST) and a route for the endpoint (/backmask in this case).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9a0c42w1jktl1297r6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9a0c42w1jktl1297r6m.png" alt="Annotation attributes"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Next, I need to change the function signature. I still want to return an HTTP result containing both status and the encoded text, so the return type will be &lt;code&gt;IHttpResult&lt;/code&gt; (this is the new functionality added recently to the framework). I could, if I wanted, change the return type to be just a string representing the text-encoded audio, but I want to keep the initial payload validation and associated HTTP status code results, so an &lt;code&gt;IHttpResult&lt;/code&gt; result is ideal. &lt;/p&gt;

&lt;p&gt;Now I update the parameters of the function. Instead of the original &lt;code&gt;APIGatewayHttpApiV2ProxyRequest&lt;/code&gt; type, I want the function signature to express the true intent; it should accept a string representing the id of the voice to use when generating the audio, and a second string containing the text to encode. The voice parameter will come from the query string, so I decorate it with the &lt;code&gt;FromQuery&lt;/code&gt; attribute and make sure we use the same name as we expect in the request URL. The text to encode will come from the body so similarly I use a &lt;code&gt;FromBody&lt;/code&gt; attribute to express the body as the source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cerlc9aarqc68teuy3c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cerlc9aarqc68teuy3c.png" alt="New function signature"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Note that I'm keeping the Lambda context parameter as I want to keep the logging code making use of it, but could remove it otherwise.&lt;/p&gt;

&lt;p&gt;Next, I can clean up the boilerplate code seen earlier that extracted the real parameters needed from the original &lt;code&gt;APIGatewayHttpApiV2ProxyRequest&lt;/code&gt; parameter. Instead of constructing &lt;code&gt;APIGatewayHttpApiV2ProxyResponse&lt;/code&gt; objects, if validation fails, l use the newly added &lt;code&gt;HttpResults&lt;/code&gt; type in the framework.&lt;/p&gt;

&lt;p&gt;The result is below, which you can compare with the original above. I think you'll agree it's a lot simpler and easier to read (for context, I've once again highlighted the "actual work" part of the function).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcxe9avojbxlud28rgh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcxe9avojbxlud28rgh0.png" alt="Revised function code"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Those few steps were all that were required to convert the function's code. The next step is to build the project. Once we've done that, inspection of the serverless template file for the project shows a change - the source generator implemented inside the annotations framework has output a new &lt;code&gt;AWS::Serverless::Function&lt;/code&gt; resource for the function into the template. It's easy to spot, as the logical Lambda function name has the suffix &lt;em&gt;Generated&lt;/em&gt;. This generated function resource has an added benefit in that I no longer have to keep the function handler string in the resource definition in sync with the function code. If I decide to change the name of the actual method implementing the function (the one I attributed with &lt;code&gt;[LambdaFunction]&lt;/code&gt;), or the class, or the namespace containing the class then, when I build the code, the source generator in the framework will keep the related resource in the template up-to-date for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Copy function settings to the generated function definition
&lt;/h2&gt;

&lt;p&gt;I'm almost done with all the conversion steps. In my case, the function resource declared in the template had a custom permissions policy specified, allowing the code to call the &lt;code&gt;polly:SynthesizeSpeech&lt;/code&gt; API. It also referenced a Lambda layer (also declared in the template) that contains the ffmpeg binaries used to reverse the audio generated by Polly. So, I simply copy the relevant items to the generated function resource definition, and then delete the old function resource.&lt;/p&gt;

&lt;p&gt;The highlighted boxes in the image below show the data that I simply copied from the original function resource definition. Note that I also deleted the &lt;code&gt;MemorySize&lt;/code&gt; and &lt;code&gt;Timeout&lt;/code&gt; elements from the generated function definition. I did this since I have them specified (as the same default values) in a &lt;code&gt;Globals&lt;/code&gt; section of my template. If, however, I'd specified them as arguments to the &lt;code&gt;[LambdaFunction]&lt;/code&gt; attribute I attached to the code I would have kept them. As I noted earlier, I can also set these values, along with role and policy information, in that attribute. If I do so, the values I set get emitted by the source generator into the template and kept in sync with changes to the attribute. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2an28au8re0fh2xoalhy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2an28au8re0fh2xoalhy.png" alt="Generated function resource in the template"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: …there is no step 4
&lt;/h2&gt;

&lt;p&gt;That's it, "job done" for this function! We can now deploy the serverless application and test it out or, if it contains more functions, convert those to use the new annotations framework. If you want to look at the project and the changes in their entirety, checkout the &lt;em&gt;s1e2_backmask.net-annotations&lt;/em&gt; branch in the &lt;a href="https://github.com/allenmichael/CodeCornerProjects/tree/main/projects/s1e2/backmask.net" rel="noopener noreferrer"&gt;Code Corner repository on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hopefully, this post will have intrigued you enough to check out the new framework, and try converting some of your existing functions into what I think is a much more readable and easier to comprehend format. Also, the development team is always interested in feedback, which you can supply in the &lt;a href="https://github.com/aws/aws-lambda-dotnet/issues" rel="noopener noreferrer"&gt;issues folder of their repo&lt;/a&gt;. You can also find the design document for the annotations in &lt;a href="https://github.com/aws/aws-lambda-dotnet/issues/979" rel="noopener noreferrer"&gt;this issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy annotating!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>dotnet</category>
      <category>serverless</category>
    </item>
    <item>
      <title>AMster &amp; the Brit's Code Corner</title>
      <dc:creator>Steve Roberts</dc:creator>
      <pubDate>Tue, 19 Jul 2022 20:06:07 +0000</pubDate>
      <link>https://dev.to/steveroberts/amster-the-brits-code-corner-1f4c</link>
      <guid>https://dev.to/steveroberts/amster-the-brits-code-corner-1f4c</guid>
      <description>&lt;p&gt;AWS on Air is a livestream show, broadcast from AWS events in North America as well as regular Friday shows when the team isn't at an event. You can find &lt;a href="https://www.youtube.com/playlist?list=PL2yQDdvlhXf8cfiry7ngFIK7hprDWGJp0"&gt;past episodes on YouTube&lt;/a&gt;. The show features guests from AWS teams, talking about service launches, updates, and other news. We also occasionally feature special guests from outside AWS to discuss industry topics.&lt;/p&gt;

&lt;p&gt;During the broadcast from the recent &lt;a href="https://remars.amazonevents.com/"&gt;Amazon re:MARS conference&lt;/a&gt; I teamed up with fellow AWS on Air host AM Grobelny, a Solutions Architect at AWS (Twitter: @amsxbg), to start a new series of informal, developer-oriented segments we're calling &lt;strong&gt;&lt;em&gt;AMster &amp;amp; the Brit's Code Corner&lt;/em&gt;&lt;/strong&gt;. We were hoping to do more at the AWS Summit in New York but sadly I had to skip the event. We'll be back with more in the future so in this post, I thought I'd highlight the three initial videos from re:MARS that have now been uploaded to YouTube.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using AI Services from your Code - Amazon Rekognition
&lt;/h2&gt;

&lt;p&gt;When we think of AWS services, we tend to focus on using them from code we've deployed to the cloud. However, they can equally well be used from code running locally on our machines. In this segment, we look at how I use &lt;a href="https://aws.amazon.com/rekognition/"&gt;Amazon Rekognition&lt;/a&gt; to support tagging images in my photography hobby, so that I can upload them to stock galleries. I'm somewhat lazy, so leaning on a service to do the basic tagging for me is an ideal scenario! The segment also takes a look at how using the various language SDKs that AWS provides (in this case for .NET) makes calling services super-easy.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/SHf2ue2pmek"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Tour of the AWS IDE Toolkits
&lt;/h2&gt;

&lt;p&gt;Did you know that AWS provides a number of freely available integrations with common Integrated Development Environments (IDEs)? In our second segment, we take a quick look at three integration, called toolkits, for Visual Studio, Visual Studio Code, and JetBrains Rider. While Visual Studio and Rider are focused on .NET development, the VS Code toolkit can also be used by developers working in other languages such as Typescript. By the way, you can find details on the full collection of free tools from AWS, for .NET development, &lt;a href="https://aws.amazon.com/developer/language/net/tools/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/P_Ojw_NlJyw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Using AI Services from your Code - Amazon Polly and Amazon Transcribe
&lt;/h2&gt;

&lt;p&gt;In this final segment from the show, AM takes the demo hot seat to showcase a text-to-audio, audio-to-text sample he created, using &lt;a href="https://aws.amazon.com/polly/"&gt;Amazon Polly&lt;/a&gt; and &lt;a href="https://aws.amazon.com/transcribe/"&gt;Amazon Transcribe&lt;/a&gt;, that again uses the AWS SDKs to handle the work of calling services from your code (secretly, he loves C# and PowerShell...don't tell anyone).&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/yGvEhVcfPGk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We had a lot of fun recording these sessions, and are looking forward to more AMster &amp;amp; the Brit Code Corner sessions in future shows. If you've ideas on things you'd like to see covered, let us know on Twitter or in comments!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Finding your way around the AWS Tools for PowerShell</title>
      <dc:creator>Steve Roberts</dc:creator>
      <pubDate>Thu, 17 Feb 2022 00:59:43 +0000</pubDate>
      <link>https://dev.to/steveroberts/finding-your-way-around-the-aws-tools-for-powershell-6a9</link>
      <guid>https://dev.to/steveroberts/finding-your-way-around-the-aws-tools-for-powershell-6a9</guid>
      <description>&lt;p&gt;I’ve been writing and presenting on the &lt;a href="https://aws.amazon.com/powershell/" rel="noopener noreferrer"&gt;AWS Tools for PowerShell&lt;/a&gt; recently and, out of curiosity, decided to take a look at how many cmdlets the tools now support. Back when we shipped version 1, in 2013, the (then single) module contained around 500 cmdlets spanning 20 or so services.&lt;/p&gt;

&lt;p&gt;Having maintained and enhanced the tools for several years, and seen them grow to support the expanding set of services for AWS, I was however somewhat unprepared for the scale of what I found. On a new EC2 instance, running Windows Server 2019 with the monolithic &lt;a href="https://www.powershellgallery.com/packages/AWSPowerShell/" rel="noopener noreferrer"&gt;AWSPowerShell module&lt;/a&gt; pre-installed (like all Windows images provided by the EC2 team), &lt;code&gt;Get-Command&lt;/code&gt; reported over 11000 cmdlets!&lt;/p&gt;

&lt;p&gt;When I moved on from being the lead developer a couple years back, the tools had grown to over 5000 cmdlets, and expansion has obviously continued. Back then, we broke our ability to publish the monolithic modules (AWSPowerShell and &lt;a href="https://www.powershellgallery.com/packages/AWSPowerShell.NetCore/" rel="noopener noreferrer"&gt;AWSPowerShell.NetCore&lt;/a&gt;) to the PowerShell Gallery a couple times, as we’d stumbled over a previously undocumented limit on the number of cmdlet names you could include in the module manifest. In order to publish, we had to remove the cmdlet names from the module manifests, which is why tab-completion of cmdlet names failed periodically every time we broke through the limit.&lt;/p&gt;

&lt;p&gt;Although the PowerShell team did us a favor and raised the limit a few times, allowing us to reinstate the exported names, it was clear this wasn’t going to be a workable approach going forward. The sheer size of the monolithic modules was why the AWS team refactored to per-service modules, known as &lt;a href="https://www.powershellgallery.com/packages?q=AWS.Tools.*" rel="noopener noreferrer"&gt;AWS.Tools.*&lt;/a&gt;, on the gallery. The per-service modules load faster, always have tab completion for cmdlet names, and offer the additional benefit of not having to have all 11000+ cmdlets installed if you're only working with a handful of services.&lt;/p&gt;

&lt;p&gt;All editions of the tools (AWSPowerShell, AWSPowerShell.NetCore, and AWS.Tools.*) contain the same overall set of cmdlets – it’s just a packaging variation. The monolithic modules are kept around for backwards compatibility as there are scripts out there that import the modules from their pre-installed location on the EC2 instances.&lt;/p&gt;

&lt;p&gt;So, this all begs a question – given over 11000 cmdlets spanning 200 services, how do you find your way around? How do you locate the cmdlets for a given service and, within that service, how do you find the cmdlet you need for a given service API? (Pretty much all of the cmdlets map 1:1 with AWS service APIs.) Let’s take a look.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AWS cmdlet naming convention
&lt;/h2&gt;

&lt;p&gt;When we originally created the tools, we had one module but needed to find a way to distinguish cmdlets between different services that used the same underlying API name. For example, several services have an API named &lt;code&gt;DescribeInstances&lt;/code&gt; (EC2 being one such service). This would ordinarily map to a cmdlet name like &lt;code&gt;Get-Instance&lt;/code&gt;, but only one service could then use this in our single module, unless we add a parameter to distinguish the service. That’s not the route we took. Instead, we chose to prefix the noun portion of the cmdlet name with a short 2-5 letter "tag" identifying the parent service – so &lt;code&gt;Get-EC2Instance&lt;/code&gt;, &lt;code&gt;Get-GMLInstance&lt;/code&gt; (GameLift), and &lt;code&gt;Get-OPSInstance&lt;/code&gt; (OpsWorks), for example.&lt;/p&gt;

&lt;p&gt;The prefix tags for some services are obvious – S3, EC2, ECS, etc., while others are less so. Interestingly, when we started to look at refactoring to per-service modules and asked our user base about potentially dropping the convention we got a 50/50 split between keeping and dropping the prefix. Ensuring backwards compatibility also played a large part in our decision making to keep the prefix.&lt;/p&gt;

&lt;p&gt;The prefix tags allow us to isolate cmdlets for a service, but how to find the tag used for a service? The cmdlets contain a custom version cmdlet, &lt;code&gt;Get-AWSPowerShellVersion&lt;/code&gt; (I don’t recall why we added this now, but it has its uses). When invoked with the &lt;code&gt;-ListServiceVersionInfo&lt;/code&gt; parameter, one of the data items it outputs is the noun prefix:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnal1dalp3ck35e1bicku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnal1dalp3ck35e1bicku.png" alt="Listing service prefixes with Get-AWSPowerShellVersion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have the AWS.Tools modules installed, so you can also see the corresponding per-service module. If you’re using the monolithic versions, you’ll see AWSPowerShell or AWSPowerShell.NetCore in that third column. For the AWS.Tools edition, the version cmdlet reports the prefix tags of all supported services, even if you don’t have the module for a particular service installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding cmdlets for a service
&lt;/h2&gt;

&lt;p&gt;Now that we know how to identify the prefix tag for a service, how do you list the cmdlets for that service? For the AWS.Tools modules, &lt;code&gt;Get-Command&lt;/code&gt; will suffice if you have the corresponding module installed (for example, &lt;code&gt;Get-Command -Module AWS.Tools.EC2&lt;/code&gt;). This isn’t so clear-cut with the monolithic modules however, and only works with the per-service modules if the module is installed.&lt;/p&gt;

&lt;p&gt;Back in the mists of time we added the cmdlet &lt;code&gt;Get-AWSCmdletName&lt;/code&gt; to help with discovery. This cmdlet takes advantage of additional metadata that we compile into every cmdlet, enabling users to slice-and-dice the set of cmdlets based on service, API, and AWS CLI command.&lt;/p&gt;

&lt;p&gt;To find the set of cmdlets for a service, use the &lt;code&gt;-Service&lt;/code&gt; parameter. This nets you the set of cmdlets, and corresponding APIs (as well as service name and module name, not shown in this example), for the specified service. Again, with the AWS.Tools modules, this works even if you don’t have the corresponding service module installed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wopb3jt9hwb4edk3eem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wopb3jt9hwb4edk3eem.png" alt="Listing cmdlets by service prefix"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: all the images in this post use a Windows command line prompt, however all these commands can be used on Linux and macOS too. For those of you that might be unaware, PowerShell is no longer tied to Windows!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you don’t know the service tag, you can use all or part of the service name to get the same kind of result. However, as some services use similar words (“elastic” for example) this can be a little less focused:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fci61f1vj3q8v9iqd3yae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fci61f1vj3q8v9iqd3yae.png" alt="Listing cmdlets using service name words"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-Service&lt;/code&gt; parameter can also be used with multiple words (&lt;code&gt;-Service "Compute Cloud"&lt;/code&gt;) and also supports a &lt;code&gt;-MatchWithRegex&lt;/code&gt; parameter. Note that the &lt;code&gt;-MatchWithRegex&lt;/code&gt; parameter automatically surrounds the regex you provide with &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; terms. Also, in the examples I'm showing in this post I’m using mixed case, but case is actually insignificant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding cmdlets that implement a service API
&lt;/h2&gt;

&lt;p&gt;Having found cmdlets that map to a service, how about the inverse – finding cmdlets that map to AWS service APIs? This is useful for developers used to working with one of the AWS SDKs and who have some familiarity with the underlying services. As an ex-SDK developer, this is my usual go-to approach especially as I’m usually starting from a service’s API reference material.&lt;/p&gt;

&lt;p&gt;To map from API to cmdlet, use the&lt;code&gt;-ApiOperation&lt;/code&gt; parameter to &lt;code&gt;Get-AWSCmdletName&lt;/code&gt;, and provide the name of the API (case is not significant):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0pv9yfuoxqp1d3c7vm06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0pv9yfuoxqp1d3c7vm06.png" alt="Looking for a cmdlet by API name"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to restrict the search to a particular service, add the &lt;code&gt;-Service&lt;/code&gt; parameter. Modifying the example above to be &lt;code&gt;Get-AWSCmdletName -ApiOperation -Service EC2&lt;/code&gt; would output only one result.&lt;/p&gt;

&lt;p&gt;You can also perform a regex-based search with &lt;code&gt;-ApiOperation&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F421374bjif4ostxdofvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F421374bjif4ostxdofvi.png" alt="Looking for a cmdlet by operation and regex"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping from an AWS CLI command to a cmdlet
&lt;/h2&gt;

&lt;p&gt;It’s no secret that there are more help examples available for the &lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; than PowerShell. &lt;code&gt;Get-AWSCmdletName&lt;/code&gt; has an additional mode, invoked with the &lt;code&gt;-AwsCliCommand&lt;/code&gt; parameter, that parses a CLI command to output the name of the corresponding cmdlet. This is based on how the CLI command hyphenates the service API name so might not be 100% correct if this convention ever changes. To use it, take the CLI command with the service indicator and operation name and pass it to the cmdlet. You don’t need the initial "aws" component in the CLI command example, and if you pass it, it’s ignored. For example, below I could also have specified &lt;code&gt;-AwsCliCommand "aws ec2 describe-instances"&lt;/code&gt; to achieve the same result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6u7qgdi3k0axklepl9ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6u7qgdi3k0axklepl9ck.png" alt="Looking up a cmdlet from CLI command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the deprecation warning; personally, I hope this feature isn’t ever removed but, since it’s based on a naming convention rather than internal metadata added when the cmdlets are built, it’s possible it could fall victim to a future change.&lt;/p&gt;

&lt;p&gt;The CLI mode doesn’t translate parameters (and ignores them if you include them in the parameter value). Since the CLI and the PowerShell cmdlets map to AWS service APIs, I usually head to the &lt;a href="https://docs.aws.amazon.com/powershell/latest/reference/Index.html" rel="noopener noreferrer"&gt;cmdlet reference&lt;/a&gt;, or the respective service reference documentation, at that point.&lt;/p&gt;

&lt;p&gt;With over 11000 cmdlets available, the tools provide a lot of scope to work with AWS services and resources from PowerShell, but finding out what’s available can be daunting. Hopefully, from this post you can now see how you can find your way around to become productive quickly.&lt;/p&gt;

&lt;p&gt;Happy scripting!&lt;/p&gt;

&lt;h2&gt;
  
  
  Addendum
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/vladhrybok/" rel="noopener noreferrer"&gt;AWS Solutions Architect Vlad Hrybok&lt;/a&gt; informed me of a site that you might also find useful when mapping from AWS CLI commands to the PowerShell cmdlet, &lt;a href="https://aws-cli-eq-pwsh.shibata.tech/" rel="noopener noreferrer"&gt;AWS CLI -eq PowerShell&lt;/a&gt;. Clicking entries in the Services column takes you to the product details, whereas clicking in the CLI command column takes you to a command/cmdlet list for that service. Super useful!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>powershell</category>
    </item>
    <item>
      <title>Working with Amazon S3 presigned URLs in Visual Studio</title>
      <dc:creator>Steve Roberts</dc:creator>
      <pubDate>Mon, 07 Feb 2022 16:58:00 +0000</pubDate>
      <link>https://dev.to/steveroberts/working-with-amazon-s3-presigned-urls-in-visual-studio-2h9d</link>
      <guid>https://dev.to/steveroberts/working-with-amazon-s3-presigned-urls-in-visual-studio-2h9d</guid>
      <description>&lt;p&gt;I noticed a little &lt;a href="https://twitter.com/steffeng/status/1486617289952837636" rel="noopener noreferrer"&gt;flurry of interest on Twitter recently&lt;/a&gt;, after it was noticed that it’s now possible to create presigned URLs for objects in Amazon S3 storage buckets using the AWS Management Console. Whilst it’s always cool to get a positive reaction for customer-facing enhancements, I do recall thinking to myself “Pffft, .NET developers using the AWS Toolkit for Visual Studio have had this ability since v1 of our toolkit, back in 2011!”.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's a "presigned URL"?
&lt;/h2&gt;

&lt;p&gt;Objects (files) in an S3 storage bucket are private by default. While you could share objects in a bucket by relaxing permissions, on either specific objects or the entire bucket. this really isn’t a good practice. Who knows what data you’ll put into the bucket in future, having forgotten that you relaxed permissions….and now you have a data leak ☹.&lt;/p&gt;

&lt;p&gt;A presigned URL is simply a generated, time-limited URL to an object that you can use to share the object with others. When you create a presigned URL, you supply the bucket and object name (the object key, in AWS parlance), the allowed HTTP method, an expiration date and time, and your security credentials (which can be temporary, token-based time limited credentials too). If you use token-based credentials, the link expires when the token expires, even if this is earlier than the requested link duration. The maximum duration you can request for a presigned URL is 7 days.&lt;/p&gt;

&lt;p&gt;The URL you get back can be shared with others, who can then access the object – even it is otherwise private. Therefore, you should obviously be careful when sharing them. Presigned URLs can be used to download, upload, and delete objects depending on the method encoded in the URL. I use presigned URLs frequently, to share large files with colleagues. This allows me to follow best practices and otherwise keep all my buckets and objects in them private and accessible only by me.&lt;/p&gt;

&lt;p&gt;Essentially, a presigned URL contains a bearer token whose permissions are scoped by the permissions granted to whoever (or whatever) created the URL. This means a presigned URL grants no additional permissions to the consumer beyond those of the creator. For example, if my account doesn’t have permission to get (download) an object, a presigned URL I create, with HTTP method GET, to the object will fail to work - for me and anyone I share it with. It’s also possible to further restrict usage of a presigned URL to specific network paths, although not when creating them through the toolkits. See &lt;strong&gt;Limiting presigned URL capabilities&lt;/strong&gt; in the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html" rel="noopener noreferrer"&gt;S3 user guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AWS Toolkit for Visual Studio
&lt;/h2&gt;

&lt;p&gt;Ok, now to the toolkit. If you’ve not seen or heard of it the toolkit is a free extension, available on the Visual Studio marketplace, that supports &lt;a href="https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2017" rel="noopener noreferrer"&gt;Visual Studio 2017/2019&lt;/a&gt;, and &lt;a href="https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2022" rel="noopener noreferrer"&gt;Visual Studio 2022&lt;/a&gt; (there’s also a &lt;a href="https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-toolkit-vscode" rel="noopener noreferrer"&gt;free toolkit for Visual Studio Code&lt;/a&gt;, which also supports creating presigned URLs without leaving the editor).&lt;/p&gt;

&lt;p&gt;The Visual Studio toolkit surfaces an explorer window in the IDE that allows you to work with several AWS services (S3, EC2, DynamoDB, Lambda, et al) from within the convenience of the IDE and not have to jump out to a web browser to use the console, and several wizards making it easy to deploy code to Elastic Beanstalk (web apps), Lambda (serverless functions and apps), Elastic Container Service, and CloudFormation (infrastructure as code).&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating a presigned URL using the toolkit
&lt;/h2&gt;

&lt;p&gt;To create a presigned URL to an object using the toolkit, you first need to open a view onto the bucket’s objects. From the AWS Explorer window (View -&amp;gt; AWS Explorer, if the window’s not visible), expand the Amazon S3 hierarchy and locate the entry for the bucket. Double-click it, or use the context menu, to open the view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyfo28gkezp8yfutuo29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsyfo28gkezp8yfutuo29.png" alt="Opening an S3 bucket view in Visual Studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although S3 is a flat object store, not a hierarchical file system, the toolkit’s organizes objects in the bucket into “folders”, parsing on the / character in the object keys. So, you might need to traverse down one or more of those simulated folders to find the object you want. Let's generate a presignedURL for an object at the root of the bucket.&lt;/p&gt;

&lt;p&gt;To do that, right-click on the object to share a link to and select “Create Pre-Signed URL…”:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt4atgb48d9jj3gi6764.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt4atgb48d9jj3gi6764.png" alt="S3 object context menu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the resulting dialog I select the HTTP method (GET or PUT, the toolkit doesn’t support DELETE) and the expiry date and time. I can also set a content-type if needed. Then I click the Generate button to obtain the URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf90yupytr1kxruaudqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf90yupytr1kxruaudqr.png" alt="Presigned URL dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Copy button copies the generated URL to the clipboard. I can now click OK to dismiss the dialog and head back to whatever content I was writing (email, etc.), and paste the link. The recipient will then be able to use the link to download the object, even though it’s private, until 12PM on Feb 7th in this example.&lt;/p&gt;

&lt;p&gt;If you’re a Visual Studio Code user, you can do this too but the process is a little different. In the VS Code toolkit, you navigate objects in a bucket directly within the explorer pane. The context menu on an object shows a command to generate a URL (here I’m going to generate a URL for an object in a “subfolder“):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i3frc5euxm5cx6w2e7s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i3frc5euxm5cx6w2e7s.png" alt="Generating a presigned URL in Visual Studio Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’re then asked for the validity duration, in minutes – there’s no dialog as in Visual Studio, and you can’t change the method (it’s always GET) or set a custom content type:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4b8vu6mkpmazr79yuwmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4b8vu6mkpmazr79yuwmc.png" alt="Setting the expiry in Visual Studio Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press Enter once you’ve set the duration and the toolkit creates a URL, automatically copies it to the clipboard, ready to paste elsewhere. It also displays confirmation in the status bar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gdnryo62wv8i7bhcpmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gdnryo62wv8i7bhcpmd.png" alt="Status bar confirmation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I don’t use an IDE toolkit, now what?
&lt;/h2&gt;

&lt;p&gt;To wrap up the post, I wanted to note that it’s also possible to create presigned URLs from command line tools. Using the &lt;a href="https://aws.amazon.com/powershell/" rel="noopener noreferrer"&gt;AWS Tools for PowerShell&lt;/a&gt;, the &lt;code&gt;Get-S3PresignedUrl&lt;/code&gt; is what you need &lt;a href="https://docs.aws.amazon.com/powershell/latest/reference/items/Get-S3PreSignedURL.html" rel="noopener noreferrer"&gt;(docs here)&lt;/a&gt;. It accepts a &lt;code&gt;DateTime&lt;/code&gt; object to set the expiry, defaulting to HTTPS protocol and GET as the method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-S3PresignedUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-BucketName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;steve-presignedurldemo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotNETAndPowerShellOnAWSBoothVideo2021.mp4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Expire&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2022-02-06&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;https://steve-presignedurldemo.s3.us-west-2.amazonaws.com/dotNETAndPowerShellOnAWSBoothVideo2021.mp4&lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;X-Amz-Expires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;217008&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;X-Amz-Algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AWS4-HMAC-SHA256&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;X-Amz-Credential&lt;/span&gt;&lt;span class="o"&gt;=...&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;X-Amz-SignedHeaders&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;X-Amz-Signature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5982&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;fb4b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use the &lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt;, you use &lt;code&gt;aws s3 presign&lt;/code&gt; &lt;a href="https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/presign.html" rel="noopener noreferrer"&gt;(docs here)&lt;/a&gt;, which defaults to a one-hour expiry if not specified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 presign s3://steve-presignedurldemo/dotNETAndPowerShellOnAWSBoothVideo2021.mp4
https://steve-presignedurldemo.s3.us-west-2.amazonaws.com/dotNETAndPowerShellOnAWSBoothVideo2021.mp4?X-Amz-Algorithm&lt;span class="o"&gt;=&lt;/span&gt;AWS4-HMAC-SHA256&amp;amp;X-Amz-Credential&lt;span class="o"&gt;=&lt;/span&gt;...&amp;amp;X-Amz-Expires&lt;span class="o"&gt;=&lt;/span&gt;3600&amp;amp;X-Amz-SignedHeaders&lt;span class="o"&gt;=&lt;/span&gt;host&amp;amp;X-Amz-Signature&lt;span class="o"&gt;=&lt;/span&gt;f7b8...29bdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Happy presigning – and welcome to the party, (management console) pal!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
    </item>
    <item>
      <title>Retrieve Amazon EC2 instance tags using PowerShell</title>
      <dc:creator>Steve Roberts</dc:creator>
      <pubDate>Wed, 12 Jan 2022 20:04:04 +0000</pubDate>
      <link>https://dev.to/steveroberts/retrieve-amazon-ec2-instance-tags-using-powershell-501i</link>
      <guid>https://dev.to/steveroberts/retrieve-amazon-ec2-instance-tags-using-powershell-501i</guid>
      <description>&lt;p&gt;In a &lt;a href="https://aws.amazon.com/about-aws/whats-new/2022/01/instance-tags-amazon-ec2-instance-metadata-service/" rel="noopener noreferrer"&gt;recent What’s New post&lt;/a&gt;, the &lt;a href="https://aws.amazon.com/ec2/" rel="noopener noreferrer"&gt;Amazon EC2&lt;/a&gt; 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 &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html" rel="noopener noreferrer"&gt;EC2’s &lt;code&gt;RunInstances&lt;/code&gt; API&lt;/a&gt;, or other tooling you might use to launch instances, such as &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt; templates. Below is a screenshot of the management console option to in the EC2 launch wizard to make tag data available on the instance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrlz50n663a4itfkr7ud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrlz50n663a4itfkr7ud.png" alt="Enabling tags in instance metadata using the management console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html" rel="noopener noreferrer"&gt;instance metadata categories topic in the EC2 User Guide&lt;/a&gt; details the kinds of data available from IMDS on an instance, which can be queried on the instance without needing credentials.&lt;/p&gt;

&lt;p&gt;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. &lt;a href="https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html" rel="noopener noreferrer"&gt;Tagging resources is considered a best practice&lt;/a&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading tags in the "before times"
&lt;/h2&gt;

&lt;p&gt;Prior to EC2 making tags available from IMDS, to access them from code running on an instance you needed to make use of &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeTags.html" rel="noopener noreferrer"&gt;EC2’s &lt;code&gt;DescribeTags&lt;/code&gt; API&lt;/a&gt;. This API is exposed by the &lt;a href="https://docs.aws.amazon.com/powershell/latest/reference/items/Get-EC2Tag.html" rel="noopener noreferrer"&gt;Get-EC2Tag cmdlet&lt;/a&gt; in the &lt;a href="https://aws.amazon.com/powershell/" rel="noopener noreferrer"&gt;AWS Tools for PowerShell&lt;/a&gt; modules.&lt;/p&gt;

&lt;p&gt;You’ll find the monolithic &lt;em&gt;AWSPowerShell&lt;/em&gt; module pre-installed on Windows images provided by Amazon EC2. For Linux and macOS there's an equivalent cross-platform (and also monolithic) &lt;em&gt;AWSPowerShell.NetCore&lt;/em&gt; 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 (&lt;em&gt;AWS.Tools.*&lt;/em&gt;) 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 &lt;em&gt;AWS.Tools.EC2&lt;/em&gt;, &lt;em&gt;AWS.Tools.S3&lt;/em&gt;, 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.&lt;/p&gt;

&lt;p&gt;The example below shows how I can retrieve the tags attached to a given instance using &lt;code&gt;Get-EC2Tag&lt;/code&gt; (this code runs on the instance):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-EC2Tag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"resource-type"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"instance"&lt;/span&gt;&lt;span class="p"&gt;},@{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"resource-id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;" i-0f53ee144573513c5"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;ResourceId&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;ResourceType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;---&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;App1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;App1Project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Purpose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;Dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Stage&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;Beta&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Team&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;WinDev&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output echoes the five keys, &lt;em&gt;Name&lt;/em&gt;, &lt;em&gt;Project&lt;/em&gt;, &lt;em&gt;Purpose&lt;/em&gt;, &lt;em&gt;Stage&lt;/em&gt;, and &lt;em&gt;Team&lt;/em&gt;, 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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;DescribeTags&lt;/code&gt; 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 &lt;code&gt;DescribeTags&lt;/code&gt; API. This isn’t so bad, as launching instances with an attached role is a fairly standard practice.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving the instance ID
&lt;/h3&gt;

&lt;p&gt;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 (&lt;em&gt;&lt;a href="http://169.254.169.254" rel="noopener noreferrer"&gt;http://169.254.169.254&lt;/a&gt;&lt;/em&gt; in IPv4, or &lt;em&gt;http://[fd00:ec2::254]&lt;/em&gt; in IPv6) for data. However, there’s a simpler way using the &lt;code&gt;Get-EC2InstanceMetadata&lt;/code&gt; cmdlet, present in all variants of the AWS Tools for PowerShell modules (in the modular variant, you’ll find it in the &lt;em&gt;AWS.Tools.EC2&lt;/em&gt; module).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Get-EC2InstanceMetadata&lt;/code&gt; handles the work of accessing the underlying IMDS endpoint for you, and outputs the requested metadata. The cmdlet accepts a &lt;code&gt;-Category&lt;/code&gt; parameter that you use to select which data you want output. There’s also a switch, &lt;code&gt;-ListCategory&lt;/code&gt;, 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).&lt;/p&gt;

&lt;p&gt;For the earlier example, we could rewrite it to use &lt;code&gt;Get-EC2InstanceMetadata&lt;/code&gt; cmdlet with the &lt;code&gt;InstanceId&lt;/code&gt; category value to get the same output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-EC2Tag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"resource-type"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"instance"&lt;/span&gt;&lt;span class="p"&gt;},@{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"resource-id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;EC2InstanceMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InstanceId&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;ResourceId&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nx"&gt;ResourceType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;---&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;App1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;App1Project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Purpose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;Dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Stage&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;Beta&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Team&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i-0f53ee144573513c5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;WinDev&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;h2&gt;
  
  
  Retrieving tags from IMDS
&lt;/h2&gt;

&lt;p&gt;If you run the &lt;code&gt;Get-EC2InstanceMetadata&lt;/code&gt; cmdlet with the &lt;code&gt;-ListCategory&lt;/code&gt; 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:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-EC2InstanceMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ListCategory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;AmiId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;LaunchIndex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ManifestPath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;AncestorAmiId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;BlockDeviceMapping&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;InstanceId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;InstanceType&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;LocalHostname&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;LocalIpv4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;KernelId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;AvailabilityZone&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;ProductCode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;PublicHostname&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;PublicIpv4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;PublicKey&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;RamdiskId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Region&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;ReservationId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;SecurityGroup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;UserData&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;InstanceMonitoring&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;IdentityDocument&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;IdentitySignature&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;IdentityPkcs7&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;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 &lt;em&gt;/tags/instance&lt;/em&gt; path, shown below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-EC2InstanceMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/tags/instance&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Purpose&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Stage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Team&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;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):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-EC2InstanceMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/tags/instance/Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;App1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-EC2InstanceMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/tags/instance/Stage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Beta&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-EC2InstanceMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/tags/instance/Purpose&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Dev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;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 &lt;code&gt;Invoke-RestMethod&lt;/code&gt;, shown in the example below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'169.254.169.254/latest/meta-data/tags/instance'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Purpose&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Stage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Team&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'169.254.169.254/latest/meta-data/tags/instance/Name'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;App1&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;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!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>powershell</category>
      <category>ec2</category>
    </item>
  </channel>
</rss>
