Private/Split-AzureResourceId.ps1

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

<#
    .SYNOPSIS
    Parses an Azure resource ID and returns an object that includes resource details.
 
    .PARAMETER Id
    Azure resource ID to parse.
 
    .DESCRIPTION
    The Split-AzureResourceId command parses an Azure resource ID and returns an object with properties based on what is parseable from the resource ID string.
    This command does not call any APIs and does not validate the resource exists.
 
    Split-AzureResourceId will fix invalid resource IDs in the following cases:
    - Adds a leading slash, if missing.
    - Removes a trailing slash, if present.
    - Removes the last segment if the resource ID has an odd number of segments.
 
    .EXAMPLE
    Split-AzureResourceId -Id '/subscriptions/##-#-#-#-###/resourceGroups/foo/providers/Microsoft.Bar/bazes/baz1'
 
    Parses the resource ID and returns an object with resource details.
#>

function Split-AzureResourceId {
    [CmdletBinding()]
    param
    (
        [AllowNull()]
        [AllowEmptyString()]
        [Parameter(Mandatory = $true)]
        [string]
        $Id
    )

    if ($Id) {
        Write-Verbose "Parsing resource ID: '$Id'"

        if (-not $Id) {
            return @{ ResourceId = $null }
        }

        # Add leading slash
        if (-not $Id.StartsWith('/')) {
            $Id = "/$Id"
        }

        # Remove trailing slash
        $parts = $Id.TrimEnd('/').Split('/')

        # Check for odd number of segments
        if ($parts.Count % 2 -eq 0) {
            Write-Verbose "Resource ID has odd number of segments and is invalid: $Id"
            $Id = ($parts[0..($parts.Count - 2)] -join '/')
            Write-Verbose " Parsing trimmed resource ID: $Id"
            return Split-AzureResourceId -Id $Id
        }

        # Check basic format
        $isRoot = $Id -eq '/'
        $isSubResource = -not $isRoot -and $parts[1].ToLower() -eq 'subscriptions'
        $isRGResource = $isSubResource -and $parts.Count -gt 3 -and $parts[3].ToLower() -eq 'resourcegroups'
        $isRG = $isRGResource -and $parts.Count -eq 5
        $isTenantResource = $parts[1].ToLower() -eq 'providers'
        $isProvider = $isTenantResource -and $parts.Count -eq 3
        $isTenant = $parts[1].ToLower() -eq 'tenants'

        Write-Verbose "Root? $isRoot"
        Write-Verbose "Subscription resource? $isSubResource"
        Write-Verbose "Resource group resource? $isRGResource"
        Write-Verbose "Resource group? $isRG"
        Write-Verbose "Tenant resource? $isTenantResource"
        Write-Verbose "Provider? $isProvider"
        Write-Verbose "Tenant? $isTenant"

        # Add implicit Microsoft.Resources RP before we check the resource details
        if ($isProvider) {
            # -or $isTenant
            # Prepend implicit RP name
            $leafParts = @('Microsoft.Resources') + $parts[1..($parts.Count - 1)]
        } else {
            # Prepend implicit RP name
            $leafParts = @($parts[0], 'providers', 'Microsoft.Resources') + $parts[1..($parts.Count - 1)]

            # Find last providers segment
            $leafParts = (($leafParts -replace '/PROVIDERS/', '/providers/') -Join '/').Split('/providers/')[-1].Split('/')
        }

        Write-Verbose "Leaf resource: $($leafParts -Join '/')"

        return [PSCustomObject]@{
            ResourceId             = $Id
            SubscriptionId         = if ($isSubResource) { $parts[2] } else { $null }
            SubscriptionResourceId = if ($isSubResource) { $parts[0..2] -Join '/' } else { $null }
            ResourceGroupId        = if ($isRGResource) { $parts[0..4] -Join '/' } else { $null }
            ResourceGroupName      = if ($isRGResource) { $parts[4] } else { $null }
            Provider               = $leafParts[0]
            Type                   = @($leafParts[0]) + $leafParts[(1..($leafParts.Count - 1)).Where{ $_ % 2 -eq 1 }] -join '/'
            Name                   = if ($isRG) { $leafParts[-1] } else { @($leafParts[(2..($leafParts.Count - 1)).Where{ $_ % 2 -eq 0 }]) -join '/' }
        }
    }
}