manifests/collectors/Compute/VirtualMachineScaleSet.psd1

#
# GENERATED by scripts/ConvertTo-ScoutCollectorDefinition.ps1 from Modules/Public/InventoryModules/Compute/VirtualMachineScaleSet.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.compute/virtualmachinescalesets'
    )

    ResourceTypeMatching = 'Grouped'

    AdditionalFilter = $null

    FilterPreamble = ''

    RowLoopVariable = '1'

    SetupPreamble = @'
$vmss = $Resources | Where-Object {$_.TYPE -eq 'microsoft.compute/virtualmachinescalesets'}
        $AutoScale = $Resources | Where-Object {$_.TYPE -eq "microsoft.insights/autoscalesettings" -and $_.Properties.enabled -eq 'true'}
        $AKS = $Resources | Where-Object {$_.TYPE -eq 'microsoft.containerservice/managedclusters'}
        $SFC = $Resources | Where-Object {$_.TYPE -eq 'microsoft.servicefabric/clusters'}
        $VMExtraDetails = $Resources | Where-Object { $_.TYPE -eq 'AZSC/VM/SKU' }
        $VMQuotas = $Resources | Where-Object { $_.TYPE -eq 'AZSC/VM/Quotas' }
'@


    SetupVariables = @(
        'vmss'
        'AutoScale'
        'AKS'
        'SFC'
        'VMExtraDetails'
        'VMQuotas'
    )

    Preamble = @'
