Modules/Private/Main/Get-AZSCIdSegment.ps1

<#
.Synopsis
Reads one '/'-delimited segment out of an Azure resource id, safely under StrictMode.
 
.DESCRIPTION
Roughly thirty of the InventoryModules collectors pull a name out of a related resource's id with
a FIXED index -- `$data.networkSecurityGroup.id.split('/')[8]` for the NSG name,
`$1.MANAGEDBY.split('/')[8]` for the VM a disk is attached to, `($_.id -split '/')[10]` for a
subnet -- because the canonical resource id
 
    /subscriptions/{s}/resourceGroups/{rg}/providers/{Namespace}/{type}/{name}
     0 1 2 3 4 5 6 7 8
 
puts the name at index 8.
 
Plenty of real ids are shorter than that. A subscription-scoped id (a Defender or Security
assessment with no resource target, a subscription-level policy assignment) has five or six
segments; a `managedBy` can be a bare resource group id. Under Set-StrictMode -Version Latest an
out-of-range array index THROWS
 
    Index was outside the bounds of the array.
 
where without StrictMode it quietly returns $null (AB#5671) -- so the collectors were written
against the quiet behaviour and crash under the strict one. That is the third of the three
StrictMode defect classes in the collectors, alongside the absent-property read
(Get-AZSCSafeProperty) and the empty-collection member enumeration (Get-AZSCCollectedValue).
 
This function returns $null for an out-of-range index, a $null/empty id, or a non-string id,
which is exactly what the unguarded expression produced with StrictMode off. Nothing about a
collector's output changes for an id that IS long enough.
 
.PARAMETER Id
The resource id to split. $null, empty and non-string values are accepted and return $null.
 
.PARAMETER Index
The zero-based segment index. 8 is the resource name in a canonical resource id.
 
.OUTPUTS
The segment at that index, or $null if the id has too few segments.
 
.EXAMPLE
Get-AZSCIdSegment -Id $data.networkSecurityGroup.id -Index 8
 
.EXAMPLE
# The guarded form of ($_.id -split '/')[10] -- a subnet name.
Get-AZSCIdSegment -Id $_.id -Index 10
 
.COMPONENT
This PowerShell Module is part of Azure Scout (AZSC).
#>

function Get-AZSCIdSegment {
    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, ValueFromPipeline)]
        [AllowNull()]
        [AllowEmptyString()]
        $Id,

        [Parameter(Mandatory, Position = 1)]
        [int]$Index
    )

    process {
        if ($null -eq $Id) { return $null }

        # A member-enumeration result can arrive here as an array of ids; [string] on it matches
        # what the unguarded `$array.split('/')` chain degenerated to rather than inventing a new
        # answer, and the single-id case (every real caller) is unaffected.
        $Text = [string]$Id
        if ([string]::IsNullOrEmpty($Text)) { return $null }

        $Segments = @($Text -split '/')
        if ($Index -lt 0 -or $Index -ge $Segments.Count) { return $null }

        return $Segments[$Index]
    }
}