manifests/collectors/Compute/VMOperationalData.psd1
|
# # GENERATED by scripts/ConvertTo-ScoutCollectorDefinition.ps1 from Modules/Public/InventoryModules/Compute/VMOperationalData.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/virtualmachines' ) ResourceTypeMatching = 'Grouped' AdditionalFilter = $null FilterPreamble = '' RowLoopVariable = '1' SetupPreamble = @' $vms = $Resources | Where-Object { $_.TYPE -eq 'microsoft.compute/virtualmachines' } # Hoisted once-per-collector joins. They deliberately sit outside the `$vms` guard so the # declarative interpreter can bind the same sets as its SetupPreamble; filtering an in-memory # collection has no side effect when no VM exists. $backupItems = $Resources | Where-Object { $_.TYPE -like 'microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems' } $advisorRecs = $Resources | Where-Object { $_.TYPE -eq 'microsoft.advisor/recommendations' } '@ SetupVariables = @( 'vms' 'backupItems' 'advisorRecs' ) Preamble = @' $ResUCount = 1 # AB#5671: empty $sub1 (subscription out of scope) is not $null, so `.Name` throws; # and an untagged VM's Resource Graph row OMITS `tags`, so both `$1.tags` and # `$null.psobject.properties` throw. The '0' sentinel only ever existed to make the # tag loop run once for an untagged resource -- but `'0'.Name` throws too, so an empty # tag object replaces it, emitting the identical [string]-cast empty Tag Name/Value. $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 $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 } } # ---- Extensions summary ---- # An extension id shorter than nine segments makes the fixed [8] index return $null # rather than throw once it is guarded on segment count. $vmExts = $Resources | Where-Object { $_.TYPE -eq 'microsoft.compute/virtualmachines/extensions' -and (Get-AZSCIdSegment -Id $_.id -Index 8) -eq $1.NAME } $extCount = if ($vmExts) { @($vmExts).Count } else { 0 } $extNames = if ($vmExts) { (@($vmExts) | ForEach-Object { Get-AZSCSafeProperty -InputObject $_ -Path 'PROPERTIES.type' }) -join ', ' } else { 'None' } # `$vmExts.PROPERTIES.publisher` is member enumeration through a collection whose # elements may omit `publisher` entirely -- an absent key on EVERY element makes the # enumeration yield nothing, which is what StrictMode reports as the missing property. $extPublishers = @(Get-AZSCSafeProperty -InputObject $vmExts -Path 'PROPERTIES.publisher' -Enumerate) $extTypes = @(Get-AZSCSafeProperty -InputObject $vmExts -Path 'PROPERTIES.type' -Enumerate) $hasAMA = if ($vmExts -and ($extPublishers -contains 'Microsoft.Azure.Monitor')) { 'Yes' } else { 'No' } $hasDefender = if ($vmExts -and ($extTypes -like '*MDE*' -or $extPublishers -like '*MicrosoftDefender*')) { 'Yes' } else { 'No' } # ---- Boot diagnostics ---- # `diagnosticsProfile` is ABSENT (not null) on any VM created without boot diagnostics, # which is the default -- so this was the very first line to throw here (AB#5671). $bootDiagEnabled = Get-AZSCSafeProperty -InputObject $data -Path 'diagnosticsProfile.bootDiagnostics.enabled' $bootDiagUri = Get-AZSCSafeProperty -InputObject $data -Path 'diagnosticsProfile.bootDiagnostics.storageUri' $PowerState = Get-AZSCSafeProperty -InputObject $data -Path 'extended.instanceView.powerState.displayStatus' $bootDiag = if ($bootDiagEnabled -eq $true) { 'Enabled' } else { 'Disabled' } $bootDiagSA = if ($bootDiagUri) { $bootDiagUri } else { 'Managed' } # ---- Backup status (cross-ref from pre-loaded backup items) ---- $backupItem = $backupItems | Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'PROPERTIES.sourceResourceId') -eq $1.id } $backupEnabled = if ($backupItem) { 'Yes' } else { 'No' } $lastBackupTime = if ($backupItem) { Get-AZSCSafeProperty -InputObject $backupItem -Path 'PROPERTIES.lastBackupTime' -Enumerate } else { 'N/A' } $backupVault = if ($backupItem) { Get-AZSCIdSegment -Id (Get-AZSCCollectedValue -InputObject $backupItem -Name 'id') -Index 8 } else { 'N/A' } $backupPolicy = if ($backupItem) { Get-AZSCSafeProperty -InputObject $backupItem -Path 'PROPERTIES.policyName' -Enumerate } else { 'N/A' } # ---- Advisor recommendations (cross-ref) ---- $vmAdvisor = $advisorRecs | Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'PROPERTIES.resourceMetadata.resourceId') -eq $1.id } $advisorCount = if ($vmAdvisor) { @($vmAdvisor).Count } else { 0 } $costAdvisor = if ($vmAdvisor) { @($vmAdvisor | Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'PROPERTIES.category') -eq 'Cost' }).Count } else { 0 } $secAdvisor = if ($vmAdvisor) { @($vmAdvisor | Where-Object { (Get-AZSCSafeProperty -InputObject $_ -Path 'PROPERTIES.category') -eq 'Security' }).Count } else { 0 } # Patch assessment is collected once and attached to the VM's typed operational # envelope. The processing/reporting phase is intentionally network-free. $OperationalEnvelope = @($Resources | Where-Object { $_.TYPE -eq 'AZSC/Operational/VMOperationalData' -and $_.id -eq $1.id }) | Select-Object -First 1 $PatchAssessment = Get-AZSCSafeProperty -InputObject $OperationalEnvelope -Path 'properties.PatchAssessment' $pendingCritical = 'N/A'; $pendingImportant = 'N/A'; $lastPatchTime = 'N/A' if ($PatchAssessment -and -not (Get-AZSCSafeProperty -InputObject $PatchAssessment -Path '__AZSCError')) { $pendingCritical = if (Get-AZSCSafeProperty -InputObject $PatchAssessment -Path 'criticalAndSecurityPatchCount') { Get-AZSCSafeProperty -InputObject $PatchAssessment -Path 'criticalAndSecurityPatchCount' } else { 0 } $pendingImportant = if (Get-AZSCSafeProperty -InputObject $PatchAssessment -Path 'otherPatchCount') { Get-AZSCSafeProperty -InputObject $PatchAssessment -Path 'otherPatchCount' } else { 0 } $PatchStart = Get-AZSCSafeProperty -InputObject $PatchAssessment -Path 'startDateTime' $lastPatchTime = if ($PatchStart) { ([datetime]$PatchStart).ToString('yyyy-MM-dd') } else { 'N/A' } } # ---- Lifecycle tags ---- # Reading a specific tag off a resource that has no `tags` block at all, or has tags but # not this one, throws under StrictMode. Get-AZSCSafeProperty's lookup is already # case-insensitive, so the original Environment/environment pairs collapse to one read # each -- the same value, since the fall-back arm could only ever match what the first # arm already would have. $tagEnv = Get-AZSCSafeProperty -InputObject $RowTags -Path 'Environment' $tagOwner = Get-AZSCSafeProperty -InputObject $RowTags -Path 'Owner' $tagCostCenter = Get-AZSCSafeProperty -InputObject $RowTags -Path 'CostCenter' $tagExpiry = Get-AZSCSafeProperty -InputObject $RowTags -Path 'ExpirationDate' if (-not $tagExpiry) { $tagExpiry = Get-AZSCSafeProperty -InputObject $RowTags -Path 'Expiration' } if (-not $tagEnv) { $tagEnv = 'N/A' } if (-not $tagOwner) { $tagOwner = 'N/A' } if (-not $tagCostCenter) { $tagCostCenter = 'N/A' } if (-not $tagExpiry) { $tagExpiry = 'N/A' } '@ AdditionalRowLoops = @() TagLoop = @{ Variable = 'Tag' Source = '$Tags' Preamble = '' } Fields = @( @{ Name = 'ID' Expression = '$1.id' } @{ Name = 'Subscription' Expression = '$SubscriptionName' } @{ Name = 'Resource Group' Expression = '$1.RESOURCEGROUP' } @{ Name = 'VM Name' Expression = '$1.NAME' } @{ Name = 'Location' Expression = '$1.LOCATION' } @{ Name = 'Power State' Expression = 'if ($PowerState) { $PowerState } else { ''N/A'' }' } @{ Name = 'Extensions Count' Expression = '$extCount' } @{ Name = 'Extensions Installed' Expression = '$extNames' } @{ Name = 'Azure Monitor Agent' Expression = '$hasAMA' } @{ Name = 'Defender Extension' Expression = '$hasDefender' } @{ Name = 'Boot Diagnostics' Expression = '$bootDiag' } @{ Name = 'Boot Diag Storage' Expression = '$bootDiagSA' } @{ Name = 'Backup Enabled' Expression = '$backupEnabled' } @{ Name = 'Last Backup Time' Expression = '$lastBackupTime' } @{ Name = 'Backup Vault' Expression = '$backupVault' } @{ Name = 'Backup Policy' Expression = '$backupPolicy' } @{ Name = 'Advisor Recs Total' Expression = '$advisorCount' } @{ Name = 'Advisor Cost Recs' Expression = '$costAdvisor' } @{ Name = 'Advisor Security Recs' Expression = '$secAdvisor' } @{ Name = 'Pending Critical Patches' Expression = '$pendingCritical' } @{ Name = 'Pending Other Patches' Expression = '$pendingImportant' } @{ Name = 'Last Patch Assessment' Expression = '$lastPatchTime' } @{ Name = 'Tag: Environment' Expression = '$tagEnv' } @{ Name = 'Tag: Owner' Expression = '$tagOwner' } @{ Name = 'Tag: Cost Center' Expression = '$tagCostCenter' } @{ Name = 'Tag: Expiration Date' Expression = '$tagExpiry' } @{ Name = 'Resource U' Expression = '$ResUCount' } @{ Name = 'Tag Name' Expression = '[string]$Tag.Name' } @{ Name = 'Tag Value' Expression = '[string]$Tag.Value' } ) Export = @{ WorksheetName = 'VM Operational Data' TableNamePrefix = 'VMOperDataTable_' Columns = @( 'Subscription' 'Resource Group' 'VM Name' 'Location' 'Power State' 'Extensions Count' 'Extensions Installed' 'Azure Monitor Agent' 'Defender Extension' 'Boot Diagnostics' 'Boot Diag Storage' 'Backup Enabled' 'Last Backup Time' 'Backup Vault' 'Backup Policy' 'Advisor Recs Total' 'Advisor Cost Recs' 'Advisor Security Recs' 'Pending Critical Patches' 'Pending Other Patches' 'Last Patch Assessment' 'Tag: Environment' 'Tag: Owner' 'Tag: Cost Center' 'Tag: Expiration Date' 'Resource U' ) TagColumns = @( 'Tag Name' 'Tag Value' ) TagColumnsBefore = $null NumberFormat = '0' ConditionalText = @( 'New-ConditionalText -ConditionalType ContainsText ''No'' -ConditionalTextColor ([System.Drawing.Color]::FromArgb(255,165,0)) -BackgroundColor ([System.Drawing.Color]::White)' 'New-ConditionalText -ConditionalType ContainsText ''Disabled'' -ConditionalTextColor ([System.Drawing.Color]::FromArgb(255,0,0)) -BackgroundColor ([System.Drawing.Color]::White)' 'New-ConditionalText -ConditionalType ContainsText ''Yes'' -ConditionalTextColor ([System.Drawing.Color]::FromArgb(0,176,80)) -BackgroundColor ([System.Drawing.Color]::White)' ) } SourceCollector = 'Modules/Public/InventoryModules/Compute/VMOperationalData.ps1' } |