$ResUCount = 1
                # An EMPTY $sub1 is not $null, and `.Name` on it throws under StrictMode -- the
                # match is empty for any resource whose subscription is outside the requested
                # scope. Resolved once here, defensively, 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
                # AB#5671: every chain below hangs off `virtualMachineProfile`, which is ABSENT
                # (not $null) on a Flexible-orchestration scale set -- Flexible keeps the profile
                # on the member VMs, so the raw chains threw on the first such scale set. Reads
                # crossing `networkInterfaceConfigurations` / `ipConfigurations` (arrays) use
                # -Enumerate so the member enumeration the original relied on is reproduced
                # exactly, not silently collapsed to $null.
                $VMProfile = Get-AZSCSafeProperty -InputObject $data -Path 'virtualMachineProfile'
                $OS = Get-AZSCSafeProperty -InputObject $VMProfile -Path 'storageProfile.osDisk.osType'
                $RelatedAKS = Get-AZSCCollectedValue -InputObject @($AKS | Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'properties.nodeResourceGroup') -eq $1.resourceGroup }) -Name 'Name'
                if([string]::IsNullOrEmpty($RelatedAKS)){
                    $VMSSClusterEndpoints = @(Get-AZSCSafeProperty -InputObject $VMProfile -Path 'extensionProfile.extensions.properties.settings.clusterEndpoint' -Enumerate)
                    $Related = Get-AZSCCollectedValue -InputObject @($SFC | Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'Properties.clusterEndpoint') -in $VMSSClusterEndpoints }) -Name 'Name'
                }else{$Related = $RelatedAKS}
                $Scaling = ($AutoScale | Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'Properties.targetResourceUri') -eq $1.id })
                if([string]::IsNullOrEmpty($Scaling)){$AutoSc = $false}else{$AutoSc = $true}
                $Diag = if(Get-AZSCSafeProperty -InputObject $VMProfile -Path 'diagnosticsProfile'){'Enabled'}else{'Disabled'}
                if($OS -eq 'Linux'){$PWD = Get-AZSCSafeProperty -InputObject $VMProfile -Path 'osProfile.linuxConfiguration.disablePasswordAuthentication'}Else{$PWD = 'N/A'}
                $AcceleratedNet = if(![string]::IsNullOrEmpty((Get-AZSCSafeProperty -InputObject $VMProfile -Path 'networkProfile.networkInterfaceConfigurations.properties.enableAcceleratedNetworking' -Enumerate))){$true}else{$false}
 
                # Extra Hardware Details
                #
                # $VMExtraDetails (the synthetic 'AZSC/VM/SKU' rows) is EMPTY whenever the SKU
                # enrichment query returned nothing, and reading `.properties` off an empty
                # collection throws under StrictMode (AB#5671). Every capability variable is also
                # reset per scale set: they are assigned only inside the loop below, so without
                # this they would leak the PREVIOUS scale set's values -- or, on the first
                # iteration, be genuinely unset, which StrictMode reports as an error too.
                $vCPUs = $null; $vCPUsPerCore = $null; $RAM = $null; $MaxDataDiskCount = $null
                $UncachedDiskIOPS = $null; $UncachedDiskBytesPerSecond = $null; $MaxNetworkInterfaces = $null
 
                $SKUName = Get-AZSCSafeProperty -InputObject $1 -Path 'sku.name'
                $VMExtraDetail = @(Get-AZSCCollectedValue -InputObject $VMExtraDetails -Name 'properties') |
                    Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'Location') -eq $1.location }
                $VMExtraDetail = @(Get-AZSCCollectedValue -InputObject $VMExtraDetail -Name 'SKUs') |
                    Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'Name') -eq $SKUName }
 
                foreach ($Capability in @(Get-AZSCCollectedValue -InputObject $VMExtraDetail -Name 'Capabilities')) {
                    if ($Capability.Name -eq 'vCPUs') {$vCPUs = $Capability.Value}
                    if ($Capability.Name -eq 'vCPUsPerCore') {$vCPUsPerCore = $Capability.Value}
                    if ($Capability.Name -eq 'MemoryGB') {$RAM = $Capability.Value}
                    if ($Capability.Name -eq 'MaxDataDiskCount') {$MaxDataDiskCount = $Capability.Value}
                    if ($Capability.Name -eq 'UncachedDiskIOPS') {$UncachedDiskIOPS = $Capability.Value}
                    if ($Capability.Name -eq 'UncachedDiskBytesPerSecond') {$UncachedDiskBytesPerSecond = ([math]::Round($Capability.Value / 1024) / 1024)}
                    if ($Capability.Name -eq 'MaxNetworkInterfaces') {$MaxNetworkInterfaces = $Capability.Value}
                }
 
                # Quotas -- same empty-collection hazard as the SKU block above, plus the
                # subtraction itself: with no matching quota row both operands were reads off an
                # empty collection. They now resolve to $null, and $null - $null is 0, which is
                # what the unguarded expression produced under -Off.
                $Size = Get-AZSCCollectedValue -InputObject $VMExtraDetail -Name 'Family'
                $Quota = @(Get-AZSCCollectedValue -InputObject $VMQuotas -Name 'properties') |
                    Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'SubId') -eq $1.subscriptionId }
                $Quota = $Quota | Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'Location') -eq $1.location }
                $QuotaData = @(Get-AZSCCollectedValue -InputObject $Quota -Name 'Data') |
                    Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'Name.Value') -eq $Size }
                $RemainingQuota = ((Get-AZSCCollectedValue -InputObject $QuotaData -Name 'Limit') -
                                   (Get-AZSCCollectedValue -InputObject $QuotaData -Name 'CurrentValue'))
 
 
                # `timeCreated` is absent on older API versions, and [datetime]$null throws, so the
                # unguarded three-line conversion fails twice over under StrictMode (AB#5671).
                $timecreated = Get-AZSCSafeProperty -InputObject $data -Path 'timeCreated'
                $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
                    }
                # Two array hops (networkInterfaceConfigurations, then ipConfigurations) -- exactly
                # what -Enumerate is for. `.split('/')[8]` / `[10]` are also guarded on segment
                # count now: a subnet id shorter than the standard shape used to raise
                # "Index was outside the bounds of the array" rather than yield $null (AB#5671).
                $Subnet = Get-AZSCSafeProperty -InputObject $VMProfile -Path 'networkProfile.networkInterfaceConfigurations.properties.ipConfigurations.properties.subnet.id' -Enumerate |
                    Select-Object -Unique
                # NOT `$x = if (...) { @(...) } else { @() }` -- an `if` used as an expression
                # returns its branch through the pipeline, and an EMPTY array enumerates to
                # nothing, so $x lands as $null and the very next `.Count` throws. Assign the
                # empty default first, then overwrite.
                $SubnetSegments = @()
                if(![string]::IsNullOrEmpty($Subnet)){ $SubnetSegments = @(([string]$Subnet).split('/')) }
                $VNET = if($SubnetSegments.Count -gt 8) {$SubnetSegments[8]}else{$null}
                $Subnet = if($SubnetSegments.Count -gt 10){$SubnetSegments[10]}else{$null}
                $ext = @()
                # Not @()-wrapped: @($null) has one element, which would append a stray ', ' for a
                # scale set with no extensions where the original appended nothing.
                $ext = foreach ($ex in (Get-AZSCSafeProperty -InputObject $VMProfile -Path 'extensionProfile.extensions.name' -Enumerate))
                                {
                                    $ex + ', '
                                }
                $VMSSNsgId = Get-AZSCSafeProperty -InputObject $VMProfile -Path 'networkProfile.networkInterfaceConfigurations.properties.networkSecurityGroup.id' -Enumerate
                $VMSSNsgSegments = @()
                if(![string]::IsNullOrEmpty($VMSSNsgId)){ $VMSSNsgSegments = @(([string]$VMSSNsgId).split('/')) }
                $NSG = if($VMSSNsgSegments.Count -gt 8){$VMSSNsgSegments[8]}else{'Not Configured'}
                # A Resource Graph row for an untagged resource OMITS `tags` (it is not present-
                # and-empty), so both `$1.tags` and `$null.psobject.properties` throw under
                # StrictMode. The historic fall-back was the string '0', a sentinel whose only job
                # was to make the loop below run exactly once for an untagged resource -- but
                # `'0'.Name` throws under StrictMode too. An empty tag object does the same job
                # and emits the identical row: [string]$null and [string]('0'.Name) are both ''.
                $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 = @()

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

    Fields = @(
        @{
            Name = 'ID'
            Expression = '$1.id'
        }
        @{
            Name = 'Subscription'
            Expression = '$SubscriptionName'
        }
        @{
            Name = 'Resource Group'
            Expression = '$1.RESOURCEGROUP'
        }
        @{
            Name = 'AKS / SFC'
            Expression = '$Related'
        }
        @{
            Name = 'Name'
            Expression = '$1.NAME'
        }
        @{
            Name = 'Location'
            Expression = '$1.LOCATION'
        }
        @{
            Name = 'SKU Tier'
            Expression = '(Get-AZSCSafeProperty -InputObject $1 -Path ''sku.tier'')'
        }
        @{
            Name = 'Retiring Feature'
            Expression = '$RetiringFeature'
        }
        @{
            Name = 'Retiring Date'
            Expression = '$RetiringDate'
        }
        @{
            Name = 'Fault Domain'
            Expression = '(Get-AZSCSafeProperty -InputObject $data -Path ''platformFaultDomainCount'')'
        }
        @{
            Name = 'Upgrade Policy'
            Expression = '(Get-AZSCSafeProperty -InputObject $data -Path ''upgradePolicy.mode'')'
        }
        @{
            Name = 'Diagnostics'
            Expression = '$Diag'
        }
        @{
            Name = 'VM Size'
            Expression = '$SKUName'
        }
        @{
            Name = 'Instances'
            Expression = '(Get-AZSCSafeProperty -InputObject $1 -Path ''sku.capacity'')'
        }
        @{
            Name = 'vCPUs (per Instance)'
            Expression = '$vCPUs'
        }
        @{
            Name = 'vCPUs per Core (per Instance)'
            Expression = '$vCPUsPerCore'
        }
        @{
            Name = 'Memory (GB) (per Instance)'
            Expression = '$RAM'
        }
        @{
            Name = 'Remaining Quota'
            Expression = '$RemainingQuota'
        }
        @{
            Name = 'Autoscale Enabled'
            Expression = '$AutoSc'
        }
        @{
            Name = 'VM OS'
            Expression = '$OS'
        }
        @{
            Name = 'OS Image'
            Expression = '(Get-AZSCSafeProperty -InputObject $VMProfile -Path ''storageProfile.imageReference.offer'')'
        }
        @{
            Name = 'Image Version'
            Expression = '(Get-AZSCSafeProperty -InputObject $VMProfile -Path ''storageProfile.imageReference.sku'')'
        }
        @{
            Name = 'VM OS Disk Size (GB)'
            Expression = '(Get-AZSCSafeProperty -InputObject $VMProfile -Path ''storageProfile.osDisk.diskSizeGB'')'
        }
        @{
            Name = 'Disk Storage Account Type'
            Expression = '(Get-AZSCSafeProperty -InputObject $VMProfile -Path ''storageProfile.osDisk.managedDisk.storageAccountType'')'
        }
        @{
            Name = 'Disable Password Authentication'
            Expression = '$PWD'
        }
        @{
            Name = 'Custom DNS Servers'
            Expression = '[string](Get-AZSCSafeProperty -InputObject $VMProfile -Path ''networkProfile.networkInterfaceConfigurations.properties.dnsSettings.dnsServers'' -Enumerate)'
        }
        @{
            Name = 'Virtual Network'
            Expression = '$VNET'
        }
        @{
            Name = 'Subnet'
            Expression = '$Subnet'
        }
        @{
            Name = 'Accelerated Networking Enabled'
            Expression = '$AcceleratedNet'
        }
        @{
            Name = 'Network Security Group'
            Expression = '$NSG'
        }
        @{
            Name = 'Extensions'
            Expression = '[string]$ext'
        }
        @{
            Name = 'Admin Username'
            Expression = '(Get-AZSCSafeProperty -InputObject $VMProfile -Path ''osProfile.adminUsername'')'
        }
        @{
            Name = 'VM Name Prefix'
            Expression = '(Get-AZSCSafeProperty -InputObject $VMProfile -Path ''osProfile.computerNamePrefix'')'
        }
        @{
            Name = 'Created Time'
            Expression = '$timecreated'
        }
        @{
            Name = 'Resource U'
            Expression = '$ResUCount'
        }
        @{
            Name = 'Tag Name'
            Expression = '[string]$Tag.Name'
        }
        @{
            Name = 'Tag Value'
            Expression = '[string]$Tag.Value'
        }
    )

    Export = @{
        WorksheetName = 'Virtual Machine Scale Sets'
        TableNamePrefix = 'VMSSTable_'
        Columns = @(
            'Subscription'
            'Resource Group'
            'AKS / SFC'
            'Name'
            'Location'
            'SKU Tier'
            'Retiring Feature'
            'Retiring Date'
            'Fault Domain'
            'Upgrade Policy'
            'Diagnostics'
            'VM Size'
            'Instances'
            'vCPUs (per Instance)'
            'vCPUs per Core (per Instance)'
            'Memory (GB) (per Instance)'
            'Remaining Quota'
            'Autoscale Enabled'
            'VM OS'
            'OS Image'
            'Image Version'
            'VM OS Disk Size (GB)'
            'Disk Storage Account Type'
            'Disable Password Authentication'
            'Custom DNS Servers'
            'Virtual Network'
            'Subnet'
            'Accelerated Networking Enabled'
            'Network Security Group'
            'Extensions'
            'Admin Username'
            'VM Name Prefix'
            'Created Time'
            'Resource U'
        )
        TagColumns = @(
            'Tag Name'
            'Tag Value'
        )
        TagColumnsBefore = 'Resource U'
        NumberFormat = '0'
        ConditionalText = @(
            'New-ConditionalText FALSE -Range R:R'
            'New-ConditionalText Disabled -Range K:K'
            'New-ConditionalText FALSE -Range AB:AB'
            'New-ConditionalText 0 -Range Q:Q'
            'New-ConditionalText -Range G2:G100 -ConditionalType ContainsText'
        )
    }

    SourceCollector = 'Modules/Public/InventoryModules/Compute/VirtualMachineScaleSet.ps1'
}