manifests/collectors/AI/AzureAI.psd1

#
# GENERATED by scripts/ConvertTo-ScoutCollectorDefinition.ps1 from Modules/Public/InventoryModules/AI/AzureAI.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.cognitiveservices/accounts'
    )

    ResourceTypeMatching = 'Grouped'

    AdditionalFilter = '$_.Kind -eq ''AIServices'''

    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
                # The datecreated field is absent (not present-and-null) on older API versions and some
                # resource kinds, so the raw read throws under StrictMode -- and [datetime] of a null
                # produced a bogus 0001-01-01 before that (AB#5671).
                $timecreated = Get-AZSCSafeProperty -InputObject $data -Path 'datecreated'
                $timecreated = if ($timecreated) { ([datetime]$timecreated).ToString("yyyy-MM-dd HH:mm") } else { '' }
                $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
                    }
                $pvt = if(![string]::IsNullOrEmpty((Get-AZSCSafeProperty -InputObject $data -Path 'privateendpointconnections' -Enumerate))){(Get-AZSCSafeProperty -InputObject $data -Path 'privateendpointconnections' -Enumerate)}else{'0'}
                # 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 } }
'@


    AdditionalRowLoops = @(
        @{
            Variable = 'pv'
            # AB#6845 decision ($pv): NO EmitNullWhenEmpty, deliberately. $pvt is assigned through
            # a sentinel in the row preamble -- if the account carries no
            # privateEndpointConnections it becomes '0' rather than an empty collection -- so this
            # loop always runs at least once and an Azure AI Services multi-service account keeps
            # its row.
            #
            # This is not a lucky accident worth leaving unstated: a private endpoint is opt-in,
            # so HAVING NONE IS THE DEFAULT. The sentinel is the only reason this worksheet is not
            # empty for a typical estate, and deleting it reopens AB#6845 here.
            #
            # EmitNullWhenEmpty would be dead configuration -- it fires on an EMPTY source, and
            # the sentinel guarantees this source is never empty. Adding it would also change the
            # shipped column value from '0' to blank for every account that has no connections.
            Source = '$pvt'
            Preamble = '$priv = Get-AZSCIdSegment -Id $pv -Index 8'
        }
    )

    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 = 'SKU'
            Expression = '(Get-AZSCSafeProperty -InputObject $1 -Path ''sku.name'' -Enumerate)'
        }
        @{
            Name = 'Retiring Feature'
            Expression = '$RetiringFeature'
        }
        @{
            Name = 'Retiring Date'
            Expression = '$RetiringDate'
        }
        @{
            Name = 'Public Network Access'
            Expression = '(Get-AZSCSafeProperty -InputObject $data -Path ''publicnetworkaccess'' -Enumerate)'
        }
        @{
            Name = 'Creation Time'
            Expression = '$timecreated'
        }
        @{
            Name = 'Is Migrated'
            Expression = '(Get-AZSCSafeProperty -InputObject $data -Path ''ismigrated'' -Enumerate)'
        }
        @{
            Name = 'Custom Domain Name'
            Expression = '(Get-AZSCSafeProperty -InputObject $data -Path ''customsubdomainname'' -Enumerate)'
        }
        @{
            Name = 'Endpoint'
            Expression = '(Get-AZSCSafeProperty -InputObject $data -Path ''endpoint'' -Enumerate)'
        }
        @{
            Name = 'Network Default Action'
            Expression = '(Get-AZSCSafeProperty -InputObject $data -Path ''networkacls.defaultaction'' -Enumerate)'
        }
        @{
            Name = 'IP Rules'
            Expression = '@((Get-AZSCSafeProperty -InputObject $data -Path ''networkacls.iprules'' -Enumerate)).count'
        }
        @{
            Name = 'Virtual Network Rules'
            Expression = '@((Get-AZSCSafeProperty -InputObject $data -Path ''networkacls.virtualnetworkrules'' -Enumerate)).count'
        }
        @{
            Name = 'Private Endpoint'
            Expression = '$priv'
        }
        @{
            Name = 'Resource U'
            Expression = '$ResUCount'
        }
        @{
            Name = 'Tag Name'
            Expression = '[string]$Tag.Name'
        }
        @{
            Name = 'Tag Value'
            Expression = '[string]$Tag.Value'
        }
    )

    Export = @{
        WorksheetName = 'Azure AI'
        TableNamePrefix = 'AzureAITable_'
        Columns = @(
            'Subscription'
            'Resource Group'
            'Name'
            'SKU'
            'Retiring Feature'
            'Retiring Date'
            'Public Network Access'
            'Creation Time'
            'Is Migrated'
            'Custom Domain Name'
            'Endpoint'
            'Network Default Action'
            'IP Rules'
            'Virtual Network Rules'
            'Private Endpoint'
            'Resource U'
        )
        TagColumns = @(
            'Tag Name'
            'Tag Value'
        )
        TagColumnsBefore = 'Resource U'
        NumberFormat = '0'
        ConditionalText = @(
            'New-ConditionalText F0 -Range D:D'
            'New-ConditionalText enabled -Range G:G'
            'New-ConditionalText -Range E2:E100 -ConditionalType ContainsText'
        )
    }

    SourceCollector = 'Modules/Public/InventoryModules/AI/AzureAI.ps1'
}