manifests/collectors/Networking/VirtualNetwork.psd1

#
# GENERATED by scripts/ConvertTo-ScoutCollectorDefinition.ps1 from Modules/Public/InventoryModules/Networking/VirtualNetwork.ps1 (AB#5660).
# Field expressions are copied verbatim from the original collector and evaluate in an
# equivalent scope -- see docs/design/decisions/declarative-collectors.md.
# Review before trusting; regenerate rather than hand-patch if the source collector changes.
#
@{
    ResourceTypes = @(
        'microsoft.network/virtualnetworks'
    )

    ResourceTypeMatching = 'Grouped'

    AdditionalFilter = $null

    FilterPreamble = ''

    RowLoopVariable = '1'

    Preamble = @'
$ResUCount = 1
                # An EMPTY $sub1 is not $null -- the match is empty for any resource whose subscription
                # is outside the requested scope -- and reading .Name off an empty collection throws
                # under StrictMode (AB#5671). Resolved once here, as VirtualMachine.ps1 already does.
                $sub1 = $SUB | Where-Object { $_.Id -eq $1.subscriptionId }
                # The else arm is $null, NOT '': with StrictMode off $sub1.Name on an unmatched ($null)
                # $sub1 evaluated to $null, and the ~110 collectors that still read $sub1.Name directly
                # emit $null here. '' was a silent behaviour change -- the declarative equivalence proof
                # caught it on 11 collectors, and it would have been invisible on the rest (AB#5659).
                $SubscriptionName = if ($sub1) { @($sub1)[0].Name } else { $null }
                $data = $1.PROPERTIES
                $Retired = Foreach ($Retirement in $Retirements)
                    {
                        if ($Retirement.id -eq $1.id) { $Retirement }
                    }
                if ($Retired)
                    {
                        $RetiredFeature = foreach ($Retire in $Retired)
                            {
                                $RetiredServiceID = $Unsupported | Where-Object {$_.Id -eq $Retired.ServiceID}
                                $tmp0 = [pscustomobject]@{
                                        'RetiredFeature' = $RetiredServiceID.RetiringFeature
                                        'RetiredDate' = $RetiredServiceID.RetirementDate
                                    }
                                $tmp0
                            }
                        $RetiringFeature = if (@($RetiredFeature.RetiredFeature).count -gt 1) { $RetiredFeature.RetiredFeature | ForEach-Object { $_ + ' ,' } }else { $RetiredFeature.RetiredFeature}
                        $RetiringFeature = [string]$RetiringFeature
                        $RetiringFeature = if ($RetiringFeature -like '* ,*') { $RetiringFeature -replace ".$" }else { $RetiringFeature }
 
                        $RetiringDate = if (@($RetiredFeature.RetiredDate).count -gt 1) { $RetiredFeature.RetiredDate | ForEach-Object { $_ + ' ,' } }else { $RetiredFeature.RetiredDate}
                        $RetiringDate = [string]$RetiringDate
                        $RetiringDate = if ($RetiringDate -like '* ,*') { $RetiringDate -replace ".$" }else { $RetiringDate }
                    }
                else
                    {
                        $RetiringFeature = $null
                        $RetiringDate = $null
                    }
                # AB#5671: an untagged resource's Resource Graph row OMITS the tags property rather
                # than carrying an empty object, so the raw read throws under StrictMode -- and so
                # does psobject.properties on a $null. The historic '0' sentinel existed only to make
                # the tag loop below run ONCE for an untagged resource, but '0'.Name throws too; an
                # empty tag object runs it once AND emits the identical [string]-cast empty Name/Value.
                $RowTags = Get-AZSCSafeProperty -InputObject $1 -Path 'tags'
                $TagProps = if ($null -ne $RowTags) { $RowTags.psobject.properties } else { $null }
                $Tags = if (![string]::IsNullOrEmpty($TagProps)) { $TagProps } else { [pscustomobject]@{ Name = $null; Value = $null } }
 
                $AddrPool = if (@((Get-AZSCSafeProperty -InputObject $data -Path 'addressSpace.addressPrefixes' -Enumerate)).count -gt 1) { (Get-AZSCSafeProperty -InputObject $data -Path 'addressSpace.addressPrefixes' -Enumerate) | ForEach-Object { $_ + ' ,' } }else { (Get-AZSCSafeProperty -InputObject $data -Path 'addressSpace.addressPrefixes' -Enumerate) }
                $AddrPool = [string]$AddrPool
                $AddrPool = if ($AddrPool -like '* ,*') { $AddrPool -replace ".$" }else { $AddrPool }
 
                $DNSServers = if (@((Get-AZSCSafeProperty -InputObject $data -Path 'dhcpOptions.dnsServers' -Enumerate)).count -gt 1) { (Get-AZSCSafeProperty -InputObject $data -Path 'dhcpOptions.dnsServers' -Enumerate)| ForEach-Object { $_ + ' ,' } }else { (Get-AZSCSafeProperty -InputObject $data -Path 'dhcpOptions.dnsServers' -Enumerate) }
                $DNSServers = [string]$DNSServers
                $DNSServers = if ($DNSServers -like '* ,*') { $DNSServers -replace ".$" }else { $DNSServers }
'@


    AdditionalRowLoops = @(
        @{
            Variable = '2'
            Source = '(Get-AZSCSafeProperty -InputObject $data -Path ''subnets'' -Enumerate)'
            # AB#6845: a VNet with no subnets is a real state -- freshly created, or its address
            # space reserved ahead of the design -- and this is the only worksheet a VNet appears
            # on, so it was missing from the report entirely rather than shown with no subnets.
            EmitNullWhenEmpty = $true
            Preamble = @'
# AB#6845: `@($null).count` is 1, so without the filter a VNet whose subnets are absent
                        # from the payload -- the case EmitNullWhenEmpty now emits a row for -- reported
                        # "Consumed IPs = 1" for a subnet that does not exist.
                        $ConsumedIPs = [int]@((Get-AZSCSafeProperty -InputObject $2 -Path 'properties.ipConfigurations.id' -Enumerate) | Where-Object { $null -ne $_ }).count
                        $Prefixes = if(![string]::IsNullOrEmpty((Get-AZSCSafeProperty -InputObject $2 -Path 'properties.addressPrefix' -Enumerate))){(Get-AZSCSafeProperty -InputObject $2 -Path 'properties.addressPrefix' -Enumerate)}else{(Get-AZSCSafeProperty -InputObject $2 -Path 'properties.addressPrefixes' -Enumerate)}
                        # This is a CIDR mask, not a resource id: '10.0.0.0/24'.split('/')[1] is '24'.
                        # A subnet with neither addressPrefix nor addressPrefixes leaves $Prefixes
                        # empty, and index [1] of a one-element split then throws under StrictMode
                        # instead of returning $null (AB#5671). Get-AZSCIdSegment is index-guarded
                        # '/'-splitting, which is exactly what is wanted here too.
                        $Prefix = Get-AZSCIdSegment -Id $Prefixes -Index 1
                        $AvailableIPs = $null
 
                        $Delegations = if (@((Get-AZSCSafeProperty -InputObject $2 -Path 'properties.delegations.properties.servicename' -Enumerate)).count -gt 1) { (Get-AZSCSafeProperty -InputObject $2 -Path 'properties.delegations.properties.servicename' -Enumerate) | ForEach-Object { $_ + ' ,' } }else { (Get-AZSCSafeProperty -InputObject $2 -Path 'properties.delegations.properties.servicename' -Enumerate)}
                        $Delegations = [string]$Delegations
                        $Delegations = if ($Delegations -like '* ,*') { $Delegations -replace ".$" }else { $Delegations }
 
                        $SubnetNSG = if ((Get-AZSCSafeProperty -InputObject $2 -Path 'properties.networkSecurityGroup.id' -Enumerate)) { (Get-AZSCIdSegment -Id (Get-AZSCSafeProperty -InputObject $2 -Path 'properties.networkSecurityGroup.id' -Enumerate) -Index 8) } else {'None'}
 
                        switch ([int]$Prefix)
                            {
                                8 {$AvailableIPs = 16777211 - $ConsumedIPs}
                                9 {$AvailableIPs = 8388603 - $ConsumedIPs}
                                10 {$AvailableIPs = 4194299 - $ConsumedIPs}
                                11 {$AvailableIPs = 2097147 - $ConsumedIPs}
                                12 {$AvailableIPs = 1048571 - $ConsumedIPs}
                                13 {$AvailableIPs = 524283 - $ConsumedIPs}
                                14 {$AvailableIPs = 262139 - $ConsumedIPs}
                                15 {$AvailableIPs = 131067 - $ConsumedIPs}
                                16 {$AvailableIPs = 65531 - $ConsumedIPs}
                                17 {$AvailableIPs = 32763 - $ConsumedIPs}
                                18 {$AvailableIPs = 16379 - $ConsumedIPs}
                                19 {$AvailableIPs = 8187 - $ConsumedIPs}
                                20 {$AvailableIPs = 4091 - $ConsumedIPs}
                                21 {$AvailableIPs = 2043 - $ConsumedIPs}
                                22 {$AvailableIPs = 1019 - $ConsumedIPs}
                                23 {$AvailableIPs = 507 - $ConsumedIPs}
                                24 {$AvailableIPs = 251 - $ConsumedIPs}
                                25 {$AvailableIPs = 123 - $ConsumedIPs}
                                26 {$AvailableIPs = 59 - $ConsumedIPs}
                                27 {$AvailableIPs = 27 - $ConsumedIPs}
                                28 {$AvailableIPs = 11 - $ConsumedIPs}
                                29 {$AvailableIPs = 4 - $ConsumedIPs}
                                30 {$AvailableIPs = 2 - $ConsumedIPs}
                                31 {$AvailableIPs = 2 - $ConsumedIPs}
                                32 {$AvailableIPs = 1 - $ConsumedIPs}
                                Default
                                    {
                                        # AB#6845: this was a BARE `$null`, lifted verbatim from the
                                        # original collector where it was a harmless no-op. In the
                                        # interpreter the row script's output stream IS the row set,
                                        # so a bare value emits a PHANTOM NULL ROW -- which then
                                        # crashes any consumer that indexes into it. It fires for
                                        # any prefix outside 8..32, including a subnet with no
                                        # addressPrefix at all. An assignment states the same intent
                                        # ("unknown mask, no IP count") and writes nothing.
                                        $AvailableIPs = $null
                                    }
                            }
'@

        }
    )

    TagLoop = @{
        Variable = 'Tag'
        Source = '$Tags'
        Preamble = ''
    }

    Fields = @(
        @{
            Name = 'ID'
            Expression = '$1.id'
        }
        @{
            Name = 'Subscription'
            Expression = '$SubscriptionName'
        }
        @{
            Name = 'Resource Group'
            Expression = '$1.RESOURCEGROUP'
        }
        @{
            Name = 'Name'
            Expression = '$1.NAME'
        }
        @{
            Name = 'Location'
            Expression = '$1.LOCATION'
        }
        @{
            Name = 'Retiring Feature'
            Expression = '$RetiringFeature'
        }
        @{
            Name = 'Retiring Date'
            Expression = '$RetiringDate'
        }
        @{
            Name = 'Address Space'
            Expression = '$AddrPool'
        }
        @{
            Name = 'Enable DDOS Protection'
            Expression = '(Get-AZSCSafeProperty -InputObject $data -Path ''enableDdosProtection'' -Enumerate)'
        }
        @{
            Name = 'DNS Servers'
            Expression = '$DNSServers'
        }
        @{
            Name = 'Consumed IPs'
            Expression = '[string]$ConsumedIPs'
        }
        @{
            Name = 'Available IPs'
            Expression = '[string]$AvailableIPs'
        }
        @{
            Name = 'Subnet Name'
            Expression = '(Get-AZSCSafeProperty -InputObject $2 -Path ''name'' -Enumerate)'
        }
        @{
            Name = 'Private Subnet'
            Expression = 'if((Get-AZSCSafeProperty -InputObject $2 -Path ''properties.defaultOutboundAccess'' -Enumerate) -eq ''false''){$true}else{$false}'
        }
        @{
            Name = 'Subnet Prefix'
            Expression = '[string]$Prefixes'
        }
        @{
            Name = 'Subnet Private Link Service Network Policies'
            Expression = '(Get-AZSCSafeProperty -InputObject $2 -Path ''properties.privateLinkServiceNetworkPolicies'' -Enumerate)'
        }
        @{
            Name = 'Subnet Private Endpoint Network Policies'
            Expression = '(Get-AZSCSafeProperty -InputObject $2 -Path ''properties.privateEndpointNetworkPolicies'' -Enumerate)'
        }
        @{
            Name = 'Subnet Delegations'
            Expression = '$Delegations'
        }
        @{
            Name = 'Subnet Route Table'
            Expression = 'if ((Get-AZSCSafeProperty -InputObject $2 -Path ''properties.routeTable.id'' -Enumerate)) { (Get-AZSCIdSegment -Id (Get-AZSCSafeProperty -InputObject $2 -Path ''properties.routeTable.id'' -Enumerate) -Index 8) }'
        }
        @{
            Name = 'Subnet Network Security Group'
            Expression = '$SubnetNSG'
        }
        @{
            Name = 'Resource U'
            Expression = '$ResUCount'
        }
        @{
            Name = 'Tag Name'
            Expression = '[string]$Tag.Name'
        }
        @{
            Name = 'Tag Value'
            Expression = '[string]$Tag.Value'
        }
    )

    Export = @{
        WorksheetName = 'Virtual Networks'
        TableNamePrefix = 'VNETTable_'
        Columns = @(
            'Subscription'
            'Resource Group'
            'Name'
            'Location'
            'Address Space'
            'Enable DDOS Protection'
            'Retiring Feature'
            'Retiring Date'
            'DNS Servers'
            'Subnet Name'
            'Private Subnet'
            'Subnet Prefix'
            'Consumed IPs'
            'Available IPs'
            'Subnet Private Link Service Network Policies'
            'Subnet Private Endpoint Network Policies'
            'Subnet Delegations'
            'Subnet Route Table'
            'Subnet Network Security Group'
            'Resource U'
        )
        TagColumns = @(
            'Tag Name'
            'Tag Value'
        )
        TagColumnsBefore = 'Resource U'
        NumberFormat = '0'
        ConditionalText = @(
            'New-ConditionalText FALSE -Range F:F'
            'New-ConditionalText -Range G2:G100 -ConditionalType ContainsText'
            'New-ConditionalText None -Range S:S'
        )
    }

    SourceCollector = 'Modules/Public/InventoryModules/Networking/VirtualNetwork.ps1'
}