tests/maproom/scripts/New-RangerSyntheticManifest.ps1
|
<# .SYNOPSIS Generates a synthetic Ranger manifest fixture using IIC (Infinite Improbability Corp) fictional company data for simulation testing. .DESCRIPTION Modeled on the Azure Scout New-SyntheticSampleReport.ps1 pattern. Produces tests/maproom/Fixtures/synthetic-manifest.json without any live connections, Az module, or WinRM sessions. All company data follows the mandatory IIC canonical standard defined in: https://azurelocal.github.io/standards/examples .EXAMPLE .\tests\maproom\scripts\New-RangerSyntheticManifest.ps1 .NOTES Output: tests/maproom/Fixtures/synthetic-manifest.json Schema: 1.1.0-draft / mode: as-built #> [CmdletBinding()] param( [string]$OutputPath = (Join-Path $PSScriptRoot '..\Fixtures\synthetic-manifest.json') ) Set-StrictMode -Version Latest # ============================================================================= # IIC REFERENCE DATA POOLS # All data follows the mandatory IIC (Infinite Improbability Corp) standard. # Domain: iic.local | NetBIOS: IMPROBABLE | Public: improbability.cloud # ============================================================================= $iic = @{ Company = 'Infinite Improbability Corp' Abbreviation = 'IIC' Domain = 'iic.local' NetBIOS = 'IMPROBABLE' PublicDomain = 'improbability.cloud' EntraTenant = 'improbability.onmicrosoft.com' # Cluster ClusterName = 'azlocal-iic-01' NodeNames = @('azl-iic-n01', 'azl-iic-n02', 'azl-iic-n03') NodeFqdns = @('azl-iic-n01.iic.local', 'azl-iic-n02.iic.local', 'azl-iic-n03.iic.local') NodeIPs = @('10.0.0.11', '10.0.0.12', '10.0.0.13') IdracIPs = @('10.245.64.11', '10.245.64.12', '10.245.64.13') ServiceTags = @('ABC1234', 'ABC1235', 'ABC1236') HardwareModel = 'PowerEdge R760' HardwareMfr = 'Dell Inc.' FirmwareVersion = '2.10.0.0' # Azure Platform TenantId = '00000000-0000-0000-0000-000000000000' SubscriptionId = '33333333-3333-3333-3333-333333333333' ResourceGroup = 'rg-iic-compute-01' ArcRG = 'rg-iic-arc-01' MonitorRG = 'rg-iic-monitor-01' SecurityRG = 'rg-iic-security-01' KeyVault = 'kv-iic-platform' LogAnalytics = 'law-iic-monitor-01' DCR = 'dcr-iic-azl-01' DCE = 'dce-iic-azl-01' ArcGateway = 'arcgw-iic-01' CustomLocation = 'cl-iic-azlocal-01' ResourceBridge = 'rb-iic-azlocal-01' # VLANs VlanMgmt = 2203 VlanMgmtCidr = '10.0.0.0/24' VlanStorage = 2204 VlanStorageCidr = '10.0.1.0/24' VlanWorkload = 2205 VlanWorkloadCidr = '10.0.2.0/24' DnsServers = @('192.168.10.10', '192.168.10.11') Gateway = '10.0.0.1' # CSV / Storage StoragePoolName = 'S2D on azlocal-iic-01' CsvNames = @('csv-iic-azlocal-01-vmstore-01', 'csv-iic-azlocal-01-vmstore-02', 'csv-iic-azlocal-01-vmstore-03') # VMs AvdVmNames = @('avd-iic-sh01', 'avd-iic-sh02', 'avd-iic-sh03') ArcVmNames = @('arc-iic-vm01', 'arc-iic-vm02') # Accounts LcmAccount = 'lcm-iic-azl-clus01' DomainJoin = 'IMPROBABLE\svc.iic.deploy' LocalAdmin = 'Administrator' OuPath = 'OU=Clusters,OU=Servers,DC=iic,DC=local' } # ============================================================================= # HELPER — build a cert that expires in 60 days (triggers warning finding) # ============================================================================= $certExpiry60Days = (Get-Date).AddDays(60).ToString('yyyy-MM-ddTHH:mm:ssZ') $certExpiry2Years = (Get-Date).AddDays(730).ToString('yyyy-MM-ddTHH:mm:ssZ') $runStart = '2026-04-06T12:00:00Z' $runEnd = '2026-04-06T12:08:32Z' # ============================================================================= # BUILD: run block # ============================================================================= $run = [ordered]@{ toolVersion = '0.2.0' schemaVersion = '1.1.0-draft' startTimeUtc = $runStart endTimeUtc = $runEnd mode = 'as-built' runner = 'RANGER-SYNTH' includeDomains = @() excludeDomains = @() selectedCollectors = @( 'topology-cluster', 'hardware', 'storage-networking', 'workload-identity-azure', 'monitoring-observability', 'management-performance' ) schemaValidation = [ordered]@{ isValid = $true errors = @() warnings = @() } } # ============================================================================= # BUILD: target block # ============================================================================= $target = [ordered]@{ environmentLabel = 'iic-azlocal-01' clusterName = $iic.ClusterName clusterFqdn = "$($iic.ClusterName).$($iic.Domain)" resourceGroup = $iic.ResourceGroup subscriptionId = $iic.SubscriptionId tenantId = $iic.TenantId nodeList = $iic.NodeFqdns } # ============================================================================= # BUILD: topology block # ============================================================================= $topology = [ordered]@{ deploymentType = 'hyperconverged' identityMode = 'ad' controlPlaneMode = 'connected' storageArchitecture = 'storage-spaces-direct' networkArchitecture = 'switched' variantMarkers = @('connected') } # ============================================================================= # BUILD: collectors block # ============================================================================= $collectors = [ordered]@{ 'topology-cluster' = [ordered]@{ status = 'success' } 'hardware' = [ordered]@{ status = 'success' } 'storage-networking' = [ordered]@{ status = 'success' } 'workload-identity-azure' = [ordered]@{ status = 'success' } 'monitoring-observability' = [ordered]@{ status = 'success' } 'management-performance' = [ordered]@{ status = 'success' } } # ============================================================================= # BUILD: domains.clusterNode # ============================================================================= $nodes = @( [ordered]@{ name = $iic.NodeNames[0] fqdn = $iic.NodeFqdns[0] state = 'Up' uptimeHours = 312.5 osCaption = 'Microsoft Azure Stack HCI' osVersion = '10.0.25398.1189' manufacturer = $iic.HardwareMfr model = $iic.HardwareModel totalMemoryGiB = 512 logicalProcessorCount = 64 partOfDomain = $true domain = $iic.Domain biosVersion = '2.10.0.0' } [ordered]@{ name = $iic.NodeNames[1] fqdn = $iic.NodeFqdns[1] state = 'Up' uptimeHours = 311.8 osCaption = 'Microsoft Azure Stack HCI' osVersion = '10.0.25398.1189' manufacturer = $iic.HardwareMfr model = $iic.HardwareModel totalMemoryGiB = 512 logicalProcessorCount = 64 partOfDomain = $true domain = $iic.Domain biosVersion = '2.10.0.0' } [ordered]@{ name = $iic.NodeNames[2] fqdn = $iic.NodeFqdns[2] state = 'Up' uptimeHours = 310.2 osCaption = 'Microsoft Azure Stack HCI' osVersion = '10.0.25398.1189' manufacturer = $iic.HardwareMfr model = $iic.HardwareModel totalMemoryGiB = 512 logicalProcessorCount = 64 partOfDomain = $true domain = $iic.Domain biosVersion = '2.10.0.0' } ) $csvItems = $iic.CsvNames | ForEach-Object { [ordered]@{ name = $_; state = 'Online'; ownerNode = $iic.NodeNames[0] } } $clusterNode = [ordered]@{ cluster = [ordered]@{ name = $iic.ClusterName id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' domain = $iic.Domain clusterFunctionalLevel = 11 s2dEnabled = $true dynamicQuorum = $true registrationConfigured = $true productName = 'Microsoft Azure Stack HCI' displayVersion = '23H2' releaseId = '2311' currentBuild = '25398' editionId = 'ServerAzureStackHCICor' installationType = 'Server Core' operatingModel = [ordered]@{ deploymentType = 'hyperconverged' identityMode = 'ad' controlPlaneMode = 'connected' } registration = [ordered]@{ subscriptionId = $iic.SubscriptionId resourceGroup = $iic.ResourceGroup tenantId = $iic.TenantId } licensing = @() } nodes = $nodes quorum = [ordered]@{ quorumType = 'CloudWitness' quorumResource = 'Cloud Witness' quorumResourcePath = 'iic-witness.blob.core.windows.net' } faultDomains = @( [ordered]@{ name = 'Rack-IIC-01'; faultDomainType = 'Rack'; location = 'IIC Primary DC Rack 01' } ) networks = @( [ordered]@{ name = 'Management'; role = 1; address = '10.0.0.0'; addressMask = '255.255.255.0'; state = 'Up'; metric = 100 } [ordered]@{ name = 'Storage-1'; role = 2; address = '10.0.1.0'; addressMask = '255.255.255.0'; state = 'Up'; metric = 200 } [ordered]@{ name = 'Storage-2'; role = 2; address = '10.0.1.128';addressMask = '255.255.255.128';state = 'Up'; metric = 200 } [ordered]@{ name = 'Workload'; role = 3; address = '10.0.2.0'; addressMask = '255.255.255.0'; state = 'Up'; metric = 300 } ) roles = @( [ordered]@{ name = 'Cluster Group'; groupType = 'ClusterGroup'; state = 'Online'; ownerNode = $iic.NodeNames[0] } [ordered]@{ name = 'Available Storage'; groupType = 'ClusterGroup'; state = 'Online'; ownerNode = $iic.NodeNames[1] } [ordered]@{ name = "$($iic.CsvNames[0])"; groupType = 'ClusterSharedVolume';state = 'Online'; ownerNode = $iic.NodeNames[0] } [ordered]@{ name = "$($iic.CsvNames[1])"; groupType = 'ClusterSharedVolume';state = 'Online'; ownerNode = $iic.NodeNames[1] } [ordered]@{ name = "$($iic.CsvNames[2])"; groupType = 'ClusterSharedVolume';state = 'Online'; ownerNode = $iic.NodeNames[2] } ) csvSummary = [ordered]@{ count = 3 items = @($csvItems) } updatePosture = [ordered]@{ clusterAwareUpdating = [ordered]@{ clusterName = $iic.ClusterName maxRetriesPerNode = 3 requireAllNodesOnline = $true startDate = '2026-04-01T02:00:00Z' daysOfWeek = 'Tuesday' } lifecycleServices = @( [ordered]@{ name = 'ClusSvc'; displayName = 'Cluster Service'; status = 'Running'; startType = 'Automatic' } [ordered]@{ name = 'CauService'; displayName = 'Cluster-Aware Updating'; status = 'Running'; startType = 'Manual' } ) validationReports = @( [ordered]@{ name = 'Test-Cluster-2026-03-28.htm'; lastWriteTime = '2026-03-28T04:12:00Z'; length = 245760 } ) } eventSummary = @( [ordered]@{ timeCreated = '2026-04-06T08:14:32Z'; id = 1135; levelDisplayName = 'Warning'; providerName = 'Microsoft-Windows-FailoverClustering'; message = 'Cluster network Management lost quorum.' } [ordered]@{ timeCreated = '2026-04-06T07:02:11Z'; id = 1064; levelDisplayName = 'Information'; providerName = 'Microsoft-Windows-FailoverClustering'; message = 'Cluster network connection restored.' } [ordered]@{ timeCreated = '2026-04-05T22:00:05Z'; id = 1656; levelDisplayName = 'Information'; providerName = 'Microsoft-Windows-FailoverClustering'; message = 'CAU update pass completed successfully.' } ) healthSummary = [ordered]@{ totalNodes = 3 healthyNodes = 3 unhealthy = 0 } nodeSummary = [ordered]@{ manufacturers = @([ordered]@{ name = $iic.HardwareMfr; count = 3 }) models = @([ordered]@{ name = $iic.HardwareModel; count = 3 }) totalMemoryGiB = 1536 totalLogicalCpu = 192 domainJoinedNodes = 3 localIdentityNodes = 0 } faultDomainSummary = [ordered]@{ count = 1 byType = @([ordered]@{ name = 'Rack'; count = 1 }) locations = @([ordered]@{ name = 'IIC Primary DC Rack 01'; count = 1 }) } networkSummary = [ordered]@{ clusterNetworkCount = 4 byRole = @( [ordered]@{ name = '1'; count = 1 } [ordered]@{ name = '2'; count = 2 } [ordered]@{ name = '3'; count = 1 } ) switched = $true } } # ============================================================================= # BUILD: domains.hardware # ============================================================================= $hardwareNodes = 0..2 | ForEach-Object { $i = $_ [ordered]@{ node = $iic.NodeNames[$i] model = $iic.HardwareModel manufacturer = $iic.HardwareMfr serviceTag = $iic.ServiceTags[$i] ipmiAddress = $iic.IdracIPs[$i] firmwareVersion = $iic.FirmwareVersion biosVersion = $iic.FirmwareVersion secureBoot = $true tpmPresent = $true tpmVersion = '2.0' totalMemoryGiB = 512 processorCount = 2 processorSockets = 2 processorModel = 'Intel Xeon Gold 6448Y' logicalCoreCount = 64 } } $hardware = [ordered]@{ nodes = @($hardwareNodes) summary = [ordered]@{ nodeCount = 3 manufacturers = @([ordered]@{ name = $iic.HardwareMfr; count = 3 }) models = @([ordered]@{ name = $iic.HardwareModel; count = 3 }) firmwareNodes = 3 totalMemoryGiB = 1536 totalProcessors = 6 } firmware = [ordered]@{ managedNodes = 3 versions = @([ordered]@{ name = $iic.FirmwareVersion; count = 3 }) } security = [ordered]@{ trustedModuleNodes = 3 secureBootEnabledNodes = 3 } } # ============================================================================= # BUILD: domains.storage (24 NVMe disks: 8/node, 3 CSVs, 3 virtual disks) # ============================================================================= $physicalDisks = @( foreach ($nodeIdx in 0..2) { foreach ($diskIdx in 0..7) { $diskNum = ($nodeIdx * 8) + $diskIdx + 1 [ordered]@{ friendlyName = "PhysicalDisk $($diskNum.ToString('000'))" serialNumber = "IIC-SN-$($iic.ServiceTags[$nodeIdx])-D$($diskIdx.ToString('00'))" mediaType = 'NVMe' size = 1920396288000 healthStatus = 'Healthy' operationalStatus = 'OK' canPool = $false usage = 'Auto-Select' node = $iic.NodeNames[$nodeIdx] } } } ) $virtualDisks = $iic.CsvNames | ForEach-Object { [ordered]@{ friendlyName = $_ resiliencyName = 'Mirror' size = 2147483648000 allocatedSize = 2000000000000 healthStatus = 'Healthy' operationalStatus = 'OK' numberOfColumns = 3 interleave = 65536 } } $csvObjects = $iic.CsvNames | ForEach-Object { [ordered]@{ name = "Cluster Virtual Disk ($_)" state = 'Online' ownerNode = $iic.NodeNames[0] path = "C:\ClusterStorage\$_" } } $storage = [ordered]@{ pools = @( [ordered]@{ friendlyName = $iic.StoragePoolName healthStatus = 'Healthy' operationalStatus = 'OK' size = 46244158668800 allocatedSize = 6442450944000 isReadOnly = $false isPrimordial = $false } ) physicalDisks = @($physicalDisks) virtualDisks = @($virtualDisks) volumes = @( [ordered]@{ driveLetter = 'C'; fileSystemLabel = 'OS'; sizeGB = 128; freeSpaceGB = 62 } [ordered]@{ driveLetter = $null; fileSystemLabel = $iic.CsvNames[0]; sizeGB = 2000; freeSpaceGB = 1480 } [ordered]@{ driveLetter = $null; fileSystemLabel = $iic.CsvNames[1]; sizeGB = 2000; freeSpaceGB = 1620 } [ordered]@{ driveLetter = $null; fileSystemLabel = $iic.CsvNames[2]; sizeGB = 2000; freeSpaceGB = 1710 } ) csvs = @($csvObjects) qos = @() sofs = @() replica = @() summary = [ordered]@{ poolCount = 1 physicalDiskCount = 24 virtualDiskCount = 3 volumeCount = 4 csvCount = 3 totalPoolCapacityGiB = 43008 allocatedPoolCapacityGiB = 6144 diskMediaTypes = @([ordered]@{ name = 'NVMe'; count = 24 }) unhealthyDisks = 0 } } # ============================================================================= # BUILD: domains.networking (4 adapters/node, ATC intents, IIC VLANs) # ============================================================================= $adapters = @( foreach ($nodeIdx in 0..2) { $node = $iic.NodeNames[$nodeIdx] foreach ($adapterName in @('NIC1', 'NIC2', 'NIC3', 'NIC4')) { [ordered]@{ node = $node name = $adapterName speed = 25000000000 mediaType = 'Ethernet' status = 'Up' macAddress = "00:50:56:IIC:$($nodeIdx):$(([array]::IndexOf(@('NIC1','NIC2','NIC3','NIC4'), $adapterName)).ToString('00'))" vlanId = if ($adapterName -in @('NIC1', 'NIC2')) { $iic.VlanMgmt } elseif ($adapterName -eq 'NIC3') { $iic.VlanStorage } else { $iic.VlanWorkload } ipAddress = if ($adapterName -eq 'NIC1') { $iic.NodeIPs[$nodeIdx] } else { $null } } } } ) $networking = [ordered]@{ nodes = @($iic.NodeNames | ForEach-Object { [ordered]@{ node = $_ } }) clusterNetworks = @( [ordered]@{ name = 'Management'; address = '10.0.0.0'; subnetMask = '255.255.255.0'; role = 1; state = 'Up' } [ordered]@{ name = 'Storage-1'; address = '10.0.1.0'; subnetMask = '255.255.255.0'; role = 2; state = 'Up' } [ordered]@{ name = 'Storage-2'; address = '10.0.1.128'; subnetMask = '255.255.255.128'; role = 2; state = 'Up' } [ordered]@{ name = 'Workload'; address = '10.0.2.0'; subnetMask = '255.255.255.0'; role = 3; state = 'Up' } ) adapters = @($adapters) vSwitches = @( [ordered]@{ name = 'ConvergedSwitch'; switchType = 'External'; allowManagementOS = $true; embeddedTeaming = $true } [ordered]@{ name = 'StorageSwitch'; switchType = 'External'; allowManagementOS = $false; embeddedTeaming = $true } ) hostVirtualNics = @( [ordered]@{ name = 'vManagement'; switchName = 'ConvergedSwitch'; vlanId = $iic.VlanMgmt; ipAddress = '10.0.0.11' } ) intents = @( [ordered]@{ name = 'ManagementCompute'; trafficType = @('Management', 'Compute'); overrideAdapterProperty = $true } [ordered]@{ name = 'StorageIntent'; trafficType = @('Storage'); overrideAdapterProperty = $true } ) dns = @( [ordered]@{ server = $iic.DnsServers[0]; reachable = $true } [ordered]@{ server = $iic.DnsServers[1]; reachable = $true } ) proxy = @() firewall = @() sdn = @() summary = [ordered]@{ nodeCount = 3 clusterNetworkCount = 4 adapterCount = 12 adapterStates = @([ordered]@{ name = 'Up'; count = 12 }) vSwitchCount = 2 intentCount = 2 dnsServers = $iic.DnsServers proxyConfiguredNodes = 0 sdnControllerCount = 0 } } # ============================================================================= # BUILD: domains.virtualMachines (3 AVD session hosts + 2 Arc VMs) # ============================================================================= $vmInventory = @( # AVD session hosts foreach ($i in 0..2) { $vmName = $iic.AvdVmNames[$i] $hostNode = $iic.NodeNames[$i] [ordered]@{ name = $vmName hostNode = $hostNode state = 'Running' isClustered = $true generation = 2 processorCount = 8 memoryAssignedMb = 16384 dynamicMemory = $true diskCount = 2 networkAdapterCount = 1 storagePaths = @("C:\ClusterStorage\$($iic.CsvNames[$i])\VMs\$vmName\$vmName.vhdx") switchNames = @('ConvergedSwitch') replicationMode = $null replicationHealth = $null workloadFamily = 'AVD' } } # Arc VMs foreach ($i in 0..1) { $vmName = $iic.ArcVmNames[$i] $hostNode = $iic.NodeNames[$i] [ordered]@{ name = $vmName hostNode = $hostNode state = 'Running' isClustered = $true generation = 2 processorCount = 4 memoryAssignedMb = 8192 dynamicMemory = $false diskCount = 1 networkAdapterCount = 1 storagePaths = @("C:\ClusterStorage\$($iic.CsvNames[$i])\VMs\$vmName\$vmName.vhdx") switchNames = @('ConvergedSwitch') replicationMode = $null replicationHealth = $null workloadFamily = 'Arc VMs' } } ) $vmPlacement = $vmInventory | ForEach-Object { [ordered]@{ vm = $_.name; hostNode = $_.hostNode; state = $_.state } } $virtualMachines = [ordered]@{ inventory = @($vmInventory) placement = @($vmPlacement) workloadFamilies = @( [ordered]@{ name = 'AVD'; count = 3 } [ordered]@{ name = 'Arc VMs'; count = 2 } ) replication = @() summary = [ordered]@{ totalVms = 5 runningVms = 5 clusteredVms = 5 totalAssignedMemoryGb = 80 byGeneration = @([ordered]@{ name = '2'; count = 5 }) byState = @([ordered]@{ name = 'Running'; count = 5 }) } } # ============================================================================= # BUILD: domains.identitySecurity (1 cert expiring in 60 days → warning finding) # ============================================================================= $identityNodes = 0..2 | ForEach-Object { $i = $_ [ordered]@{ node = $iic.NodeNames[$i] partOfDomain = $true domain = $iic.Domain credSsp = $false } } $certificates = 0..2 | ForEach-Object { $i = $_ $items = if ($i -eq 0) { # Node 0 has a cert expiring in 60 days (triggers warning) @( [ordered]@{ subject = "CN=$($iic.NodeNames[$i]).$($iic.Domain)"; thumbprint = "AAABBB$($i)01"; notAfter = $certExpiry60Days } [ordered]@{ subject = "CN=$($iic.ClusterName).$($iic.Domain)"; thumbprint = "AAABBB$($i)02"; notAfter = $certExpiry2Years } ) } else { @( [ordered]@{ subject = "CN=$($iic.NodeNames[$i]).$($iic.Domain)"; thumbprint = "AAABBB$($i)01"; notAfter = $certExpiry2Years } ) } [ordered]@{ node = $iic.NodeNames[$i]; items = $items } } $posture = 0..2 | ForEach-Object { $i = $_ [ordered]@{ node = $iic.NodeNames[$i] defender = [ordered]@{ antivirusEnabled = $true; realTimeProtectionEnabled = $true; aMRunningMode = 'Normal' } deviceGuard = [ordered]@{ virtualizationBasedSecurityStatus = 2 } bitlocker = @([ordered]@{ mountPoint = 'C:'; protectionStatus = 'On' }) secureBoot = $true appLocker = @() } } $localAdmins = 0..2 | ForEach-Object { $i = $_ [ordered]@{ node = $iic.NodeNames[$i] members = @( [ordered]@{ name = "$($iic.NetBIOS)\Domain Admins"; objectClass = 'Group' } [ordered]@{ name = "$($iic.NetBIOS)\$($iic.LcmAccount)"; objectClass = 'User' } ) } } $auditPolicy = 0..2 | ForEach-Object { [ordered]@{ node = $iic.NodeNames[$_] values = @('Logon/Logoff Success and Failure', 'Account Management Success and Failure', 'Privilege Use Success') } } $activeDirectoryPerNode = 0..2 | ForEach-Object { $i = $_ [ordered]@{ node = $iic.NodeNames[$i] domain = [ordered]@{ DNSRoot = $iic.Domain NetBIOSName = $iic.NetBIOS DomainMode = 'Windows2016Domain' DistinguishedName = 'DC=iic,DC=local' ParentDomain = $null } forest = [ordered]@{ Name = $iic.Domain ForestMode = 'Windows2016Forest' RootDomain = $iic.Domain } } } $keyVaultPerNode = 0..2 | ForEach-Object { [ordered]@{ node = $iic.NodeNames[$_] references = @("keyvault://$($iic.KeyVault)/local-admin-password", "keyvault://$($iic.KeyVault)/domain-join-password") } } $identitySecurity = [ordered]@{ nodes = @($identityNodes) certificates = @($certificates) posture = @($posture) localAdmins = @($localAdmins) auditPolicy = @($auditPolicy) activeDirectory = @($activeDirectoryPerNode) keyVault = @($keyVaultPerNode) summary = [ordered]@{ nodeCount = 3 domainJoinedNodes = 3 credSspEnabledNodes = 0 defenderProtectedNodes = 3 bitLockerProtectedNodes = 3 certificateCount = 4 certificateExpiringWithin90Days = 1 appLockerNodes = 0 secureBootEnabledNodes = 3 } } # ============================================================================= # BUILD: domains.azureIntegration # ============================================================================= $arcMachineResources = $iic.NodeNames | ForEach-Object { [ordered]@{ name = $_ resourceType = 'Microsoft.HybridCompute/machines' resourceGroupName = $iic.ArcRG location = 'eastus' } } $azureResources = @( [ordered]@{ name = $iic.ClusterName; resourceType = 'Microsoft.AzureStackHCI/clusters'; resourceGroupName = $iic.ResourceGroup; location = 'eastus' } [ordered]@{ name = 'avd-iic-hostpool-01'; resourceType = 'Microsoft.DesktopVirtualization/hostPools'; resourceGroupName = $iic.ResourceGroup; location = 'eastus' } [ordered]@{ name = $iic.ResourceBridge; resourceType = 'Microsoft.ResourceConnector/appliances'; resourceGroupName = $iic.ArcRG; location = 'eastus' } [ordered]@{ name = $iic.CustomLocation; resourceType = 'Microsoft.ExtendedLocation/customLocations'; resourceGroupName = $iic.ArcRG; location = 'eastus' } [ordered]@{ name = 'rsv-iic-azlocal-01'; resourceType = 'Microsoft.RecoveryServices/vaults'; resourceGroupName = $iic.ResourceGroup; location = 'eastus' } [ordered]@{ name = 'aum-iic-azlocal-01'; resourceType = 'Microsoft.Maintenance/configurationAssignments'; resourceGroupName = $iic.ResourceGroup; location = 'eastus' } [ordered]@{ name = $iic.LogAnalytics; resourceType = 'Microsoft.OperationalInsights/workspaces'; resourceGroupName = $iic.MonitorRG; location = 'eastus' } ) + $arcMachineResources $azureIntegration = [ordered]@{ context = [ordered]@{ subscriptionId = $iic.SubscriptionId resourceGroup = $iic.ResourceGroup tenantId = $iic.TenantId } resources = @($azureResources) services = @( [ordered]@{ category = 'Microsoft.AzureStackHCI/clusters'; count = 1; name = 'Microsoft.AzureStackHCI/clusters' } [ordered]@{ category = 'Microsoft.DesktopVirtualization/hostPools'; count = 1; name = 'Microsoft.DesktopVirtualization/hostPools' } [ordered]@{ category = 'Microsoft.HybridCompute/machines'; count = 3; name = 'Microsoft.HybridCompute/machines' } [ordered]@{ category = 'Microsoft.ResourceConnector/appliances'; count = 1; name = 'Microsoft.ResourceConnector/appliances' } [ordered]@{ category = 'Microsoft.ExtendedLocation/customLocations'; count = 1; name = 'Microsoft.ExtendedLocation/customLocations' } [ordered]@{ category = 'Microsoft.RecoveryServices/vaults'; count = 1; name = 'Microsoft.RecoveryServices/vaults' } [ordered]@{ category = 'Microsoft.OperationalInsights/workspaces'; count = 1; name = 'Microsoft.OperationalInsights/workspaces' } ) policy = @( [ordered]@{ name = 'iic-tagging-policy'; displayName = 'IIC: Enforce resource tags'; scope = "/subscriptions/$($iic.SubscriptionId)/resourceGroups/$($iic.ResourceGroup)"; enforcementMode = 'Default' } [ordered]@{ name = 'iic-audit-policy'; displayName = 'IIC: Audit security controls'; scope = "/subscriptions/$($iic.SubscriptionId)/resourceGroups/$($iic.ResourceGroup)"; enforcementMode = 'Default' } ) backup = @( [ordered]@{ name = 'rsv-iic-azlocal-01'; resourceType = 'Microsoft.RecoveryServices/vaults' } ) update = @( [ordered]@{ name = 'aum-iic-azlocal-01'; resourceType = 'Microsoft.Maintenance/configurationAssignments' } ) cost = @() resourceBridge = @( [ordered]@{ name = $iic.ResourceBridge; resourceType = 'Microsoft.ResourceConnector/appliances'; resourceGroupName = $iic.ArcRG; location = 'eastus' } ) customLocations = @( [ordered]@{ name = $iic.CustomLocation; resourceType = 'Microsoft.ExtendedLocation/customLocations'; resourceGroupName = $iic.ArcRG; location = 'eastus' } ) extensions = @( [ordered]@{ name = 'AzureMonitorWindowsAgent'; resourceType = 'Microsoft.AzureStackHCI/clusters/extensions' } [ordered]@{ name = 'AzureEdgeTelemetryAndDiagnostics'; resourceType = 'Microsoft.AzureStackHCI/clusters/extensions' } ) arcMachines = @($arcMachineResources) siteRecovery = @() resourceSummary = [ordered]@{ totalResources = @($azureResources).Count byType = @( [ordered]@{ name = 'Microsoft.AzureStackHCI/clusters'; count = 1 } [ordered]@{ name = 'Microsoft.DesktopVirtualization/hostPools'; count = 1 } [ordered]@{ name = 'Microsoft.HybridCompute/machines'; count = 3 } [ordered]@{ name = 'Microsoft.ResourceConnector/appliances'; count = 1 } [ordered]@{ name = 'Microsoft.ExtendedLocation/customLocations'; count = 1 } [ordered]@{ name = 'Microsoft.RecoveryServices/vaults'; count = 1 } [ordered]@{ name = 'Microsoft.OperationalInsights/workspaces'; count = 1 } ) byLocation = @([ordered]@{ name = 'eastus'; count = @($azureResources).Count }) azureArcMachines = 3 hciClusterRegistrations = 1 backupResources = 1 updateResources = 1 resourceBridgeCount = 1 customLocationCount = 1 extensionCount = 2 } resourceLocations = @([ordered]@{ name = 'eastus'; count = @($azureResources).Count }) policySummary = [ordered]@{ assignmentCount = 2 enforcementModes = @([ordered]@{ name = 'Default'; count = 2 }) } auth = [ordered]@{ method = 'service-principal' tenantId = $iic.TenantId subscriptionId = $iic.SubscriptionId azureCliFallback = $false } } # ============================================================================= # BUILD: domains.monitoring (AMA on 3 nodes, DCR, DCE, LAW, 2 alerts) # ============================================================================= $amaAgents = $iic.NodeNames | ForEach-Object { [ordered]@{ name = 'AzureMonitorWindowsAgent'; node = $_; status = 'Running'; version = '1.22.2.0' } } $healthPerNode = $iic.NodeNames | ForEach-Object { [ordered]@{ node = $_; healthServiceRunning = $true; reportCount = 12 } } $monitoring = [ordered]@{ telemetry = @( [ordered]@{ name = 'AzureEdgeTelemetryAndDiagnostics'; status = 'Succeeded'; version = '1.0.6' } ) ama = @($amaAgents) dcr = @( [ordered]@{ name = $iic.DCR resourceGroup = $iic.MonitorRG location = 'eastus' dataFlows = @('Windows Event Log', 'Performance Counters') } ) dce = @( [ordered]@{ name = $iic.DCE resourceGroup = $iic.MonitorRG location = 'eastus' endpoint = "https://$($iic.DCE).eastus-1.ingest.monitor.azure.com" } ) insights = @( [ordered]@{ name = $iic.LogAnalytics resourceGroup = $iic.MonitorRG location = 'eastus' workspaceId = "/subscriptions/55555555-5555-5555-5555-555555555555/resourceGroups/$($iic.MonitorRG)/providers/Microsoft.OperationalInsights/workspaces/$($iic.LogAnalytics)" retentionDays = 90 } ) alerts = @( [ordered]@{ name = 'alert-iic-azl-critical-cpu'; severity = 0; state = 'Enabled'; condition = 'CPU > 85%' } [ordered]@{ name = 'alert-iic-azl-storage-capacity'; severity = 1; state = 'Enabled'; condition = 'Used capacity > 80%' } ) health = @($healthPerNode) updateManager = @( [ordered]@{ name = 'aum-iic-azlocal-01'; resourceType = 'Microsoft.Maintenance/configurationAssignments' } ) summary = [ordered]@{ telemetryCount = 1 amaCount = 3 dcrCount = 1 dceCount = 1 alertCount = 2 updateManagerCount = 1 healthServiceRunningNodes = 3 } } # ============================================================================= # BUILD: domains.managementTools # ============================================================================= $mgmtToolServices = @( 'ServerManagementGateway', 'HealthService', 'WinRM', 'MicrosoftMonitoringAgent' ) | ForEach-Object { [ordered]@{ name = $_; status = 'Running' } } $mgmtAgents = $iic.NodeNames | ForEach-Object { [ordered]@{ name = 'HealthService'; node = $_; status = 'Running' } } $managementTools = [ordered]@{ tools = @($mgmtToolServices) agents = @($mgmtAgents) summary = [ordered]@{ totalServices = @($mgmtToolServices).Count runningServices = @($mgmtToolServices).Count serviceNames = @($mgmtToolServices | ForEach-Object { [ordered]@{ name = $_.name; count = 1 } }) } } # ============================================================================= # BUILD: domains.performance # ============================================================================= $perfCompute = 0..2 | ForEach-Object { $i = $_ $cpu = @(35, 42, 38)[$i] [ordered]@{ node = $iic.NodeNames[$i]; metrics = [ordered]@{ cpuUtilizationPercent = $cpu; availableMemoryMb = 180224 } } } $performance = [ordered]@{ nodes = $iic.NodeNames compute = @($perfCompute) storage = @($iic.NodeNames | ForEach-Object { [ordered]@{ node = $_; metrics = @() } }) networking = @($iic.NodeNames | ForEach-Object { [ordered]@{ node = $_; metrics = @() } }) outliers = @() events = @($iic.NodeNames | ForEach-Object { [ordered]@{ node = $_; values = @() } }) summary = [ordered]@{ averageCpuUtilizationPercent = 38.3 averageAvailableMemoryMb = 180224 runningManagementServices = @($mgmtToolServices).Count toolNames = @($mgmtToolServices | ForEach-Object { [ordered]@{ name = $_.name; count = 1 } }) highCpuNodes = 0 eventSeverities = @() } } # ============================================================================= # BUILD: domains.oemIntegration # ============================================================================= $oemEndpoints = 0..2 | ForEach-Object { $i = $_ [ordered]@{ host = "idrac-$($iic.NodeNames[$i]).$($iic.Domain)" node = $iic.NodeNames[$i] ip = $iic.IdracIPs[$i] port = 443 reachable = $true } } $oemPosture = 0..2 | ForEach-Object { $i = $_ [ordered]@{ node = $iic.NodeNames[$i] managerModel = 'iDRAC9' firmwareVersion = '7.10.30.00' serviceTag = $iic.ServiceTags[$i] } } $oemIntegration = [ordered]@{ endpoints = @($oemEndpoints) managementPosture = @($oemPosture) } # ============================================================================= # BUILD: findings (2 warnings + 2 informationals) # ============================================================================= $findings = @( [ordered]@{ severity = 'warning' title = 'One or more node certificates expire within 90 days' description = "Identity posture data indicates certificate on $($iic.NodeNames[0]) expires within 90 days." affectedComponents = @($iic.NodeNames[0]) currentState = '1 certificate expiring within 90 days' recommendation = 'Review certificate ownership and renew expiring node certificates before handoff.' } [ordered]@{ severity = 'warning' title = 'iDRAC firmware below recommended baseline on all nodes' description = 'OEM integration data shows iDRAC firmware at 7.10.30.00. The recommended baseline is 7.20 or later.' affectedComponents = $iic.NodeNames currentState = 'iDRAC firmware 7.10.30.00' recommendation = 'Update iDRAC firmware via Dell OME or Lifecycle Controller during next maintenance window.' } [ordered]@{ severity = 'informational' title = 'Azure Policy assignments discovered at resource group scope' description = '2 policy assignments are enforcing tagging and security control auditing.' affectedComponents = @($iic.ResourceGroup) currentState = '2 policy assignments active' recommendation = 'Verify policy scope extends to arc resource group and monitor for compliance drift.' } [ordered]@{ severity = 'informational' title = 'Cluster event history includes a transient network quorum warning' description = 'A Management network quorum warning event was recorded on 2026-04-06. Cluster recovered automatically.' affectedComponents = @($iic.ClusterName) currentState = 'single event logged; cluster healthy' recommendation = 'Review switch port configuration to confirm management NICs are on dedicated VLANs and bonded correctly.' } ) # ============================================================================= # BUILD: relationships # ============================================================================= $relationships = @( foreach ($vm in $vmInventory) { [ordered]@{ source = [ordered]@{ type = 'cluster-node'; id = $vm.hostNode } target = [ordered]@{ type = 'virtual-machine'; id = $vm.name } relationshipType = 'hosts' properties = [ordered]@{ state = $vm.state; workloadFamily = $vm.workloadFamily } } } foreach ($nodeIdx in 0..2) { [ordered]@{ source = [ordered]@{ type = 'cluster-node'; id = $iic.NodeNames[$nodeIdx] } target = [ordered]@{ type = 'arc-machine'; id = $iic.NodeNames[$nodeIdx] } relationshipType = 'registered-as' properties = [ordered]@{ resourceGroup = $iic.ArcRG } } } [ordered]@{ source = [ordered]@{ type = 'cluster'; id = $iic.ClusterName } target = [ordered]@{ type = 'log-analytics-workspace'; id = $iic.LogAnalytics } relationshipType = 'monitored-by' properties = [ordered]@{ dcrName = $iic.DCR } } ) # ============================================================================= # ASSEMBLE MANIFEST # ============================================================================= $manifest = [ordered]@{ run = $run target = $target topology = $topology collectors = $collectors domains = [ordered]@{ clusterNode = $clusterNode hardware = $hardware storage = $storage networking = $networking virtualMachines = $virtualMachines identitySecurity = $identitySecurity azureIntegration = $azureIntegration monitoring = $monitoring managementTools = $managementTools performance = $performance oemIntegration = $oemIntegration } relationships = @($relationships) findings = @($findings) artifacts = @() evidence = @() } # ============================================================================= # WRITE # ============================================================================= $outputDir = Split-Path -Parent $OutputPath if (-not (Test-Path $outputDir)) { New-Item -ItemType Directory -Path $outputDir -Force | Out-Null } $manifest | ConvertTo-Json -Depth 20 | Set-Content -Path $OutputPath -Encoding UTF8 -Force Write-Host "[OK] Synthetic IIC manifest written to: $OutputPath" -ForegroundColor Green Write-Host " Nodes: $($iic.NodeNames -join ', ')" -ForegroundColor Cyan Write-Host " Cluster: $($iic.ClusterName)" -ForegroundColor Cyan Write-Host " Domain: $($iic.Domain)" -ForegroundColor Cyan Write-Host " Mode: as-built" -ForegroundColor Cyan Write-Host " Findings: $(@($findings | Where-Object { $_.severity -eq 'warning' }).Count) warnings, $(@($findings | Where-Object { $_.severity -eq 'informational' }).Count) informationals" -ForegroundColor Cyan |