Azure NAT gateways: export subnet associations

Overview

This PowerShell script searches all enabled Azure subscriptions available to the current account, inventories every NAT Gateway, and maps each one to the virtual networks and subnets associated with it.

It exports both:

  • NAT Gateways that are attached to subnets.
  • NAT Gateways that exist but have no associated subnet.

This is useful when you need to quickly identify:

  • Which NAT Gateways are deployed in each subscription.
  • Which VNets and subnets are using each NAT Gateway.
  • Which NAT Gateways are currently orphaned.
  • Which public IPs or public IP prefixes are attached to each NAT Gateway.

What the script does

  • Retrieves enabled subscriptions with Get-AzSubscription.
  • Switches context into each subscription with Set-AzContext.
  • Reads NAT Gateways with Get-AzNatGateway.
  • Reads VNets with Get-AzVirtualNetwork.
  • Checks each subnet for an associated NAT Gateway.
  • Builds a lookup by NAT Gateway resource ID to match subnet associations reliably.
  • Exports associated and unassociated NAT Gateways to CSV.
  • Prints a summary table in the terminal.

Prerequisites

Install the Azure PowerShell modules if needed:

Install-Module Az -Scope CurrentUser

Connect to Azure before running the script:

Connect-AzAccount

The account must have permission to read NAT Gateways, virtual networks, and subnets in the subscriptions being checked.

Output

The CSV is written to:

$HOME/nat-gateway-inventory.csv

Each row includes:

  • SubscriptionName
  • SubscriptionId
  • NatGatewayName
  • NatGatewayResourceGroup
  • NatGatewayLocation
  • NatGatewaySku
  • NatGatewayId
  • VNetName
  • VNetResourceGroup
  • VNetLocation
  • VNetAddressSpace
  • VNetId
  • SubnetName
  • SubnetAddressSpace
  • SubnetId
  • PublicIpIds
  • PublicIpPrefixIds

For NAT Gateways with no subnet association, the script writes NO ASSOCIATED SUBNET in SubnetName.

PowerShell script

$OutputFile = "$HOME/nat-gateway-inventory.csv"
 
$Results = @()
 
$Subscriptions = Get-AzSubscription |
    Where-Object { $_.State -eq "Enabled" }
 
