manifests/specs/service-collectors.psd1
|
# # Collector spec for the service-coverage build-out — Epic AB#6741. # # Rendered into manifests/collectors/<Category>/<Name>.psd1 by # scripts/Build-ScoutServiceCollector.ps1. Edit HERE and regenerate; the generated definitions # say so in their own headers. # # WHAT EACH ENTRY DECLARES # # Category the manifests/collectors folder, which IS the Scout category # Name the definition file basename and the collector's identity # Worksheet the Excel sheet name — unique across every definition, <= 31 chars # ResourceTypes Resource Graph type strings, lower-case, as ARG returns them # ResourceTypeMatching omit to get SinglePass for multi-type, Grouped for single-type # AdditionalFilter optional Where-Object body over the matched rows ($_ is the resource) # Preamble optional extra statements, run per row after $data is bound # Fields the service-specific columns, in worksheet order # # The five identity columns (Subscription, Resource Group, Name, Location) plus the retirement # pair, 'Resource U' and the tag columns are added by the generator — never declare them here. # # RESOURCE TYPE PROVENANCE. Every type string below is taken from the ARM template reference on # Microsoft Learn (learn.microsoft.com/azure/templates/<provider>/<type>), lower-cased to match # what Resource Graph returns. AB#6444 found three fabricated type strings in Hybrid/ArcSites # that the fixture generator happily manufactured rows for, so a definition passing its tests # proves nothing about whether the type exists. tests/ServiceCollectorTypes.Tests.ps1 pins each # string against this spec; only a live run proves rows come back. # @{ Collectors = @( # --------------------------------------------------------------------------------------- # Migration — the only category at zero coverage (AB#6750: stories AB#6830, AB#6831). # --------------------------------------------------------------------------------------- @{ Category = 'Migration' Name = 'AzureMigrateProjects' Worksheet = 'Migrate Projects' ResourceTypes = @('microsoft.migrate/migrateprojects') Fields = @( @{ Name = 'Project Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Service Endpoint'; Expression = '$data.serviceEndpoint' } @{ Name = 'Utility Storage'; Expression = '$UtilityStorage' } ) Preamble = @' $UtilityStorage = if(![string]::IsNullOrEmpty($data.utilityStorageAccountId)){Get-AZSCIdSegment -Id $data.utilityStorageAccountId -Index 8}else{$null} '@ } @{ Category = 'Migration' Name = 'AzureMigrateAssessments' Worksheet = 'Migrate Assessment Projects' ResourceTypes = @('microsoft.migrate/assessmentprojects') Fields = @( @{ Name = 'Project Status'; Expression = '$data.projectStatus' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Assessment Solution'; Expression = '$AssessmentSolution' } @{ Name = 'Workspace Location'; Expression = '$data.customerWorkspaceLocation' } ) Preamble = @' $AssessmentSolution = if(![string]::IsNullOrEmpty($data.assessmentSolutionId)){Get-AZSCIdSegment -Id $data.assessmentSolutionId -Index 8}else{$null} '@ } @{ Category = 'Migration' Name = 'AzureMigrateDiscoverySites' Worksheet = 'Migrate Discovery Sites' ResourceTypes = @( 'microsoft.offazure/vmwaresites' 'microsoft.offazure/hypervsites' 'microsoft.offazure/serversites' 'microsoft.offazure/mastersites' ) Fields = @( @{ Name = 'Site Type'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Service Endpoint'; Expression = '$data.serviceEndpoint' } @{ Name = 'Discovery Solution'; Expression = '$DiscoverySolution' } @{ Name = 'Agent Version'; Expression = '$data.agentDetails.version' } ) Preamble = @' $DiscoverySolution = if(![string]::IsNullOrEmpty($data.discoverySolutionId)){Get-AZSCIdSegment -Id $data.discoverySolutionId -Index 8}else{$null} '@ } @{ Category = 'Migration' Name = 'DatabaseMigrationServices' Worksheet = 'Database Migration Services' ResourceTypes = @('microsoft.datamigration/services', 'microsoft.datamigration/sqlmigrationservices') Fields = @( @{ Name = 'Service Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'SKU Tier'; Expression = '$1.sku.tier' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Virtual Network'; Expression = '$VNET' } @{ Name = 'Subnet'; Expression = '$Subnet' } ) Preamble = @' $VNET = if(![string]::IsNullOrEmpty($data.virtualSubnetId)){Get-AZSCIdSegment -Id $data.virtualSubnetId -Index 8}else{$null} $Subnet = if(![string]::IsNullOrEmpty($data.virtualSubnetId)){Get-AZSCIdSegment -Id $data.virtualSubnetId -Index 10}else{$null} '@ } @{ Category = 'Migration' Name = 'DataBox' Worksheet = 'Data Box Jobs' ResourceTypes = @('microsoft.databox/jobs') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Job Status'; Expression = '$data.status' } @{ Name = 'Transfer Type'; Expression = '$data.transferType' } @{ Name = 'Delivery Type'; Expression = '$data.deliveryType' } @{ Name = 'Start Time'; Expression = '[string]$data.startTime' } @{ Name = 'Cancellation Reason'; Expression = '$data.cancellationReason' } ) } @{ Category = 'Migration' Name = 'StackEdge' Worksheet = 'Stack Edge Devices' ResourceTypes = @('microsoft.databoxedge/databoxedgedevices') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Device Status'; Expression = '$data.dataBoxEdgeDeviceStatus' } @{ Name = 'Device Type'; Expression = '$data.deviceType' } @{ Name = 'Model'; Expression = '$data.modelDescription' } @{ Name = 'Software Version'; Expression = '$data.deviceSoftwareVersion' } @{ Name = 'Serial Number'; Expression = '$data.serialNumber' } ) } # --------------------------------------------------------------------------------------- # General — the third missing category (AB#6838). Largely relocation: SupportTickets moves # here from Management, and Reservations/Subscriptions are genuinely new. # --------------------------------------------------------------------------------------- @{ Category = 'General' Name = 'Reservations' Worksheet = 'Reservations' ResourceTypes = @('microsoft.capacity/reservationorders', 'microsoft.capacity/reservationorders/reservations') Fields = @( @{ Name = 'Display Name'; Expression = '$data.displayName' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Quantity'; Expression = '[string]$data.quantity' } @{ Name = 'Term'; Expression = '$data.term' } @{ Name = 'Billing Plan'; Expression = '$data.billingPlan' } @{ Name = 'Benefit Start'; Expression = '[string]$data.benefitStartTime' } @{ Name = 'Expiry Date'; Expression = '[string]$data.expiryDate' } @{ Name = 'Renew'; Expression = '[string]$data.renew' } ) } # Quotas is a General service and Scout has ALWAYS fetched the data -- Get-ScoutVmQuotas # emits a single 'AZSC/VM/Quotas' envelope per run whose `properties` holds one entry per # subscription/region, each carrying the Get-AzVMUsage rows. Nothing rendered it. This is # the collector that finally does, so the two nested loops walk the envelope rather than # a resource list, and the identity columns come from the quota reading, not from ARM. @{ Category = 'General' Name = 'Quotas' Worksheet = 'Quotas' ResourceTypes = @('AZSC/VM/Quotas') Identity = @( @{ Name = 'ID'; Expression = '$Loc.SubId' } @{ Name = 'Subscription'; Expression = '$Loc.Subscription' } @{ Name = 'Location'; Expression = '$Loc.Location' } ) # Both loops are deliberately UNGUARDED. The per-loop `Comment` is emitted into the # generated manifest next to the loop it describes, because # tests/Collector.VanishingParent.Tests.ps1 requires the decision to be recorded there # rather than only here -- the reasoning must not drift away from the code. AdditionalRowLoops = @( @{ Variable = 'Loc' Source = '$data' Comment = @' AB#6845 decision ($Loc): deliberately UNGUARDED -- the parent must NOT become a row. This collector's "parent" is not an Azure resource at all: it is the synthetic AZSC/VM/Quotas envelope the engine builds to carry quota data, and every column on this worksheet except Resource U is read off $Loc or $Usage. An envelope with no locations would therefore emit a row that is entirely blank -- a phantom line item in a report about quota headroom, stating nothing and inviting the reader to wonder which subscription it belongs to. The behaviour is asserted, not merely skipped, in tests/Collector.VanishingParent.Tests.ps1. '@ } @{ Variable = 'Usage' Source = '$Loc.Data' Comment = @' AB#6845 decision ($Usage): deliberately UNGUARDED -- the ROW IS THE CHILD. One row per usage line is the unit this worksheet inventories, so a location envelope carrying no usage data has nothing to state and must not manufacture a row to say so. '@ } ) TagLoop = $null Fields = @( @{ Name = 'Quota'; Expression = '$Usage.Name.LocalizedValue' } @{ Name = 'Quota Name'; Expression = '$Usage.Name.Value' } @{ Name = 'Current Value'; Expression = '[string]$Usage.CurrentValue' } @{ Name = 'Limit'; Expression = '[string]$Usage.Limit' } # Computed in the field expression, not the row Preamble: the Preamble runs before # the AdditionalRowLoops bind $Usage, so a variable assigned there would read $null # for every row -- a silently empty column. @{ Name = 'Used %'; Expression = '[string]$(if($null -ne $Usage -and $Usage.Limit -and [double]$Usage.Limit -gt 0){[math]::Round((([double]$Usage.CurrentValue / [double]$Usage.Limit) * 100), 1)}else{$null})' } @{ Name = 'Unit'; Expression = '$Usage.Unit' } ) } # --------------------------------------------------------------------------------------- # DevOps — the second missing category. Epic AB#6741's acceptance criteria require all # eighteen categories to exist, and DevOps is named in its scope alongside Migration. # --------------------------------------------------------------------------------------- @{ Category = 'DevOps' Name = 'ChaosStudio' Worksheet = 'Chaos Studio' ResourceTypes = @('microsoft.chaos/experiments', 'microsoft.chaos/targets') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Step Count'; Expression = '[string]@($data.steps).Count' } @{ Name = 'Selector Count'; Expression = '[string]@($data.selectors).Count' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } ) } @{ Category = 'DevOps' Name = 'DevCenters' Worksheet = 'Dev Centers' ResourceTypes = @('microsoft.devcenter/devcenters', 'microsoft.devcenter/projects') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Dev Center'; Expression = '$DevCenter' } @{ Name = 'Dev Box Limit'; Expression = '[string]$data.maxDevBoxesPerUser' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } ) Preamble = @' $DevCenter = if(![string]::IsNullOrEmpty($data.devCenterId)){Get-AZSCIdSegment -Id $data.devCenterId -Index 8}else{$null} '@ } @{ Category = 'DevOps' Name = 'DevBoxPools' Worksheet = 'Dev Box Pools' ResourceTypes = @('microsoft.devcenter/projects/pools') Fields = @( @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Definition Name'; Expression = '$data.devBoxDefinitionName' } @{ Name = 'Network Connection'; Expression = '$data.networkConnectionName' } @{ Name = 'Local Admin'; Expression = '$data.licenseType' } @{ Name = 'Admin Status'; Expression = '$data.localAdministrator' } @{ Name = 'Stop On Disconnect'; Expression = '$data.stopOnDisconnect.status' } ) } @{ Category = 'DevOps' Name = 'DevCenterNetworkConnections' Worksheet = 'Dev Center Network Conns' ResourceTypes = @('microsoft.devcenter/networkconnections') Fields = @( @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Domain Join Type'; Expression = '$data.domainJoinType' } @{ Name = 'Domain Name'; Expression = '$data.domainName' } @{ Name = 'Virtual Network'; Expression = '$VNET' } @{ Name = 'Subnet'; Expression = '$Subnet' } ) Preamble = @' $VNET = if(![string]::IsNullOrEmpty($data.subnetId)){Get-AZSCIdSegment -Id $data.subnetId -Index 8}else{$null} $Subnet = if(![string]::IsNullOrEmpty($data.subnetId)){Get-AZSCIdSegment -Id $data.subnetId -Index 10}else{$null} '@ } @{ Category = 'DevOps' Name = 'DeploymentEnvironments' Worksheet = 'Deployment Environments' ResourceTypes = @( 'microsoft.devcenter/devcenters/environmenttypes' 'microsoft.devcenter/projects/environmenttypes' 'microsoft.devcenter/devcenters/catalogs' 'microsoft.devcenter/projects/catalogs' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Deployment Target'; Expression = '$DeploymentTarget' } @{ Name = 'Status'; Expression = '$data.status' } @{ Name = 'Sync State'; Expression = '$data.syncState' } ) Preamble = @' $DeploymentTarget = if(![string]::IsNullOrEmpty($data.deploymentTargetId)){Get-AZSCIdSegment -Id $data.deploymentTargetId -Index 2}else{$null} '@ } @{ Category = 'DevOps' Name = 'DevTestLabs' Worksheet = 'DevTest Labs' ResourceTypes = @('microsoft.devtestlab/labs', 'microsoft.devtestlab/schedules') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Lab Storage Type'; Expression = '$data.labStorageType' } @{ Name = 'Premium Data Disks'; Expression = '$data.premiumDataDisks' } @{ Name = 'Schedule Status'; Expression = '$data.status' } @{ Name = 'Task Type'; Expression = '$data.taskType' } ) } @{ Category = 'DevOps' Name = 'LabServices' Worksheet = 'Lab Services' ResourceTypes = @('microsoft.labservices/labs', 'microsoft.labservices/labplans') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'State'; Expression = '$data.state' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Security Open Access'; Expression = '$data.securityProfile.openAccess' } ) } @{ Category = 'DevOps' Name = 'LoadTesting' Worksheet = 'Load Testing' ResourceTypes = @('microsoft.loadtestservice/loadtests') Fields = @( @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Data Plane URI'; Expression = '$data.dataPlaneURI' } @{ Name = 'Description'; Expression = '$data.description' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } @{ Name = 'CMK Identity'; Expression = '$data.encryption.identity.type' } ) } @{ Category = 'DevOps' Name = 'ManagedDevOpsPools' Worksheet = 'Managed DevOps Pools' ResourceTypes = @('microsoft.devopsinfrastructure/pools') Fields = @( @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Maximum Agents'; Expression = '[string]$data.maximumConcurrency' } @{ Name = 'Organisation Kind'; Expression = '$data.organizationProfile.kind' } @{ Name = 'Agent OS'; Expression = '$data.fabricProfile.kind' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } ) } @{ Category = 'DevOps' Name = 'PlaywrightTesting' Worksheet = 'Playwright Workspaces' ResourceTypes = @('microsoft.azureplaywrightservice/accounts') Fields = @( @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Dashboard URI'; Expression = '$data.dashboardUri' } @{ Name = 'Local Auth'; Expression = '$data.localAuth' } @{ Name = 'Regional Affinity'; Expression = '$data.regionalAffinity' } @{ Name = 'Reporting'; Expression = '$data.reporting' } @{ Name = 'Scalable Execution'; Expression = '$data.scalableExecution' } ) } @{ Category = 'DevOps' Name = 'AppConfiguration' Worksheet = 'App Configuration' ResourceTypes = @('microsoft.appconfiguration/configurationstores') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Endpoint'; Expression = '$data.endpoint' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Local Auth Disabled'; Expression = '[string]$data.disableLocalAuth' } @{ Name = 'Purge Protection'; Expression = '[string]$data.enablePurgeProtection' } @{ Name = 'Soft Delete Days'; Expression = '[string]$data.softDeleteRetentionInDays' } @{ Name = 'Private Endpoints'; Expression = '[string]@($data.privateEndpointConnections).Count' } ) } @{ Category = 'DevOps' Name = 'ApiConnections' Worksheet = 'API Connections' ResourceTypes = @('microsoft.web/connections') Fields = @( @{ Name = 'Display Name'; Expression = '$data.displayName' } @{ Name = 'API Name'; Expression = '$data.api.name' } @{ Name = 'API Type'; Expression = '$data.api.type' } @{ Name = 'Status'; Expression = '$Status' } @{ Name = 'Created Time'; Expression = '[string]$data.createdTime' } @{ Name = 'Changed Time'; Expression = '[string]$data.changedTime' } ) Preamble = @' $Status = [string]($data.statuses | ForEach-Object { $_.status }) '@ } # --------------------------------------------------------------------------------------- # Integration — Scout's thinnest category at 3 of 15 (AB#6836). # --------------------------------------------------------------------------------------- @{ Category = 'Integration' Name = 'LogicApps' Worksheet = 'Logic Apps' ResourceTypes = @('microsoft.logic/workflows') Fields = @( @{ Name = 'State'; Expression = '$data.state' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Version'; Expression = '$data.version' } @{ Name = 'Access Endpoint'; Expression = '$data.accessEndpoint' } @{ Name = 'Integration Account'; Expression = '$IntegrationAccount' } @{ Name = 'Trigger Count'; Expression = '[string]@($data.definition.triggers.psobject.properties).Count' } @{ Name = 'Action Count'; Expression = '[string]@($data.definition.actions.psobject.properties).Count' } @{ Name = 'Created Time'; Expression = '[string]$data.createdTime' } @{ Name = 'Changed Time'; Expression = '[string]$data.changedTime' } ) Preamble = @' $IntegrationAccount = if(![string]::IsNullOrEmpty($data.integrationAccount.id)){Get-AZSCIdSegment -Id $data.integrationAccount.id -Index 8}else{$null} '@ } @{ Category = 'Integration' Name = 'IntegrationAccounts' Worksheet = 'Integration Accounts' ResourceTypes = @('microsoft.logic/integrationaccounts', 'microsoft.logic/integrationserviceenvironments') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'State'; Expression = '$data.state' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Public Network'; Expression = '$data.publicNetworkAccess' } ) } @{ Category = 'Integration' Name = 'LogicAppsCustomConnectors' Worksheet = 'Logic Apps Connectors' ResourceTypes = @('microsoft.web/customapis') Fields = @( @{ Name = 'Display Name'; Expression = '$data.displayName' } @{ Name = 'Description'; Expression = '$data.description' } @{ Name = 'Backend Service'; Expression = '$data.backendService.serviceUrl' } @{ Name = 'API Type'; Expression = '$data.apiType' } @{ Name = 'Connection Params'; Expression = '[string]@($data.connectionParameters.psobject.properties).Count' } ) } @{ Category = 'Integration' Name = 'EventGrid' Worksheet = 'Event Grid' ResourceTypes = @( 'microsoft.eventgrid/topics' 'microsoft.eventgrid/systemtopics' 'microsoft.eventgrid/domains' 'microsoft.eventgrid/partnertopics' 'microsoft.eventgrid/namespaces' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Endpoint'; Expression = '$data.endpoint' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Local Auth Disabled'; Expression = '[string]$data.disableLocalAuth' } @{ Name = 'Source Type'; Expression = '$data.topicType' } @{ Name = 'Private Endpoints'; Expression = '[string]@($data.privateEndpointConnections).Count' } ) } @{ Category = 'Integration' Name = 'EventHubClusters' Worksheet = 'Event Hubs Clusters' ResourceTypes = @('microsoft.eventhub/clusters') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Capacity'; Expression = '[string]$1.sku.capacity' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Metric Id'; Expression = '$data.metricId' } @{ Name = 'Supports Scaling'; Expression = '[string]$data.supportsScaling' } ) } @{ Category = 'Integration' Name = 'Relays' Worksheet = 'Relays' ResourceTypes = @('microsoft.relay/namespaces', 'microsoft.relay/namespaces/hybridconnections', 'microsoft.relay/namespaces/wcfrelays') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Service Bus Endpoint'; Expression = '$data.serviceBusEndpoint' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Listener Count'; Expression = '[string]$data.listenerCount' } ) } @{ Category = 'Integration' Name = 'HealthDataServices' Worksheet = 'Health Data Services' ResourceTypes = @( 'microsoft.healthcareapis/services' 'microsoft.healthcareapis/workspaces' 'microsoft.healthcareapis/workspaces/fhirservices' 'microsoft.healthcareapis/workspaces/dicomservices' 'microsoft.healthcareapis/workspaces/iotconnectors' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Service Kind'; Expression = '$1.KIND' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Authority'; Expression = '$data.authenticationConfiguration.authority' } @{ Name = 'SMART Proxy Enabled'; Expression = '[string]$data.authenticationConfiguration.smartProxyEnabled' } ) } # --------------------------------------------------------------------------------------- # Web and Mobile — 6 of 22 (AB#6836). # --------------------------------------------------------------------------------------- @{ Category = 'Web' Name = 'AppServiceEnvironments' Worksheet = 'App Service Environments' ResourceTypes = @('microsoft.web/hostingenvironments') Fields = @( @{ Name = 'Kind'; Expression = '$1.KIND' } @{ Name = 'Status'; Expression = '$data.status' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Internal Load Balancing'; Expression = '$data.internalLoadBalancingMode' } @{ Name = 'Dedicated Host Count'; Expression = '[string]$data.dedicatedHostCount' } @{ Name = 'Zone Redundant'; Expression = '[string]$data.zoneRedundant' } @{ Name = 'Virtual Network'; Expression = '$VNET' } @{ Name = 'Subnet'; Expression = '$Subnet' } ) Preamble = @' $VNET = if(![string]::IsNullOrEmpty($data.virtualNetwork.id)){Get-AZSCIdSegment -Id $data.virtualNetwork.id -Index 8}else{$null} $Subnet = if(![string]::IsNullOrEmpty($data.virtualNetwork.subnet)){$data.virtualNetwork.subnet}else{$null} '@ } @{ Category = 'Web' Name = 'StaticWebApps' Worksheet = 'Static Web Apps' ResourceTypes = @('microsoft.web/staticsites') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Default Hostname'; Expression = '$data.defaultHostname' } @{ Name = 'Repository URL'; Expression = '$data.repositoryUrl' } @{ Name = 'Branch'; Expression = '$data.branch' } @{ Name = 'Provider'; Expression = '$data.provider' } @{ Name = 'Staging Environments'; Expression = '$data.stagingEnvironmentPolicy' } @{ Name = 'Config File Changes'; Expression = '[string]$data.allowConfigFileUpdates' } @{ Name = 'Custom Domains'; Expression = '[string]@($data.customDomains).Count' } ) } @{ Category = 'Web' Name = 'FunctionApps' Worksheet = 'Function Apps' ResourceTypes = @('microsoft.web/sites') AdditionalFilter = '$_.KIND -like ''*functionapp*''' Fields = @( @{ Name = 'Kind'; Expression = '$1.KIND' } @{ Name = 'State'; Expression = '$data.state' } @{ Name = 'Default Hostname'; Expression = '$data.defaultHostName' } @{ Name = 'App Service Plan'; Expression = '$ServerFarm' } @{ Name = 'HTTPS Only'; Expression = '[string]$data.httpsOnly' } @{ Name = 'Client Cert Enabled'; Expression = '[string]$data.clientCertEnabled' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Outbound Subnet'; Expression = '$OutboundSubnet' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } ) Preamble = @' $ServerFarm = if(![string]::IsNullOrEmpty($data.serverFarmId)){Get-AZSCIdSegment -Id $data.serverFarmId -Index 8}else{$null} $OutboundSubnet = if(![string]::IsNullOrEmpty($data.virtualNetworkSubnetId)){Get-AZSCIdSegment -Id $data.virtualNetworkSubnetId -Index 10}else{$null} '@ } @{ Category = 'Web' Name = 'DeploymentSlots' Worksheet = 'Deployment Slots' ResourceTypes = @('microsoft.web/sites/slots') Fields = @( @{ Name = 'Kind'; Expression = '$1.KIND' } @{ Name = 'Parent Site'; Expression = '$ParentSite' } @{ Name = 'State'; Expression = '$data.state' } @{ Name = 'Default Hostname'; Expression = '$data.defaultHostName' } @{ Name = 'App Service Plan'; Expression = '$ServerFarm' } @{ Name = 'HTTPS Only'; Expression = '[string]$data.httpsOnly' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } ) Preamble = @' $ParentSite = if(![string]::IsNullOrEmpty($1.NAME)){Get-AZSCIdSegment -Id $1.NAME -Index 0}else{$null} $ServerFarm = if(![string]::IsNullOrEmpty($data.serverFarmId)){Get-AZSCIdSegment -Id $data.serverFarmId -Index 8}else{$null} '@ } @{ Category = 'Web' Name = 'AppServiceCertificates' Worksheet = 'App Service Certificates' ResourceTypes = @('microsoft.certificateregistration/certificateorders', 'microsoft.web/certificates') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Status'; Expression = '$data.status' } @{ Name = 'Subject Name'; Expression = '$data.subjectName' } @{ Name = 'Issuer'; Expression = '$data.issuer' } @{ Name = 'Issue Date'; Expression = '[string]$data.issueDate' } @{ Name = 'Expiration Date'; Expression = '[string]$Expiration' } @{ Name = 'Days To Expiry'; Expression = '[string]$DaysToExpiry' } @{ Name = 'Auto Renew'; Expression = '[string]$data.autoRenew' } @{ Name = 'Key Vault'; Expression = '$KeyVault' } ) Preamble = @' $Expiration = if(![string]::IsNullOrEmpty($data.expirationDate)){$data.expirationDate}else{$data.expirationTime} $DaysToExpiry = if(![string]::IsNullOrEmpty($Expiration)){[math]::Floor(([datetime]$Expiration - [datetime]'2000-01-01').TotalDays)}else{$null} $KeyVault = if(![string]::IsNullOrEmpty($data.keyVaultId)){Get-AZSCIdSegment -Id $data.keyVaultId -Index 8}else{$null} '@ } @{ Category = 'Web' Name = 'AppServiceDomains' Worksheet = 'App Service Domains' ResourceTypes = @('microsoft.domainregistration/domains') Fields = @( @{ Name = 'Registration Status'; Expression = '$data.registrationStatus' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Created Time'; Expression = '[string]$data.createdTime' } @{ Name = 'Expiration Time'; Expression = '[string]$data.expirationTime' } @{ Name = 'Auto Renew'; Expression = '[string]$data.autoRenew' } @{ Name = 'Privacy'; Expression = '[string]$data.privacy' } ) } @{ Category = 'Web' Name = 'SignalR' Worksheet = 'SignalR' ResourceTypes = @('microsoft.signalrservice/signalr') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'SKU Tier'; Expression = '$1.sku.tier' } @{ Name = 'Capacity'; Expression = '[string]$1.sku.capacity' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Host Name'; Expression = '$data.hostName' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Local Auth Disabled'; Expression = '[string]$data.disableLocalAuth' } @{ Name = 'TLS Client Cert'; Expression = '[string]$data.tls.clientCertEnabled' } ) } @{ Category = 'Web' Name = 'WebPubSub' Worksheet = 'Web PubSub' ResourceTypes = @('microsoft.signalrservice/webpubsub') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'SKU Tier'; Expression = '$1.sku.tier' } @{ Name = 'Capacity'; Expression = '[string]$1.sku.capacity' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Host Name'; Expression = '$data.hostName' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Local Auth Disabled'; Expression = '[string]$data.disableLocalAuth' } ) } @{ Category = 'Web' Name = 'CommunicationServices' Worksheet = 'Communication Services' ResourceTypes = @( 'microsoft.communication/communicationservices' 'microsoft.communication/emailservices' 'microsoft.communication/emailservices/domains' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Data Location'; Expression = '$data.dataLocation' } @{ Name = 'Host Name'; Expression = '$data.hostName' } @{ Name = 'Domain Management'; Expression = '$data.domainManagement' } @{ Name = 'Verification State'; Expression = '$data.verificationStates.Domain.status' } ) } @{ Category = 'Web' Name = 'NotificationHubs' Worksheet = 'Notification Hubs' ResourceTypes = @('microsoft.notificationhubs/namespaces', 'microsoft.notificationhubs/namespaces/notificationhubs') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'SKU Tier'; Expression = '$1.sku.tier' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Namespace Type'; Expression = '$data.namespaceType' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } ) } @{ Category = 'Web' Name = 'FluidRelay' Worksheet = 'Fluid Relay' ResourceTypes = @('microsoft.fluidrelay/fluidrelayservers') Fields = @( @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Storage SKU'; Expression = '$data.storagesku' } @{ Name = 'Frs Tenant Id'; Expression = '$data.frsTenantId' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } ) } @{ Category = 'Web' Name = 'SpringApps' Worksheet = 'Spring Apps' ResourceTypes = @('microsoft.appplatform/spring', 'microsoft.appplatform/spring/apps') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'SKU Tier'; Expression = '$1.sku.tier' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Service Id'; Expression = '$data.serviceId' } @{ Name = 'Zone Redundant'; Expression = '[string]$data.zoneRedundant' } @{ Name = 'Public Endpoint'; Expression = '[string]$data.public' } ) } # --------------------------------------------------------------------------------------- # Storage — 3 of 17 (AB#6837). Child resources are collected separately (AB#6834). # --------------------------------------------------------------------------------------- @{ Category = 'Storage' Name = 'Snapshots' Worksheet = 'Snapshots' ResourceTypes = @('microsoft.compute/snapshots') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Disk State'; Expression = '$data.diskState' } @{ Name = 'Size (GB)'; Expression = '[string]$data.diskSizeGB' } @{ Name = 'Incremental'; Expression = '[string]$data.incremental' } @{ Name = 'Created'; Expression = '[string]$data.timeCreated' } @{ Name = 'Source Disk'; Expression = '$SourceDisk' } @{ Name = 'OS Type'; Expression = '$data.osType' } @{ Name = 'Network Access'; Expression = '$data.networkAccessPolicy' } @{ Name = 'Public Network'; Expression = '$data.publicNetworkAccess' } ) Preamble = @' $SourceDisk = if(![string]::IsNullOrEmpty($data.creationData.sourceResourceId)){Get-AZSCIdSegment -Id $data.creationData.sourceResourceId -Index 8}else{$null} '@ } @{ Category = 'Storage' Name = 'DiskEncryptionSets' Worksheet = 'Disk Encryption Sets' ResourceTypes = @('microsoft.compute/diskencryptionsets') Fields = @( @{ Name = 'Encryption Type'; Expression = '$data.encryptionType' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Key Vault'; Expression = '$KeyVault' } @{ Name = 'Auto Key Rotation'; Expression = '[string]$data.rotationToLatestKeyVersionEnabled' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } ) Preamble = @' $KeyVault = if(![string]::IsNullOrEmpty($data.activeKey.sourceVault.id)){Get-AZSCIdSegment -Id $data.activeKey.sourceVault.id -Index 8}else{$null} '@ } @{ Category = 'Storage' Name = 'ElasticSan' Worksheet = 'Elastic SAN' ResourceTypes = @('microsoft.elasticsan/elasticsans', 'microsoft.elasticsan/elasticsans/volumegroups') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Base Size (TiB)'; Expression = '[string]$data.baseSizeTiB' } @{ Name = 'Extended Size (TiB)'; Expression = '[string]$data.extendedCapacitySizeTiB' } @{ Name = 'Protocol Type'; Expression = '$data.protocolType' } @{ Name = 'Encryption'; Expression = '$data.encryption' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } ) } @{ Category = 'Storage' Name = 'StorageSync' Worksheet = 'Storage Sync Services' ResourceTypes = @( 'microsoft.storagesync/storagesyncservices' 'microsoft.storagesync/storagesyncservices/syncgroups' 'microsoft.storagesync/storagesyncservices/registeredservers' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Incoming Traffic'; Expression = '$data.incomingTrafficPolicy' } @{ Name = 'Server Name'; Expression = '$data.friendlyName' } @{ Name = 'Server OS'; Expression = '$data.serverOSVersion' } @{ Name = 'Agent Version'; Expression = '$data.agentVersion' } @{ Name = 'Server Status'; Expression = '$data.serverManagementErrorCode' } ) } @{ Category = 'Storage' Name = 'EdgeHardwareCenter' Worksheet = 'Edge Hardware Center' ResourceTypes = @('microsoft.edgeorder/orders', 'microsoft.edgeorder/orderitems', 'microsoft.edgeorder/addresses') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Order Stage'; Expression = '$data.orderStageHistory' } @{ Name = 'Product Family'; Expression = '$data.productDetails.hierarchyInformation.productFamilyName' } @{ Name = 'Product Line'; Expression = '$data.productDetails.hierarchyInformation.productLineName' } @{ Name = 'Quantity'; Expression = '[string]$data.productDetails.count' } @{ Name = 'Address State'; Expression = '$data.addressValidationStatus' } ) } @{ Category = 'Storage' Name = 'PartnerStorage' Worksheet = 'Partner Storage Services' ResourceTypes = @( 'purestorage.block/storagepools' 'purestorage.block/reservations' 'qumulo.storage/filesystems' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Marketplace Plan'; Expression = '$1.plan.name' } @{ Name = 'Publisher'; Expression = '$1.plan.publisher' } @{ Name = 'Offer'; Expression = '$1.plan.product' } ) } # --------------------------------------------------------------------------------------- # Internet of Things — 8 of 19 (AB#6837). # --------------------------------------------------------------------------------------- @{ Category = 'IoT' Name = 'DeviceProvisioningServices' Worksheet = 'IoT DPS' ResourceTypes = @('microsoft.devices/provisioningservices') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Capacity'; Expression = '[string]$1.sku.capacity' } @{ Name = 'State'; Expression = '$data.state' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Service Endpoint'; Expression = '$data.serviceOperationsHostName' } @{ Name = 'Allocation Policy'; Expression = '$data.allocationPolicy' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Linked IoT Hubs'; Expression = '[string]@($data.iotHubs).Count' } ) } @{ Category = 'IoT' Name = 'IoTCentral' Worksheet = 'IoT Central' ResourceTypes = @('microsoft.iotcentral/iotapps') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'State'; Expression = '$data.state' } @{ Name = 'Application Id'; Expression = '$data.applicationId' } @{ Name = 'Subdomain'; Expression = '$data.subdomain' } @{ Name = 'Template'; Expression = '$data.template' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } ) } @{ Category = 'IoT' Name = 'DeviceUpdate' Worksheet = 'Device Update' ResourceTypes = @('microsoft.deviceupdate/accounts', 'microsoft.deviceupdate/accounts/instances') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$data.sku' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Host Name'; Expression = '$data.hostName' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Linked IoT Hubs'; Expression = '[string]@($data.iotHubs).Count' } ) } @{ Category = 'IoT' Name = 'DigitalTwins' Worksheet = 'Digital Twins' ResourceTypes = @( 'microsoft.digitaltwins/digitaltwinsinstances' 'microsoft.digitaltwins/digitaltwinsinstances/endpoints' 'microsoft.digitaltwins/digitaltwinsinstances/timeseriesdatabaseconnections' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Host Name'; Expression = '$data.hostName' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Endpoint Type'; Expression = '$data.endpointType' } @{ Name = 'Identity Type'; Expression = '$1.identity.type' } ) } @{ Category = 'IoT' Name = 'Maps' Worksheet = 'Azure Maps' ResourceTypes = @('microsoft.maps/accounts', 'microsoft.maps/accounts/creators') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'SKU Tier'; Expression = '$1.sku.tier' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Unique Id'; Expression = '$data.uniqueId' } @{ Name = 'Local Auth Disabled'; Expression = '[string]$data.disableLocalAuth' } @{ Name = 'Storage Units'; Expression = '[string]$data.storageUnits' } ) } @{ Category = 'IoT' Name = 'DefenderForIoT' Worksheet = 'Defender for IoT' ResourceTypes = @( 'microsoft.iotsecurity/defendersettings' 'microsoft.iotsecurity/sites' 'microsoft.iotsecurity/sensors' 'microsoft.iotsecurity/onpremisesensors' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Device Quota'; Expression = '[string]$data.deviceQuota' } @{ Name = 'Onboarding Kind'; Expression = '$data.onboardingKind' } @{ Name = 'Sentinel Workspace'; Expression = '$SentinelWorkspace' } @{ Name = 'MDE Integration'; Expression = '$data.mdeIntegration.status' } @{ Name = 'Display Name'; Expression = '$data.displayName' } ) Preamble = @' $SentinelWorkspace = if(![string]::IsNullOrEmpty($data.sentinelWorkspaceResourceIds)){Get-AZSCIdSegment -Id ([string]@($data.sentinelWorkspaceResourceIds)[0]) -Index 8}else{$null} '@ } # --------------------------------------------------------------------------------------- # Child resources (AB#6833 / AB#6834 / AB#6751). # # `Get-ScoutArmChildResource` COLLECTS these; without a collector to render them the data # is fetched on every run and thrown away, which is precisely the collect-versus-display # defect §5.3 of the audit is about. Their rows are synthetic `AZSC/ARMChild/*` envelopes, # so each declares its own Identity: there is no `tags` column, no retirement record and # no subscription join off `$1.subscriptionId` alone. # --------------------------------------------------------------------------------------- @{ Category = 'Security' Name = 'KeyVaultSecrets' Worksheet = 'Key Vault Secrets' ResourceTypes = @('AZSC/ARMChild/KeyVaultSecrets') Identity = @( @{ Name = 'ID'; Expression = '$1.id' } @{ Name = 'Subscription'; Expression = '$sub1.Name' } @{ Name = 'Resource Group'; Expression = '$1.RESOURCEGROUP' } @{ Name = 'Key Vault'; Expression = '$1.PARENTNAME' } @{ Name = 'Secret'; Expression = '$SecretName' } ) TagLoop = $null Fields = @( @{ Name = 'Content Type'; Expression = '$data.contentType' } @{ Name = 'Kind'; Expression = '$Kind' } @{ Name = 'Enabled'; Expression = '[string]$data.attributes.enabled' } @{ Name = 'Expires'; Expression = '$Expires' } @{ Name = 'Days To Expiry'; Expression = '[string]$DaysToExpiry' } @{ Name = 'Expiry Status'; Expression = '$ExpiryStatus' } @{ Name = 'Not Before'; Expression = '$NotBefore' } @{ Name = 'Created'; Expression = '$Created' } @{ Name = 'Updated'; Expression = '$Updated' } ) Preamble = @' $sub1 = $SUB | Where-Object { $_.id -eq $1.subscriptionId } $SecretName = if([string]::IsNullOrEmpty($1.name)){$null}else{(([string]$1.name) -split '/')[-1]} $Kind = if($data.contentType -match 'x-pkcs12|x-pem-file'){'Certificate'}else{'Secret'} $Expires = if($null -eq $data.attributes.exp){''}else{[string]([datetime]::UnixEpoch).AddSeconds([double]$data.attributes.exp).ToString('yyyy-MM-dd')} $DaysToExpiry = if($null -eq $data.attributes.exp){$null}else{[math]::Floor(((([datetime]::UnixEpoch).AddSeconds([double]$data.attributes.exp)) - $ScoutRunTime).TotalDays)} $ExpiryStatus = if($null -eq $DaysToExpiry){'No expiry set'}elseif($DaysToExpiry -lt 0){'EXPIRED'}elseif($DaysToExpiry -le 30){'Expires within 30 days'}elseif($DaysToExpiry -le 90){'Expires within 90 days'}else{'OK'} $NotBefore = if($null -eq $data.attributes.nbf){''}else{[string]([datetime]::UnixEpoch).AddSeconds([double]$data.attributes.nbf).ToString('yyyy-MM-dd')} $Created = if($null -eq $data.attributes.created){''}else{[string]([datetime]::UnixEpoch).AddSeconds([double]$data.attributes.created).ToString('yyyy-MM-dd')} $Updated = if($null -eq $data.attributes.updated){''}else{[string]([datetime]::UnixEpoch).AddSeconds([double]$data.attributes.updated).ToString('yyyy-MM-dd')} '@ } @{ Category = 'Security' Name = 'KeyVaultKeys' Worksheet = 'Key Vault Keys' ResourceTypes = @('AZSC/ARMChild/KeyVaultKeys') Identity = @( @{ Name = 'ID'; Expression = '$1.id' } @{ Name = 'Subscription'; Expression = '$sub1.Name' } @{ Name = 'Resource Group'; Expression = '$1.RESOURCEGROUP' } @{ Name = 'Key Vault'; Expression = '$1.PARENTNAME' } @{ Name = 'Key'; Expression = '$KeyName' } ) TagLoop = $null Fields = @( @{ Name = 'Key Type'; Expression = '$data.kty' } @{ Name = 'Key Size'; Expression = '[string]$data.keySize' } @{ Name = 'Curve'; Expression = '$data.curveName' } @{ Name = 'Enabled'; Expression = '[string]$data.attributes.enabled' } @{ Name = 'Expires'; Expression = '$Expires' } @{ Name = 'Days To Expiry'; Expression = '[string]$DaysToExpiry' } @{ Name = 'Expiry Status'; Expression = '$ExpiryStatus' } @{ Name = 'Rotation Policy'; Expression = '[string]$data.rotationPolicy.lifetimeActions.Count' } ) Preamble = @' $sub1 = $SUB | Where-Object { $_.id -eq $1.subscriptionId } $KeyName = if([string]::IsNullOrEmpty($1.name)){$null}else{(([string]$1.name) -split '/')[-1]} $Expires = if($null -eq $data.attributes.exp){''}else{[string]([datetime]::UnixEpoch).AddSeconds([double]$data.attributes.exp).ToString('yyyy-MM-dd')} $DaysToExpiry = if($null -eq $data.attributes.exp){$null}else{[math]::Floor(((([datetime]::UnixEpoch).AddSeconds([double]$data.attributes.exp)) - $ScoutRunTime).TotalDays)} $ExpiryStatus = if($null -eq $DaysToExpiry){'No expiry set'}elseif($DaysToExpiry -lt 0){'EXPIRED'}elseif($DaysToExpiry -le 30){'Expires within 30 days'}elseif($DaysToExpiry -le 90){'Expires within 90 days'}else{'OK'} '@ } @{ # AB#6829. `Reservations` (General/Reservations) already lists what was BOUGHT; this # renders what the Microsoft.Consumption `reservationSummaries` child call says about # how much of it is USED. Field names follow the documented ReservationSummary schema # (learn.microsoft.com/javascript/api/@azure/arm-consumption/reservationsummary) -- # unverified against a live tenant, same caveat as every other ARMChild collector here. # A reservation whose most recent monthly grain has not closed yet produces no child # row at all (see Get-ScoutArmChildResource), which is an absence, not a zero -- the # vault-with-no-items class this file's Key Vault collectors also have to get right. Category = 'General' Name = 'ReservationUtilization' Worksheet = 'Reservation Utilization' ResourceTypes = @('AZSC/ARMChild/ReservationUtilization') Identity = @( @{ Name = 'ID'; Expression = '$1.PARENTID' } @{ Name = 'Subscription'; Expression = '$SubName' } @{ Name = 'Reservation'; Expression = '$1.PARENTNAME' } ) TagLoop = $null Fields = @( @{ Name = 'SKU'; Expression = '$data.skuName' } @{ Name = 'Usage Date'; Expression = '[string]$data.usageDate' } @{ Name = 'Reserved Hours'; Expression = '[string]$data.reservedHours' } @{ Name = 'Used Hours'; Expression = '[string]$data.usedHours' } @{ Name = 'Avg Utilization %'; Expression = '[string]$data.avgUtilizationPercentage' } @{ Name = 'Min Utilization %'; Expression = '[string]$data.minUtilizationPercentage' } @{ Name = 'Max Utilization %'; Expression = '[string]$data.maxUtilizationPercentage' } @{ Name = 'Purchased Quantity'; Expression = '[string]$data.purchasedQuantity' } @{ Name = 'Remaining Quantity'; Expression = '[string]$data.remainingQuantity' } ) Preamble = @' $sub1 = $SUB | Where-Object { $_.id -eq $1.subscriptionId } $SubName = $sub1.Name '@ } @{ Category = 'Storage' Name = 'BlobContainers' Worksheet = 'Blob Containers' ResourceTypes = @('AZSC/ARMChild/StorageBlobContainers') Identity = @( @{ Name = 'ID'; Expression = '$1.id' } @{ Name = 'Subscription'; Expression = '$sub1.Name' } @{ Name = 'Resource Group'; Expression = '$1.RESOURCEGROUP' } @{ Name = 'Storage Account'; Expression = '$1.PARENTNAME' } @{ Name = 'Container'; Expression = '$ContainerName' } ) TagLoop = $null Fields = @( # 'Public Access' and the exposure verdict are the reason this collector exists: a # storage-account list cannot say whether anything inside it is anonymously # reachable, and that is the highest-severity finding in most Azure estates. @{ Name = 'Public Access'; Expression = '$PublicAccess' } @{ Name = 'Anonymous Access'; Expression = '$AnonymousAccess' } @{ Name = 'Lease State'; Expression = '$data.leaseState' } @{ Name = 'Lease Status'; Expression = '$data.leaseStatus' } @{ Name = 'Immutability Policy'; Expression = '[string]$data.hasImmutabilityPolicy' } @{ Name = 'Legal Hold'; Expression = '[string]$data.hasLegalHold' } @{ Name = 'Version Level WORM'; Expression = '[string]$data.isVersionLevelWormEnabled' } @{ Name = 'Last Modified'; Expression = '[string]$data.lastModifiedTime' } ) Preamble = @' $sub1 = $SUB | Where-Object { $_.id -eq $1.subscriptionId } $ContainerName = if([string]::IsNullOrEmpty($1.name)){$null}else{(([string]$1.name) -split '/')[-1]} $PublicAccess = if([string]::IsNullOrEmpty($data.publicAccess)){'None'}else{[string]$data.publicAccess} $AnonymousAccess = if($PublicAccess -eq 'Container'){'YES - full anonymous read of blobs AND container listing'}elseif($PublicAccess -eq 'Blob'){'YES - anonymous read of blobs'}else{'No'} '@ } @{ Category = 'Storage' Name = 'FileShares' Worksheet = 'File Shares' ResourceTypes = @('AZSC/ARMChild/StorageFileShares') Identity = @( @{ Name = 'ID'; Expression = '$1.id' } @{ Name = 'Subscription'; Expression = '$sub1.Name' } @{ Name = 'Resource Group'; Expression = '$1.RESOURCEGROUP' } @{ Name = 'Storage Account'; Expression = '$1.PARENTNAME' } @{ Name = 'Share'; Expression = '$ShareName' } ) TagLoop = $null Fields = @( @{ Name = 'Quota (GiB)'; Expression = '[string]$data.shareQuota' } @{ Name = 'Access Tier'; Expression = '$data.accessTier' } @{ Name = 'Protocols'; Expression = '$data.enabledProtocols' } @{ Name = 'Root Squash'; Expression = '$data.rootSquash' } @{ Name = 'Snapshot Count'; Expression = '[string]$data.snapshotCount' } @{ Name = 'Last Modified'; Expression = '[string]$data.lastModifiedTime' } ) Preamble = @' $sub1 = $SUB | Where-Object { $_.id -eq $1.subscriptionId } $ShareName = if([string]::IsNullOrEmpty($1.name)){$null}else{(([string]$1.name) -split '/')[-1]} '@ } @{ Category = 'Storage' Name = 'LifecyclePolicies' Worksheet = 'Storage Lifecycle Policies' ResourceTypes = @('AZSC/ARMChild/StorageLifecyclePolicies') Identity = @( @{ Name = 'ID'; Expression = '$1.id' } @{ Name = 'Subscription'; Expression = '$sub1.Name' } @{ Name = 'Resource Group'; Expression = '$1.RESOURCEGROUP' } @{ Name = 'Storage Account'; Expression = '$1.PARENTNAME' } ) TagLoop = $null Fields = @( @{ Name = 'Rule Count'; Expression = '[string]$RuleCount' } @{ Name = 'Enabled Rules'; Expression = '[string]$EnabledRules' } @{ Name = 'Rule Names'; Expression = '$RuleNames' } @{ Name = 'Last Modified'; Expression = '[string]$data.lastModifiedTime' } ) Preamble = @' $sub1 = $SUB | Where-Object { $_.id -eq $1.subscriptionId } $LifecycleRules = @($data.policy.rules) $RuleCount = $LifecycleRules.Count $EnabledRules = @($LifecycleRules | Where-Object { $_.enabled -ne $false }).Count $RuleNames = [string](@($LifecycleRules | ForEach-Object { $_.name }) -join ', ') '@ } @{ Category = 'Management' Name = 'BackupInstances' Worksheet = 'Backup Instances' ResourceTypes = @('AZSC/ARMChild/BackupInstances') Identity = @( @{ Name = 'ID'; Expression = '$1.id' } @{ Name = 'Subscription'; Expression = '$sub1.Name' } @{ Name = 'Resource Group'; Expression = '$1.RESOURCEGROUP' } @{ Name = 'Backup Vault'; Expression = '$1.PARENTNAME' } @{ Name = 'Instance'; Expression = '$InstanceName' } ) TagLoop = $null Fields = @( @{ Name = 'Friendly Name'; Expression = '$data.friendlyName' } @{ Name = 'Datasource Type'; Expression = '$data.dataSourceInfo.datasourceType' } @{ Name = 'Protected Resource'; Expression = '$ProtectedResource' } @{ Name = 'Protection State'; Expression = '$data.currentProtectionState' } @{ Name = 'Protection Status'; Expression = '$data.protectionStatus.status' } @{ Name = 'Policy'; Expression = '$PolicyName' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } ) Preamble = @' $sub1 = $SUB | Where-Object { $_.id -eq $1.subscriptionId } $InstanceName = if([string]::IsNullOrEmpty($1.name)){$null}else{(([string]$1.name) -split '/')[-1]} $ProtectedResource = if([string]::IsNullOrEmpty($data.dataSourceInfo.resourceID)){$null}else{(Get-AZSCIdSegment -Id $data.dataSourceInfo.resourceID -Index 8)} $PolicyName = if([string]::IsNullOrEmpty($data.policyInfo.policyId)){$null}else{(Get-AZSCIdSegment -Id $data.policyInfo.policyId -Index 10)} '@ } # --------------------------------------------------------------------------------------- # Security — 6 of 22, and the most damaging gap: a security-posture tool that sees a third # of the security services (AB#6837). # --------------------------------------------------------------------------------------- @{ Category = 'Security' Name = 'Sentinel' Worksheet = 'Sentinel' ResourceTypes = @('microsoft.operationsmanagement/solutions', 'microsoft.securityinsights/onboardingstates') # A Sentinel deployment surfaces in the `resources` table as an OMS solution named # `SecurityInsights(<workspace>)`. Matching the whole solutions type would sweep in # every other OMS solution in the estate, so the name is the discriminator. AdditionalFilter = '$_.TYPE -eq ''microsoft.securityinsights/onboardingstates'' -or $_.NAME -like ''SecurityInsights*''' Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Workspace'; Expression = '$Workspace' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Customer Managed Key'; Expression = '[string]$data.customerManagedKey' } @{ Name = 'Plan'; Expression = '$1.plan.name' } ) Preamble = @' $Workspace = if(![string]::IsNullOrEmpty($data.workspaceResourceId)){Get-AZSCIdSegment -Id $data.workspaceResourceId -Index 8}else{$null} '@ } @{ Category = 'Security' Name = 'ManagedHSM' Worksheet = 'Managed HSM' ResourceTypes = @('microsoft.keyvault/managedhsms') Fields = @( @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'SKU Family'; Expression = '$1.sku.family' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'HSM URI'; Expression = '$data.hsmUri' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Purge Protection'; Expression = '[string]$data.enablePurgeProtection' } @{ Name = 'Soft Delete Days'; Expression = '[string]$data.softDeleteRetentionInDays' } @{ Name = 'Security Domain'; Expression = '$data.securityDomainProperties.activationStatus' } ) } @{ Category = 'Security' Name = 'CloudHSM' Worksheet = 'Cloud HSM' ResourceTypes = @('microsoft.hardwaresecuritymodules/cloudhsmclusters') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Public Network Access'; Expression = '$data.publicNetworkAccess' } @{ Name = 'Auto Generated Domain'; Expression = '[string]$data.autoGeneratedDomainNameLabelScope' } @{ Name = 'Stamp Id'; Expression = '$data.stampId' } ) } @{ Category = 'Security' Name = 'ApplicationSecurityGroups' Worksheet = 'App Security Groups' ResourceTypes = @('microsoft.network/applicationsecuritygroups') Fields = @( @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Resource GUID'; Expression = '$data.resourceGuid' } ) } @{ Category = 'Security' Name = 'WafPolicies' Worksheet = 'WAF Policies' ResourceTypes = @( 'microsoft.network/applicationgatewaywebapplicationfirewallpolicies' 'microsoft.network/frontdoorwebapplicationfirewallpolicies' 'microsoft.cdn/cdnwebapplicationfirewallpolicies' ) Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'SKU'; Expression = '$1.sku.name' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Enabled State'; Expression = '$EnabledState' } @{ Name = 'Mode'; Expression = '$Mode' } @{ Name = 'Managed Rule Sets'; Expression = '[string]@($data.managedRules.managedRuleSets).Count' } @{ Name = 'Custom Rules'; Expression = '[string]$CustomRuleCount' } @{ Name = 'Associations'; Expression = '[string]$AssociationCount' } ) Preamble = @' $EnabledState = if(![string]::IsNullOrEmpty($data.policySettings.enabledState)){$data.policySettings.enabledState}else{[string]$data.policySettings.state} $Mode = $data.policySettings.mode $CustomRuleCount = if($null -ne $data.customRules.rules){@($data.customRules.rules).Count}else{@($data.customRules).Count} $AssociationCount = @($data.applicationGateways).Count + @($data.httpListeners).Count + @($data.pathBasedRules).Count + @($data.frontendEndpointLinks).Count '@ } @{ Category = 'Security' Name = 'DdosProtectionPlans' Worksheet = 'DDoS Protection Plans' ResourceTypes = @('microsoft.network/ddosprotectionplans') Fields = @( @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Protected VNETs'; Expression = '[string]@($data.virtualNetworks).Count' } @{ Name = 'Public IP Count'; Expression = '[string]@($data.publicIPAddresses).Count' } @{ Name = 'Resource GUID'; Expression = '$data.resourceGuid' } ) } @{ Category = 'Security' Name = 'ConfidentialLedger' Worksheet = 'Confidential Ledger' ResourceTypes = @('microsoft.confidentialledger/ledgers') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Ledger Type'; Expression = '$data.ledgerType' } @{ Name = 'Ledger URI'; Expression = '$data.ledgerUri' } @{ Name = 'Identity Service'; Expression = '$data.identityServiceUri' } @{ Name = 'Running State'; Expression = '$data.runningState' } ) } @{ Category = 'Security' Name = 'ArtifactSigning' Worksheet = 'Artifact Signing' ResourceTypes = @('microsoft.codesigning/codesigningaccounts') Fields = @( @{ Name = 'SKU'; Expression = '$data.sku.name' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Account URI'; Expression = '$data.accountUri' } ) } @{ Category = 'Security' Name = 'EntraDomainServices' Worksheet = 'Entra Domain Services' ResourceTypes = @('microsoft.aad/domainservices') Fields = @( @{ Name = 'Domain Name'; Expression = '$data.domainName' } @{ Name = 'SKU'; Expression = '$data.sku' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Sync Scope'; Expression = '$data.syncScope' } @{ Name = 'TLS v1 Enabled'; Expression = '$data.domainSecuritySettings.tlsV1' } @{ Name = 'NTLM v1 Enabled'; Expression = '$data.domainSecuritySettings.ntlmV1' } @{ Name = 'Kerberos RC4'; Expression = '$data.domainSecuritySettings.kerberosRc4Encryption' } @{ Name = 'LDAPS Enabled'; Expression = '$data.ldapsSettings.ldaps' } @{ Name = 'LDAPS External'; Expression = '$data.ldapsSettings.externalAccess' } ) } @{ Category = 'Security' Name = 'AppComplianceAutomation' Worksheet = 'App Compliance Automation' ResourceTypes = @('microsoft.appcomplianceautomation/reports', 'microsoft.appcomplianceautomation/reports/snapshots') Fields = @( @{ Name = 'Resource Kind'; Expression = '$1.TYPE' } @{ Name = 'Provisioning State'; Expression = '$data.provisioningState' } @{ Name = 'Status'; Expression = '$data.status' } @{ Name = 'Trigger Time'; Expression = '[string]$data.triggerTime' } @{ Name = 'Time Zone'; Expression = '$data.timeZone' } @{ Name = 'Resource Count'; Expression = '[string]@($data.resources).Count' } ) } ) } |