Modules/Public/InventoryModules/Hybrid/ArcServerOperationalData.ps1
|
<#
.Synopsis Operational Deep-Data for Azure Arc Servers .DESCRIPTION This script provides deep operational data for Azure Arc-enabled servers including agent health, extension summary, lifecycle tags, and update compliance. Supplement to ARCServers.ps1 — does not replace it. Excel Sheet Name: Arc Server Operational Data .Link https://github.com/thisismydemo/azure-scout/Modules/Public/InventoryModules/Hybrid/ArcServerOperationalData.ps1 .COMPONENT This powershell Module is part of Azure Scout (AZSC) .NOTES Version: 1.0.0 First Release Date: February 24, 2026 Authors: AzureScout Contributors #> <######## Default Parameters. Don't modify this ########> param($SCPath, $Sub, $Intag, $Resources, $Retirements, $Task, $File, $SmaResources, $TableStyle, $Unsupported) If ($Task -eq 'Processing') { $arcServers = $Resources | Where-Object { $_.TYPE -eq 'microsoft.hybridcompute/machines' } if ($arcServers) { # Pre-load Arc extensions $arcExtensions = $Resources | Where-Object { $_.TYPE -eq 'microsoft.hybridcompute/machines/extensions' } # Pre-load Advisor recommendations $advisorRecs = $Resources | Where-Object { $_.TYPE -eq 'microsoft.advisor/recommendations' } # Pre-load backup items $backupItems = $Resources | Where-Object { $_.TYPE -like 'microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems' } $tmp = foreach ($1 in $arcServers) { $ResUCount = 1 $sub1 = $SUB | Where-Object { $_.Id -eq $1.subscriptionId } $data = $1.PROPERTIES $Tags = if (![string]::IsNullOrEmpty($1.tags.psobject.properties)) { $1.tags.psobject.properties } else { '0' } # ---- OS Details ---- $osName = if ($data.osName) { $data.osName } else { if ($data.osSku) { $data.osSku } else { 'N/A' } } $osVersion = if ($data.osVersion) { $data.osVersion } else { 'N/A' } $osSku = if ($data.osSku) { $data.osSku } else { 'N/A' } # ---- Arc Agent Health ---- $agentVersion = if ($data.agentVersion) { $data.agentVersion } else { 'N/A' } $connStatus = if ($data.status) { $data.status } else { 'N/A' } $lastHB = if ($data.lastStatusChange) { ([datetime]$data.lastStatusChange).ToString('yyyy-MM-dd HH:mm') } else { 'N/A' } $agentErrors = if ($data.errorDetails) { ($data.errorDetails | ForEach-Object { $_.message }) -join '; ' } else { 'None' } # ---- Extensions ---- $machExts = $arcExtensions | Where-Object { $_.id -match "/machines/$($1.NAME)/extensions/" } $extCount = if ($machExts) { @($machExts).Count } else { 0 } $extNames = if ($machExts) { ($machExts | ForEach-Object { $_.PROPERTIES.type }) -join ', ' } else { 'None' } $hasAMA = if ($machExts -and ($machExts.PROPERTIES.publisher -contains 'Microsoft.Azure.Monitor')) { 'Yes' } else { 'No' } # ---- Backup ---- $backupItem = $backupItems | Where-Object { $_.PROPERTIES.sourceResourceId -eq $1.id } $backupEnabled = if ($backupItem) { 'Yes' } else { 'No' } $lastBackup = if ($backupItem) { $backupItem.PROPERTIES.lastBackupTime } else { 'N/A' } # ---- Advisor ---- $vmAdvisor = $advisorRecs | Where-Object { $_.PROPERTIES.resourceMetadata.resourceId -eq $1.id } $advisorCount = if ($vmAdvisor) { @($vmAdvisor).Count } else { 0 } $secAdvisor = if ($vmAdvisor) { @($vmAdvisor | Where-Object { $_.PROPERTIES.category -eq 'Security' }).Count } else { 0 } # ---- Update compliance via REST ---- $pendingCritical = 'N/A' $lastPatchTime = 'N/A' try { $patchUri = "/subscriptions/$($1.subscriptionId)/resourceGroups/$($1.RESOURCEGROUP)/providers/Microsoft.HybridCompute/machines/$($1.NAME)/assessPatches?api-version=2023-06-20-preview" $patchResp = Invoke-AzRestMethod -Path $patchUri -Method POST -ErrorAction SilentlyContinue if ($patchResp.StatusCode -in 200, 202) { $patchData = $patchResp.Content | ConvertFrom-Json $pendingCritical = if ($patchData.availablePatchCountByClassification.critical) { $patchData.availablePatchCountByClassification.critical } else { 0 } $lastPatchTime = if ($patchData.lastModifiedDateTime) { ([datetime]$patchData.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { 'N/A' } } } catch {} # ---- Lifecycle tags ---- $tagEnv = if ($1.tags.Environment) { $1.tags.Environment } elseif ($1.tags.environment) { $1.tags.environment } else { 'N/A' } $tagOwner = if ($1.tags.Owner) { $1.tags.Owner } elseif ($1.tags.owner) { $1.tags.owner } else { 'N/A' } $tagCostCenter = if ($1.tags.CostCenter) { $1.tags.CostCenter } elseif ($1.tags.costcenter) { $1.tags.costcenter } else { 'N/A' } $tagLocation = if ($1.tags.PhysicalLocation) { $1.tags.PhysicalLocation } else { 'N/A' } foreach ($Tag in $Tags) { $obj = @{ 'ID' = $1.id; 'Subscription' = $sub1.Name; 'Resource Group' = $1.RESOURCEGROUP; 'Machine Name' = $1.NAME; 'Location' = $1.LOCATION; 'Connection Status' = $connStatus; 'OS Name' = $osName; 'OS Version' = $osVersion; 'OS SKU' = $osSku; 'Arc Agent Version' = $agentVersion; 'Last Status Change' = $lastHB; 'Agent Errors' = $agentErrors; 'Extensions Count' = $extCount; 'Extensions Installed' = $extNames; 'Azure Monitor Agent' = $hasAMA; 'Backup Enabled' = $backupEnabled; 'Last Backup' = $lastBackup; 'Advisor Recs Total' = $advisorCount; 'Advisor Security Recs' = $secAdvisor; 'Pending Critical Patches' = $pendingCritical; 'Last Patch Assessment' = $lastPatchTime; 'Tag: Environment' = $tagEnv; 'Tag: Owner' = $tagOwner; 'Tag: Cost Center' = $tagCostCenter; 'Tag: Physical Location' = $tagLocation; 'Resource U' = $ResUCount; 'Tag Name' = [string]$Tag.Name; 'Tag Value' = [string]$Tag.Value; } $obj if ($ResUCount -eq 1) { $ResUCount = 0 } } } $tmp } } Else { if ($SmaResources) { $TableName = ('ArcSrvOperDataTable_' + (($SmaResources.'Resource U' | Measure-Object -Sum).Sum)) $Style = New-ExcelStyle -HorizontalAlignment Left -AutoSize -NumberFormat '0' $Cond = New-ConditionalText -ConditionalType ContainsText 'Disconnected' -ConditionalTextColor ([System.Drawing.Color]::White) -BackgroundColor ([System.Drawing.Color]::Red) $Cond2 = New-ConditionalText -ConditionalType ContainsText 'Connected' -ConditionalTextColor ([System.Drawing.Color]::FromArgb(0,176,80)) -BackgroundColor ([System.Drawing.Color]::White) $Cond3 = New-ConditionalText -ConditionalType ContainsText 'No' -ConditionalTextColor ([System.Drawing.Color]::FromArgb(255,165,0)) -BackgroundColor ([System.Drawing.Color]::White) $Exc = New-Object System.Collections.Generic.List[System.Object] $Exc.Add('Subscription') $Exc.Add('Resource Group') $Exc.Add('Machine Name') $Exc.Add('Location') $Exc.Add('Connection Status') $Exc.Add('OS Name') $Exc.Add('OS Version') $Exc.Add('OS SKU') $Exc.Add('Arc Agent Version') $Exc.Add('Last Status Change') $Exc.Add('Agent Errors') $Exc.Add('Extensions Count') $Exc.Add('Extensions Installed') $Exc.Add('Azure Monitor Agent') $Exc.Add('Backup Enabled') $Exc.Add('Last Backup') $Exc.Add('Advisor Recs Total') $Exc.Add('Advisor Security Recs') $Exc.Add('Pending Critical Patches') $Exc.Add('Last Patch Assessment') $Exc.Add('Tag: Environment') $Exc.Add('Tag: Owner') $Exc.Add('Tag: Cost Center') $Exc.Add('Tag: Physical Location') $Exc.Add('Resource U') if ($InTag) { $Exc.Add('Tag Name'); $Exc.Add('Tag Value') } [PSCustomObject]$SmaResources | ForEach-Object { $_ } | Select-Object $Exc | Export-Excel -Path $File ` -WorksheetName 'Arc Server Operational Data' ` -AutoSize -MaxAutoSizeRows 100 ` -ConditionalText $Cond, $Cond2, $Cond3 ` -TableName $TableName -TableStyle $TableStyle -Style $Style } } |