foreach ($Subscription in $Subscriptions) {
 
    Write-Host ""
    Write-Host "Processing subscription: $($Subscription.Name) [$($Subscription.Id)]" -ForegroundColor Cyan
 
    try {
        Set-AzContext -SubscriptionId $Subscription.Id -ErrorAction Stop | Out-Null
    }
    catch {
        Write-Warning "Unable to switch to subscription $($Subscription.Name)"
        continue
    }
 
    try {
        $NatGateways = @(Get-AzNatGateway -ErrorAction Stop)
    }
    catch {
        Write-Warning "Unable to retrieve NAT Gateways: $($_.Exception.Message)"
        continue
    }
 
    if ($NatGateways.Count -eq 0) {
        Write-Host "  No NAT Gateways found." -ForegroundColor DarkGray
        continue
    }
 
    Write-Host "  NAT Gateways found: $($NatGateways.Count)" -ForegroundColor Green
 
    $NatGatewayLookup = @{}
 
    foreach ($NatGateway in $NatGateways) {
 
        $NormalizedId = $NatGateway.Id.ToLowerInvariant()
 
        $NatGatewayLookup[$NormalizedId] = $NatGateway
 
        Write-Host "    $($NatGateway.Name)" -ForegroundColor Yellow
    }
 
    try {
        $VNets = @(Get-AzVirtualNetwork -ErrorAction Stop)
    }
    catch {
        Write-Warning "Unable to retrieve VNets: $($_.Exception.Message)"
        continue
    }
 
    $AssociatedNatGatewayIds = @{}
 
    foreach ($VNet in $VNets) {
 
        $VNetAddressSpace = ""
 
        if ($VNet.AddressSpace.AddressPrefixes) {
            $VNetAddressSpace = (
                $VNet.AddressSpace.AddressPrefixes |
                Sort-Object
            ) -join "; "
        }
 
        foreach ($Subnet in $VNet.Subnets) {
 
            $SubnetNatGatewayId = $null
 
            if ($Subnet.NatGateway) {
 
                if ($Subnet.NatGateway.Id) {
                    $SubnetNatGatewayId = $Subnet.NatGateway.Id
                }
                elseif ($Subnet.NatGateway -is [string]) {
                    $SubnetNatGatewayId = $Subnet.NatGateway
                }
            }
 
            if ([string]::IsNullOrWhiteSpace($SubnetNatGatewayId)) {
                continue
            }
 
            $NormalizedNatId = $SubnetNatGatewayId.ToLowerInvariant()
 
            if (-not $NatGatewayLookup.ContainsKey($NormalizedNatId)) {
                Write-Warning "Subnet NAT Gateway was not found in lookup: $SubnetNatGatewayId"
                continue
            }
 
            $NatGateway = $NatGatewayLookup[$NormalizedNatId]
 
            $AssociatedNatGatewayIds[$NormalizedNatId] = $true
 
            $SubnetAddressSpace = ""
 
            if ($Subnet.AddressPrefixes -and $Subnet.AddressPrefixes.Count -gt 0) {
 
                $SubnetAddressSpace = (
                    $Subnet.AddressPrefixes |
                    Sort-Object
                ) -join "; "
 
            }
            elseif ($Subnet.AddressPrefix) {
 
                $SubnetAddressSpace = $Subnet.AddressPrefix
            }
 
            Write-Host "      $($NatGateway.Name) -> $($VNet.Name)/$($Subnet.Name)" -ForegroundColor Green
 
            $PublicIpIds = ""
 
            if ($NatGateway.PublicIpAddresses) {
                $PublicIpIds = (
                    $NatGateway.PublicIpAddresses |
                    ForEach-Object { $_.Id }
                ) -join "; "
            }
 
            $PublicIpPrefixIds = ""
 
            if ($NatGateway.PublicIpPrefixes) {
                $PublicIpPrefixIds = (
                    $NatGateway.PublicIpPrefixes |
                    ForEach-Object { $_.Id }
                ) -join "; "
            }
 
            $Results += [PSCustomObject]@{
 
                SubscriptionName         = $Subscription.Name
                SubscriptionId           = $Subscription.Id
 
                NatGatewayName           = $NatGateway.Name
                NatGatewayResourceGroup  = $NatGateway.ResourceGroupName
                NatGatewayLocation       = $NatGateway.Location
                NatGatewaySku            = $NatGateway.Sku.Name
                NatGatewayId             = $NatGateway.Id
 
                VNetName                 = $VNet.Name
                VNetResourceGroup        = $VNet.ResourceGroupName
                VNetLocation             = $VNet.Location
                VNetAddressSpace         = $VNetAddressSpace
                VNetId                   = $VNet.Id
 
                SubnetName               = $Subnet.Name
                SubnetAddressSpace       = $SubnetAddressSpace
                SubnetId                 = $Subnet.Id
 
                PublicIpIds              = $PublicIpIds
                PublicIpPrefixIds        = $PublicIpPrefixIds
            }
        }
    }
 
    foreach ($NatGateway in $NatGateways) {
 
        $NormalizedId = $NatGateway.Id.ToLowerInvariant()
 
        if (-not $AssociatedNatGatewayIds.ContainsKey($NormalizedId)) {
 
            Write-Host "      $($NatGateway.Name) -> NO ASSOCIATED SUBNET" -ForegroundColor DarkYellow
 
            $PublicIpIds = ""
 
            if ($NatGateway.PublicIpAddresses) {
                $PublicIpIds = (
                    $NatGateway.PublicIpAddresses |
                    ForEach-Object { $_.Id }
                ) -join "; "
            }
 
            $PublicIpPrefixIds = ""
 
            if ($NatGateway.PublicIpPrefixes) {
                $PublicIpPrefixIds = (
                    $NatGateway.PublicIpPrefixes |
                    ForEach-Object { $_.Id }
                ) -join "; "
            }
 
            $Results += [PSCustomObject]@{
 
                SubscriptionName         = $Subscription.Name
                SubscriptionId           = $Subscription.Id
 
                NatGatewayName           = $NatGateway.Name
                NatGatewayResourceGroup  = $NatGateway.ResourceGroupName
                NatGatewayLocation       = $NatGateway.Location
                NatGatewaySku            = $NatGateway.Sku.Name
                NatGatewayId             = $NatGateway.Id
 
                VNetName                 = ""
                VNetResourceGroup        = ""
                VNetLocation             = ""
                VNetAddressSpace         = ""
                VNetId                   = ""
 
                SubnetName               = "NO ASSOCIATED SUBNET"
                SubnetAddressSpace       = ""
                SubnetId                 = ""
 
                PublicIpIds              = $PublicIpIds
                PublicIpPrefixIds        = $PublicIpPrefixIds
            }
        }
    }
}
 
if ($Results.Count -gt 0) {
 
    $Results |
        Sort-Object SubscriptionName, NatGatewayName, VNetName, SubnetName |
        Export-Csv `
            -Path $OutputFile `
            -NoTypeInformation `
            -Encoding UTF8
 
    Write-Host ""
    Write-Host "==============================================" -ForegroundColor Green
    Write-Host "EXPORT COMPLETE" -ForegroundColor Green
    Write-Host "==============================================" -ForegroundColor Green
    Write-Host "Rows exported : $($Results.Count)"
    Write-Host "File          : $OutputFile"
    Write-Host ""
 
    $Results |
        Sort-Object SubscriptionName, NatGatewayName, VNetName, SubnetName |
        Format-Table `
            SubscriptionName,
            NatGatewayName,
            VNetName,
            VNetAddressSpace,
            SubnetName,
            SubnetAddressSpace `
            -AutoSize
}
else {
 
    Write-Warning "No NAT Gateway records were found. No CSV was created."
}

Notes

  • The script matches subnet associations by normalized resource ID, which is safer than matching only by name.
  • It records orphaned NAT Gateways explicitly so the export doubles as a cleanup inventory.
  • If a subscription context switch or resource query fails, the script warns and continues with the next subscription.
  • If you want a reusable variant, move the output path into a parameter.