Create a JSON inventory of your AWS resources in PowerShell

A client recently asked how to produce a JSON inventory of all their AWS resources across all their regions and accounts. They wanted to use tools like PowerBI to manage and report on their AWS inventory and wanted JSON output from AWS so it could be consumed easily by users and tools.

Naturally, I looked first at AWS Config. But for a simple JSON inventory for selected AWS accounts, regions and resources, it was more than I needed. So, I decided to write a PowerShell script that could be customized for any number of AWS regions, accounts and services. That script is below. 

Here are some usage notes:

  • This script runs just fine on PowerShell beta 3 on MacOS. 
  • The heart of this script are three hash tables and an array.
    • One hash table contains the names of stored profiles specifying the accounts to be used to retrieve resources (used bySet-AWSCredentials -ProfileName).
    • The array contains strings representing the regions that will be queried for resources.
    • The last two hash table contains the AWSPowerShell.NetCore cmdlets that produce output. There’s one table for global resoures (like S3) and another table for regional resources. Interestingly, these hash tables can contain any combination of AWS cmdlets that produce PSObject output. For example, to list all your EC2 instances, ignoring the top-level ReservationID object, you can specify (Get-Ec2Instance).Instances. You can even pipe output from one cmdlet to another, for example: Get-SNSTopic | Get-SNSTopicAttribute.

All output is sent to ConvertTo-JSON with a -Depth of 10 so everything will be expanded.

I hope you find this useful. I can’t attach actual JSON output since it would contain client-sensitive information. However, here’s a screenshot of the output for an account with identifiers blurred so you can get an idea of the JSON structure for AWS resources.

AWS resource inventory in JSON
AWS resource inventory in JSON (click to enlarge)
<#
    .SYNOPSIS
        Inventory resources by AWS account
    
    .DESCRIPTION
        Creates an array of PSObjects from a list of resources, converts the array to JSON and writes a JSON file to the current user's desktop folder for a list of supplied accounts
    
    .PARAMETER cmdletHT
        A hash table containing key=value pairs in the form [AWS cmdlet] = [Label for service in JSON output]. Ex: "(Get-EC2Instance).Instances" = "EC2"; Note that AWS cmdlet can be a "compound" cmdlet with a pipelined output
    
    .PARAMETER cmdletHTCount
        A count of the number of key=value pairs in cmdletHT
    
    .EXAMPLE
        ./Inventory.ps1
    
    .OUTPUTS
        $HOME/desktop/[AWS account number].json
    
    .NOTES
        Change the arrays and hash tables to produce the outputs desired.
        IAM admin credentials are assumed to be stored in encrypted AWS SDK store on local machine; specify in key=value format, i.e [AWS account number] = [AWS SDK stored profile name]. Example: 12345654321=12345654321-credentials
        Alex Neihaus 2017-06-26
        (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://www.yobyot.com
    
    .INPUTS
        None; alter the script's tables to change desired outputs
#>
function Get-AWSResources
{
    param
    (
        [Parameter(Mandatory = $true)]
        $cmdletHT,
        [Parameter(Mandatory = $true)]
        [int]$cmdletHTCount,
        [Parameter(Mandatory = $true)]
        [string]$location
    )
    
    $cmdletcounter = 1 # Initialize counter used to determine when we're at the end of the list
    foreach ($cmdlet in $cmdletHT.GetEnumerator())
    {
        $o = Invoke-Expression -Command "$($cmdlet.Key)" # The heart of the script; run either a naked cmdlet, a piped cmdlet or return the subcollection of a cmdlet or a combination of all three
        if ((Measure-Object -InputObject $o).Count -gt 0) # At least one object has been returned
        {
            If ($cmdletcounter -le $cmdletHTCount) # Only add comma is there is content to be added to the output and we have not reached the end of the cmdlets in the hash table
            {
                "," | Add-Content $f # Adds comma for next array of JSON objects from next cmdlet
            }
            "`"$location-$($cmdlet.Value)`": " | Add-Content $f # Adds  region-"(service)": to account output file
            $o | ConvertTo-Json -Depth 10 -Compress | Add-Content $f # Convert returned collection to JSON with 10 levels of recursion and add it to the file
        }
        $cmdletcounter++
    }
}
Import-Module AWSPowerShell.NetCore
$regionArray = @(
    "us-east-1",
    "us-east-2",
    "us-west-1",
    "us-west-2"
)
$cmdletHTRegion = [ordered]@{
    # Specify a list of AWS PowerShell cmdlets that retrieve resources you want in your output JSON
    #  These resources ARE specific to regions
    # Hash key is AWSPowerShell cmdlet, cmdlet.property or cmdlet | cmdlet ; Hash value is JSON object name
    "(Get-EC2Instance).Instances" = "EC2";
    "Get-ELBLoadBalancer" = "ELB";
    "Get-ELB2LoadBalancer" = "ELB2";
    "Get-CFNStack" = "CloudFormation";
    "Get-ACMCertificateList" = "Certificate Manager";
    "Get-ECCacheCluster" = "ElasiCache";
    "Get-LMFunctions" = "Lambda";
    "Get-SNSSubscription" = "SNS-Subscription";
    "Get-SNSTopic | Get-SNSTopicAttribute" = "SNS-TopicAttributes";
    "Get-SQSQueue | Get-SQSQueueAttribute" = "SQS";
    "Get-CTTrail" = "CloudTrail";
    "Get-CTTrail | Get-CTTrailStatus" = "CloudTrail-Status"
}
$cmdletHTGlobal = [ordered]@{
    # Specify a list of AWS PowerShell cmdlets that retrieve resources you want in your output JSON
    #  These resources are NOT specific to regions
    "Get-S3Bucket" = "S3";
    "Get-IAMUsers" = "IAMUsers";
    "Get-IAMGroups" = "IAMGroups";
    "Get-IAMRoles" = "IAMRoles";
    "Get-R53HostedZones" = "R53HostedZones"
}
# Hash table mapping account numbers to names of stored profiles of AWS credentials with admin access to each account
$credsHT = [ordered]@{
    "123456654321"="123456654321-credentials";
    "654321123456" = "654321123456-credentials";
}
 
foreach ($cred in $credsHT.GetEnumerator()) # GetEnumerator() returns each hash table key value. Since it's an ordered HT, they are returned in the order listed
{
    $f = New-Item -Path "$HOME\Desktop\$($cred.Key).json" -Force # Create a new file for the current account
    "Processing account $($cred.Key) with stored profile $($cred.Value)"
    Set-AWSCredentials -ProfileName $($cred.Value) # Returns the profile name in the $credsHT. MUST BE the name of a AWS .Net stored credential with admin privileges 
    Set-DefaultAWSRegion -Region us-east-1 # Even though we are going to be obtaining global variables, we should set a region before calling the function
    "{ `"Account`": `"$($cred.Key)`"" | Add-Content $f # Adds {"Account: "123456654321" to account file
    
    # First, add the non-region-specific resources to the output JSON file
    Get-AWSResources -cmdletHT $cmdletHTGlobal -cmdletHTCount ([int]($cmdletHTGlobal | Select-Object -Property count -ExpandProperty count)) -location "Global"
    
    # Next, loop through each resource type in each region to add objects to the output JSON file
    foreach ($region in $regionArray) #Process services for each region
    {
        Set-DefaultAWSRegion -Region $region
        Get-AWSResources -cmdletHT $cmdletHTRegion -cmdletHTCount ([int]($cmdletHTRegion | Select-Object -Property count -ExpandProperty count)) -location $region
    }
    "}" | Add-Content $f # Adds closing } to account output file
} # All done

 


Posted

in

, , ,

by

Comments

Leave a Reply

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