') {
+ # $script:htsubscriptionsFromEntitiesThatAreNotInGetSubscriptions.($entry.name) = $entry
+ # Write-Host " Excluded Subscription '$($entry.properties.displayName)' ($($entry.name)) (contained in GetEntities, not contained in GetSubscriptions)" -ForegroundColor DarkRed
+ # continue
+ # }
+ }
+
+ $null = $script:arrayEntitiesFromAPI.Add($entry)
+ }
+
+ Write-Host " $($arrayEntitiesFromAPI.Count)/$($arrayEntitiesFromAPIInitial.Count) Entities relevant"
+
+ $endEntities = Get-Date
+ Write-Host " Getting Entities duration: $((New-TimeSpan -Start $startEntities -End $endEntities).TotalSeconds) seconds"
+
+ $startEntitiesdata = Get-Date
+ Write-Host ' Processing Entities data'
+ $script:htSubscriptionsMgPath = @{}
+ $script:htManagementGroupsMgPath = @{}
+ $script:htEntities = @{}
+ $script:htEntitiesPlain = @{}
+
+ foreach ($entity in $arrayEntitiesFromAPI) {
+ $script:htEntitiesPlain.($entity.Name) = @{}
+ $script:htEntitiesPlain.($entity.Name) = $entity
+ }
+
+ foreach ($entity in $arrayEntitiesFromAPI) {
+ if ($entity.Type -eq '/subscriptions') {
+ $parent = $entity.properties.parent.Id -replace '.*/'
+ $parentId = $entity.properties.parent.Id
+ $script:htSubscriptionsMgPath.($entity.name) = @{}
+ $script:htSubscriptionsMgPath.($entity.name).ParentNameChain = $entity.properties.parentNameChain
+ $script:htSubscriptionsMgPath.($entity.name).ParentNameChainDelimited = $entity.properties.parentNameChain -join '/'
+ $script:htSubscriptionsMgPath.($entity.name).Parent = $entity.properties.parent.Id -replace '.*/'
+ $script:htSubscriptionsMgPath.($entity.name).ParentName = $htEntitiesPlain.($entity.properties.parent.Id -replace '.*/').properties.displayName
+ $script:htSubscriptionsMgPath.($entity.name).DisplayName = $entity.properties.displayName
+ $array = $entity.properties.parentNameChain
+ $array += $entity.name
+ $script:htSubscriptionsMgPath.($entity.name).path = $array
+ $script:htSubscriptionsMgPath.($entity.name).pathDelimited = $array -join '/'
+ $script:htSubscriptionsMgPath.($entity.name).level = (($entity.properties.parentNameChain).Count - 1)
+ }
+ if ($entity.Type -eq 'Microsoft.Management/managementGroups') {
+ if ([string]::IsNullOrEmpty($entity.properties.parent.Id)) {
+ $parent = '__TenantRoot__'
+ $parentId = '__TenantRoot__'
+ }
+ else {
+ $parent = $entity.properties.parent.Id -replace '.*/'
+ $parentId = $entity.properties.parent.Id
+ }
+ $script:htManagementGroupsMgPath.($entity.name) = @{}
+ $script:htManagementGroupsMgPath.($entity.name).ParentNameChain = $entity.properties.parentNameChain
+ $script:htManagementGroupsMgPath.($entity.name).ParentNameChainDelimited = $entity.properties.parentNameChain -join '/'
+ $script:htManagementGroupsMgPath.($entity.name).ParentNameChainCount = ($entity.properties.parentNameChain | Measure-Object).Count
+ $script:htManagementGroupsMgPath.($entity.name).Parent = $parent
+ $script:htManagementGroupsMgPath.($entity.name).ChildMgsAll = ($arrayEntitiesFromAPI.where( { $_.Type -eq 'Microsoft.Management/managementGroups' -and $_.properties.ParentNameChain -contains $entity.name } )).Name
+ $script:htManagementGroupsMgPath.($entity.name).ChildMgsDirect = ($arrayEntitiesFromAPI.where( { $_.Type -eq 'Microsoft.Management/managementGroups' -and $_.properties.Parent.Id -replace '.*/' -eq $entity.name } )).Name
+ $script:htManagementGroupsMgPath.($entity.name).DisplayName = $entity.properties.displayName
+ $script:htManagementGroupsMgPath.($entity.name).Id = ($entity.name)
+ $array = $entity.properties.parentNameChain
+ $array += $entity.name
+ $script:htManagementGroupsMgPath.($entity.name).path = $array
+ $script:htManagementGroupsMgPath.($entity.name).pathDelimited = $array -join '/'
+ $script:htManagementGroupsMgPath.($entity.name).level = $array.Count
+ }
+
+ $script:htEntities.($entity.name) = @{}
+ $script:htEntities.($entity.name).ParentNameChain = $entity.properties.parentNameChain
+ $script:htEntities.($entity.name).Parent = $parent
+ $script:htEntities.($entity.name).ParentId = $parentId
+ if ($parent -eq '__TenantRoot__') {
+ $parentDisplayName = '__TenantRoot__'
+ }
+ else {
+ $parentDisplayName = $htEntitiesPlain.($htEntities.($entity.name).Parent).properties.displayName
+ }
+ $script:htEntities.($entity.name).ParentDisplayName = $parentDisplayName
+ $script:htEntities.($entity.name).DisplayName = $entity.properties.displayName
+ $script:htEntities.($entity.name).Id = $entity.Name
+ $script:htEntities.($entity.name).Type = $entity.Type
+ }
+
+ Write-Host " $(($htManagementGroupsMgPath.Keys).Count) relevant Management Groups"
+ Write-Host " $(($htSubscriptionsMgPath.Keys).Count) relevant Subscriptions"
+
+ $endEntitiesdata = Get-Date
+ Write-Host " Processing Entities data duration: $((New-TimeSpan -Start $startEntitiesdata -End $endEntitiesdata).TotalSeconds) seconds"
+
+ $script:arrayEntitiesFromAPISubscriptionsCount = ($arrayEntitiesFromAPI.where( { $_.type -eq '/subscriptions' -and $_.properties.parentNameChain -contains $ManagementGroupId } ) | Sort-Object -Property id -Unique).count
+ $script:arrayEntitiesFromAPIManagementGroupsCount = ($arrayEntitiesFromAPI.where( { $_.type -eq 'Microsoft.Management/managementGroups' -and $_.properties.parentNameChain -contains $ManagementGroupId } ) | Sort-Object -Property id -Unique).count + 1
+
+ $endEntities = Get-Date
+ Write-Host "Processing Entities duration: $((New-TimeSpan -Start $startEntities -End $endEntities).TotalSeconds) seconds"
+}
+function getFileNaming {
+ if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions -eq $true) {
+ if ($HierarchyMapOnly) {
+ $script:fileName = "AzGovViz_HierarchyMapOnly_$($ManagementGroupId)"
+ }
+ elseif ($azAPICallConf['htParameters'].ManagementGroupsOnly -eq $true) {
+ $script:fileName = "AzGovViz_ManagementGroupsOnly_$($ManagementGroupId)"
+ }
+ else {
+ $script:fileName = "AzGovViz_$($ManagementGroupId)"
+ }
+ }
+ else {
+ if ($HierarchyMapOnly) {
+ $script:fileName = "AzGovViz_HierarchyMapOnly_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)"
+ }
+ elseif ($azAPICallConf['htParameters'].ManagementGroupsOnly -eq $true) {
+ $script:fileName = "AzGovViz_ManagementGroupsOnly_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)"
+ }
+ else {
+ $script:fileName = "AzGovViz_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)"
+ }
+ }
+}
+
+function getGroupmembers($aadGroupId, $aadGroupDisplayName) {
+ if (-not $htAADGroupsDetails.($aadGroupId)) {
+ $script:htAADGroupsDetails.$aadGroupId = @{}
+ $script:htAADGroupsDetails.($aadGroupId).Id = $aadGroupId
+ $script:htAADGroupsDetails.($aadGroupId).displayname = $aadGroupDisplayName
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/groups/$($aadGroupId)/transitiveMembers"
+ $method = 'GET'
+ $aadGroupMembers = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask "getGroupmembers $($aadGroupId)"
+
+ if ($aadGroupMembers -eq 'Request_ResourceNotFound') {
+ $null = $script:arrayGroupRequestResourceNotFound.Add([PSCustomObject]@{
+ groupId = $aadGroupId
+ })
+ }
+
+ $aadGroupMembersAll = ($aadGroupMembers)
+ $aadGroupMembersUsers = $aadGroupMembers.where( { $_.'@odata.type' -eq '#microsoft.graph.user' } )
+ $aadGroupMembersGroups = $aadGroupMembers.where( { $_.'@odata.type' -eq '#microsoft.graph.group' } )
+ $aadGroupMembersServicePrincipals = $aadGroupMembers.where( { $_.'@odata.type' -eq '#microsoft.graph.servicePrincipal' } )
+
+ $aadGroupMembersAllCount = $aadGroupMembersAll.count
+ $aadGroupMembersUsersCount = $aadGroupMembersUsers.count
+ $aadGroupMembersGroupsCount = $aadGroupMembersGroups.count
+ $aadGroupMembersServicePrincipalsCount = $aadGroupMembersServicePrincipals.count
+ #for SP stuff
+ if ($aadGroupMembersServicePrincipalsCount -gt 0) {
+ foreach ($identity in $aadGroupMembersServicePrincipals) {
+ $arrayIdentityObject = [System.Collections.ArrayList]@()
+ if ($identity.servicePrincipalType -eq 'Application') {
+ if ($identity.appOwnerOrganizationId -eq $azAPICallConf['checkContext'].Tenant.Id) {
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'ServicePrincipal'
+ spTypeConcatinated = 'SP APP INT'
+ servicePrincipalType = $identity.servicePrincipalType
+ id = $identity.id
+ appid = $identity.appId
+ displayName = $identity.displayName
+ appOwnerOrganizationId = $identity.appOwnerOrganizationId
+ alternativeNames = $identity.alternativeNames
+ })
+ }
+ else {
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'ServicePrincipal'
+ spTypeConcatinated = 'SP APP EXT'
+ servicePrincipalType = $identity.servicePrincipalType
+ id = $identity.id
+ appid = $identity.appId
+ displayName = $identity.displayName
+ appOwnerOrganizationId = $identity.appOwnerOrganizationId
+ alternativeNames = $identity.alternativeNames
+ })
+ }
+ }
+ elseif ($identity.servicePrincipalType -eq 'ManagedIdentity') {
+ $miType = 'unknown'
+ if ($identity.alternativeNames) {
+ foreach ($altName in $identity.alternativeNames) {
+ if ($altName -like 'isExplicit=*') {
+ $splitAltName = $altName.split('=')
+ if ($splitAltName[1] -eq 'true') {
+ $miType = 'Usr'
+ }
+ if ($splitAltName[1] -eq 'false') {
+ $miType = 'Sys'
+ }
+ }
+ }
+ }
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'ServicePrincipal'
+ spTypeConcatinated = "SP MI $miType"
+ servicePrincipalType = $identity.servicePrincipalType
+ id = $identity.id
+ appid = $identity.appId
+ displayName = $identity.displayName
+ appOwnerOrganizationId = $identity.appOwnerOrganizationId
+ alternativeNames = $identity.alternativeNames
+ })
+ }
+ else {
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'servicePrincipal'
+ spTypeConcatinated = "SP $($identity.servicePrincipalType)"
+ servicePrincipalType = $identity.servicePrincipalType
+ id = $identity.id
+ appid = $identity.appId
+ displayName = $identity.displayName
+ appOwnerOrganizationId = $identity.appOwnerOrganizationId
+ alternativeNames = $identity.alternativeNames
+ })
+ }
+ if (-not $htServicePrincipals.($identity.id)) {
+ #Write-Host "$($identity.displayName) $($identity.id) added - - - - - - - - "
+ $script:htServicePrincipals.($identity.id) = @{}
+ $script:htServicePrincipals.($identity.id) = $arrayIdentityObject
+ }
+ }
+ }
+
+ #guests
+ if ($aadGroupMembersUsersCount -gt 0) {
+ $cntx = 0
+ $cnty = 0
+ foreach ($aadGroupMembersUser in $aadGroupMembersUsers | Sort-Object -Property id -Unique) {
+ $cntx++
+ if ($aadGroupMembersUser.userType -eq 'Guest') {
+ if (-not $htUserTypesGuest.($aadGroupMembersUser.id)) {
+ $cnty++
+ #Write-Host "$($aadGroupMembersUser.id) is Guest"
+ $script:htUserTypesGuest.($aadGroupMembersUser.id) = @{}
+ $script:htUserTypesGuest.($aadGroupMembersUser.id).userType = 'Guest'
+ }
+ else {
+ #Write-Host "$($aadGroupMembersUser.id) already known as Guest"
+ }
+ }
+ }
+ }
+
+ $script:htAADGroupsDetails.($aadGroupId).MembersAllCount = $aadGroupMembersAllCount
+ $script:htAADGroupsDetails.($aadGroupId).MembersUsersCount = $aadGroupMembersUsersCount
+ $script:htAADGroupsDetails.($aadGroupId).MembersGroupsCount = $aadGroupMembersGroupsCount
+ $script:htAADGroupsDetails.($aadGroupId).MembersServicePrincipalsCount = $aadGroupMembersServicePrincipalsCount
+
+ if ($aadGroupMembersAllCount -gt 0) {
+ $script:htAADGroupsDetails.($aadGroupId).MembersAll = $aadGroupMembersAll
+
+ if ($aadGroupMembersUsersCount -gt 0) {
+ $script:htAADGroupsDetails.($aadGroupId).MembersUsers = $aadGroupMembersUsers
+ }
+ if ($aadGroupMembersGroupsCount -gt 0) {
+ $script:htAADGroupsDetails.($aadGroupId).MembersGroups = $aadGroupMembersGroups
+ }
+ if ($aadGroupMembersServicePrincipalsCount -gt 0) {
+ $script:htAADGroupsDetails.($aadGroupId).MembersServicePrincipals = $aadGroupMembersServicePrincipals
+ }
+ }
+ }
+}
+function getMDfCSecureScoreMG {
+ $start = Get-Date
+ $currentTask = 'Getting Microsoft Defender for Cloud Secure Score for Management Groups'
+ Write-Host $currentTask
+ #ref: https://docs.microsoft.com/en-us/azure/governance/management-groups/resource-graph-samples?tabs=azure-cli#secure-score-per-management-group
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01"
+ $method = 'POST'
+
+ $query = @'
+ SecurityResources
+ | where type == 'microsoft.security/securescores'
+ | project subscriptionId,
+ subscriptionTotal = iff(properties.score.max == 0, 0.00, round(tolong(properties.weight) * todouble(properties.score.current)/tolong(properties.score.max),2)),
+ weight = tolong(iff(properties.weight == 0, 1, properties.weight))
+ | join kind=leftouter (
+ ResourceContainers
+ | where type == 'microsoft.resources/subscriptions' and properties.state == 'Enabled'
+ | project subscriptionId, mgChain=properties.managementGroupAncestorsChain )
+ on subscriptionId
+ | mv-expand mg=mgChain
+ | summarize sumSubs = sum(subscriptionTotal), sumWeight = sum(weight), resultsNum = count() by tostring(mg.displayName), mgId = tostring(mg.name)
+ | extend secureScore = iff(tolong(resultsNum) == 0, 404.00, round(sumSubs/sumWeight*100,2))
+ | project mgDisplayName=mg_displayName, mgId, sumSubs, sumWeight, resultsNum, secureScore
+ | order by mgDisplayName asc
+'@
+
+ $body = @"
+ {
+ "query": "$($query)",
+ "managementGroups":[
+ "$($ManagementGroupId)"
+ ]
+ }
+"@
+
+ $getMgAscSecureScore = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -body $body -listenOn 'Content'
+
+ if ($getMgAscSecureScore) {
+ if ($getMgAscSecureScore -eq 'capitulation') {
+ Write-Host ' Microsoft Defender for Cloud SecureScore for Management Groups will not be available' -ForegroundColor Yellow
+ }
+ else {
+ Write-Host " Retrieved 'Microsoft Defender for Cloud' SecureScore for $($getMgAscSecureScore.Count) Management Groups"
+ foreach ($entry in $getMgAscSecureScore) {
+ $script:htMgASCSecureScore.($entry.mgId) = @{}
+ if ($entry.secureScore -eq 404) {
+ $script:htMgASCSecureScore.($entry.mgId).SecureScore = 'n/a'
+ }
+ else {
+ $script:htMgASCSecureScore.($entry.mgId).SecureScore = $entry.secureScore
+ }
+ }
+ }
+ }
+
+ $end = Get-Date
+ Write-Host "Getting Microsoft Defender for Cloud Secure Score for Management Groups duration: $((New-TimeSpan -Start $start -End $end).TotalMinutes) minutes ($((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds)"
+}
+function getOrphanedResources {
+ $start = Get-Date
+ Write-Host 'Getting orphaned/unused resources (ARG)'
+
+ $queries = [System.Collections.ArrayList]@()
+ $intent = 'cost savings - stopped but not deallocated VM'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.compute/virtualmachines'
+ query = "Resources | where type =~ 'microsoft.compute/virtualmachines' and properties.extended.instanceView.powerState.code =~ 'PowerState/stopped' | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'clean up'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.resources/subscriptions/resourceGroups'
+ query = "ResourceContainers | where type =~ 'microsoft.resources/subscriptions/resourceGroups' | extend rgAndSub = strcat(resourceGroup, '--', subscriptionId) | join kind=leftouter (Resources | extend rgAndSub = strcat(resourceGroup, '--', subscriptionId) | summarize count() by rgAndSub) on rgAndSub | where isnull(count_) | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'misconfiguration'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.network/networkSecurityGroups'
+ query = "Resources | where type =~ 'microsoft.network/networkSecurityGroups' and isnull(properties.networkInterfaces) and isnull(properties.subnets) | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'misconfiguration'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.network/routeTables'
+ query = "resources | where type =~ 'microsoft.network/routeTables' | where isnull(properties.subnets) | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'misconfiguration'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.network/networkInterfaces'
+ query = "Resources | where type =~ 'microsoft.network/networkInterfaces' | where isnull(properties.privateEndpoint) | where isnull(properties.privateLinkService) | where properties.hostedWorkloads == '[]' | where properties !has 'virtualmachine' | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'cost savings'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.compute/disks'
+ query = "Resources | where type =~ 'microsoft.compute/disks' | extend diskState = tostring(properties.diskState) | where managedBy == '' | where not(name endswith '-ASRReplica' or name startswith 'ms-asr-' or name startswith 'asrseeddisk-') | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'cost savings'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.network/publicIpAddresses'
+ query = "Resources | where type =~ 'microsoft.network/publicIpAddresses' | where properties.ipConfiguration == '' and properties.natGateway == '' | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'misconfiguration'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.compute/availabilitySets'
+ query = "Resources | where type =~ 'microsoft.compute/availabilitySets' | where properties.virtualMachines == '[]' | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'misconfiguration'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.network/loadBalancers'
+ query = "Resources | where type =~ 'microsoft.network/loadBalancers' | where properties.backendAddressPools == '[]' | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $intent = 'cost savings'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.network/applicationGateways'
+ query = "resources | where type =~ 'Microsoft.Network/applicationGateways' | extend backendPoolsCount = array_length(properties.backendAddressPools),SKUName= tostring(properties.sku.name), SKUTier= tostring(properties.sku.tier),SKUCapacity=properties.sku.capacity,backendPools=properties.backendAddressPools | project type, subscriptionId, Resource=id, Intent='$intent' | join (resources | where type =~ 'Microsoft.Network/applicationGateways' | mvexpand backendPools = properties.backendAddressPools | extend backendIPCount = array_length(backendPools.properties.backendIPConfigurations) | extend backendAddressesCount = array_length(backendPools.properties.backendAddresses) | extend backendPoolName = backendPools.properties.backendAddressPools.name | extend Resource = id | summarize backendIPCount = sum(backendIPCount) ,backendAddressesCount=sum(backendAddressesCount) by Resource) on Resource | project-away Resource1 | where (backendIPCount == 0 or isempty(backendIPCount)) and (backendAddressesCount==0 or isempty(backendAddressesCount)) | order by Resource asc"
+ intent = $intent
+ })
+
+ $intent = 'cost savings'
+ $null = $queries.Add([PSCustomObject]@{
+ queryName = 'microsoft.web/serverfarms'
+ query = "Resources | where type =~ 'microsoft.web/serverfarms' | where properties.numberOfSites == 0 | project type, subscriptionId, Resource=id, Intent='$intent'"
+ intent = $intent
+ })
+
+ $queries | ForEach-Object -Parallel {
+ $queryDetail = $_
+ $arrayOrphanedResources = $using:arrayOrphanedResources
+ $subsToProcessInCustomDataCollection = $using:subsToProcessInCustomDataCollection
+ $azAPICallConf = $using:azAPICallConf
+
+ #Batching: https://docs.microsoft.com/en-us/azure/governance/resource-graph/troubleshoot/general#toomanysubscription
+ $counterBatch = [PSCustomObject] @{ Value = 0 }
+ $batchSize = 1000
+ $subscriptionsBatch = $subsToProcessInCustomDataCollection | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) }
+
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01"
+ $method = 'POST'
+ foreach ($batch in $subscriptionsBatch) {
+ Write-Host " Getting orphaned $($queryDetail.queryName) for $($batch.Group.subscriptionId.Count) Subscriptions"
+ $subscriptions = '"{0}"' -f ($batch.Group.subscriptionId -join '","')
+ $body = @"
+{
+ "query": "$($queryDetail.query)",
+ "subscriptions": [$($subscriptions)],
+ "options": {
+ "`$top": 1000
+ }
+}
+"@
+
+ $res = (AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -listenOn 'Content' -currentTask "Getting orphaned $($queryDetail.queryName)")
+ #Write-Host '$res.count:' $res.count
+ if ($res.count -gt 0) {
+ foreach ($resource in $res) {
+ $null = $script:arrayOrphanedResources.Add($resource)
+ }
+ }
+ Write-Host " $($res.count) orphaned $($queryDetail.queryName) found"
+ }
+ } -ThrottleLimit ($queries.Count)
+
+ if ($arrayOrphanedResources.Count -gt 0) {
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ $allConsumptionDataGroupedByTypeAndCurrency = $allConsumptionData | Group-Object -Property ResourceType, Currency
+ $orphanedResourcesResourceTypesCostRelevant = ($queries.where({ $_.intent -like 'cost savings*' })).queryName
+
+ $htC = @{}
+ foreach ($consumptionResourceTypeAndCurrency in $allConsumptionDataGroupedByTypeAndCurrency) {
+ $consumptionResourceTypeAndCurrencySplitted = $consumptionResourceTypeAndCurrency.Name.split(', ')
+ if ($consumptionResourceTypeAndCurrencySplitted[0] -in $orphanedResourcesResourceTypesCostRelevant ) {
+ foreach ($entry in $consumptionResourceTypeAndCurrency.Group) {
+ if (-not $htC.($entry.resourceId)) {
+ $htC.($entry.resourceId) = @{}
+ $htC.($entry.resourceId).cost = $entry.PreTaxCost
+ $htC.($entry.resourceId).currency = $entry.Currency
+ }
+ else {
+ $htC.($entry.resourceId).cost = $htC.($entry.resourceId).cost + $entry.PreTaxCost
+ }
+ }
+ }
+ }
+
+ $costrelevantOrphanedResourcesGroupedByType = ($arrayOrphanedResources | Group-Object -Property intent).where({ $_.name -like 'cost savings*' }).group | Group-Object -Property type
+ $nonCostrelevantOrphanedResourcesGroupedByType = ($arrayOrphanedResources | Group-Object -Property intent).where({ $_.name -notlike 'cost savings*' }).group | Group-Object -Property type
+ $script:arrayOrphanedResources = [System.Collections.ArrayList]@()
+
+ foreach ($costrelevantOrphanedResourceType in $costrelevantOrphanedResourcesGroupedByType) {
+ foreach ($resource in $costrelevantOrphanedResourceType.Group) {
+ if ($htC.($resource.Resource)) {
+ $null = $script:arrayOrphanedResources.Add([PSCustomObject]@{
+ Type = $costrelevantOrphanedResourceType.Name
+ Resource = $resource.Resource
+ SubscriptionId = $resource.subscriptionId
+ Intent = $resource.Intent
+ Cost = $htC.($resource.Resource).cost
+ Currency = $htC.($resource.Resource).currency
+ })
+ }
+ else {
+ $null = $script:arrayOrphanedResources.Add([PSCustomObject]@{
+ Type = $costrelevantOrphanedResourceType.Name
+ Resource = $resource.Resource
+ SubscriptionId = $resource.subscriptionId
+ Intent = $resource.Intent
+ Cost = ''
+ Currency = ''
+ })
+ }
+ }
+ }
+
+ foreach ($nonCostrelevantOrphanedResourceType in $nonCostrelevantOrphanedResourcesGroupedByType) {
+ Write-Host "Processing $($nonCostrelevantOrphanedResourceType.Name)"
+ foreach ($resource in $nonCostrelevantOrphanedResourceType.Group) {
+ $null = $script:arrayOrphanedResources.Add([PSCustomObject]@{
+ Type = $nonCostrelevantOrphanedResourceType.Name
+ Resource = $resource.Resource
+ SubscriptionId = $resource.subscriptionId
+ Intent = $resource.Intent
+ Cost = ''
+ Currency = ''
+ })
+ }
+ }
+ }
+
+ Write-Host " Found $($arrayOrphanedResources.Count) orphaned/unused Resources"
+ if (-not $NoCsvExport) {
+ Write-Host " Exporting OrphanedResources CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourcesCostOptimizationAndCleanup.csv'"
+ $arrayOrphanedResources | Sort-Object -Property Resource | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourcesCostOptimizationAndCleanup.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+ }
+ else {
+ Write-Host ' No orphaned/unused Resources found'
+ }
+
+ $end = Get-Date
+ Write-Host "Getting orphaned/unused resources (ARG) processing duration: $((New-TimeSpan -Start $start -End $end).TotalMinutes) minutes ($((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds)"
+}
+function getPIMEligible {
+ $start = Get-Date
+
+ $currentTask = 'Get PIM onboarded Subscriptions and Management Groups'
+ Write-Host $currentTask
+ $uriExt = "&`$expand=parent&`$filter=(type eq 'subscription' or type eq 'managementgroup')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/privilegedAccess/azureResources/resources?`$select=id,displayName,type,externalId" + $uriExt
+ $res = AzAPICall -AzAPICallConfiguration $azapicallConf -uri $uri -currentTask $currentTask
+ if ($res.Count -gt 0) {
+
+ $scopesToIterate = [System.Collections.ArrayList]@()
+ if (-not $PIMEligibilityIgnoreScope) {
+ if (($azAPICallConf['checkContext']).Tenant.Id -ne $ManagementGroupId) {
+ foreach ($entry in $res) {
+ if ($entry.type -eq 'managementGroup') {
+ if ($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain -contains ($entry.externalId -replace '.*/') -or $htManagementGroupsMgPath.($entry.externalId -replace '.*/').path -contains $ManagementGroupId) {
+ $null = $scopesToIterate.Add($entry)
+ }
+ }
+ if ($entry.type -eq 'subscription') {
+ if ($htSubscriptionsMgPath.($entry.externalId -replace '.*/').ParentNameChain -contains $ManagementGroupId) {
+ if ($htOutOfScopeSubscriptions.($entry.externalId -replace '.*/')) {
+ Write-Host "excluding subscription $($entry.externalId -replace '.*/') (outOfScopeSubscription -> $($htOutOfScopeSubscriptions.($entry.externalId -replace '.*/').outOfScopeReason)) (`$PIMEligibilityIgnoreScope=$PIMEligibilityIgnoreScope)"
+ }
+ else {
+ $null = $scopesToIterate.Add($entry)
+ }
+ }
+ }
+ }
+ }
+ else {
+ foreach ($entry in $res) {
+ if ($htOutOfScopeSubscriptions.($entry.externalId -replace '.*/')) {
+ Write-Host "excluding subscription $($entry.externalId -replace '.*/') (outOfScopeSubscription -> $($htOutOfScopeSubscriptions.($entry.externalId -replace '.*/').outOfScopeReason)) (`$PIMEligibilityIgnoreScope=$PIMEligibilityIgnoreScope)"
+ }
+ else {
+ $null = $scopesToIterate.Add($entry)
+ }
+ }
+ }
+ }
+ else {
+ foreach ($entry in $res) {
+ $null = $scopesToIterate.Add($entry)
+ }
+ }
+
+ $PIMOnboardedGrouped = $scopesToIterate | Group-Object -Property type
+ foreach ($entry in $PIMOnboardedGrouped) {
+ Write-Host " Found $($entry.Count) PIM onboarded $($entry.Name)s"
+ }
+
+ $htPIMEligibleDirect = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{}
+ $relevantSubscriptionIds = $subsToProcessInCustomDataCollection.subscriptionId
+ $scopesToIterate | ForEach-Object -Parallel {
+ $scope = $_
+ $azAPICallConf = $using:azAPICallConf
+ $arrayPIMEligible = $using:arrayPIMEligible
+ $htPIMEligibleDirect = $using:htPIMEligibleDirect
+ if ($scope.type -eq 'managementgroup') { $htManagementGroupsMgPath = $using:htManagementGroupsMgPath }
+ if ($scope.type -eq 'subscription') { $htSubscriptionsMgPath = $using:htSubscriptionsMgPath }
+ $htPrincipals = $using:htPrincipals
+ $htUserTypesGuest = $using:htUserTypesGuest
+ $htServicePrincipals = $using:htServicePrincipals
+ $relevantSubscriptionIds = $using:relevantSubscriptionIds
+ $function:resolveObjectIds = $using:funcResolveObjectIds
+ $function:testGuid = $using:funcTestGuid
+
+ $processThisScope = $true
+ if ($scope.type -eq 'subscription') {
+ if (($scope.externalId -replace '.*/') -notin $relevantSubscriptionIds) {
+ Write-Host " Non relevant subscriptionId '$(($scope.externalId -replace '.*/'))' /skipping this subscription as it is not contained in the 'Relevant Subscriptions' collection (needs investigation)" -ForegroundColor DarkRed
+ $processThisScope = $false
+ }
+ }
+
+ if ($processThisScope -eq $true) {
+ $currentTask = "Get Eligible assignments for Scope $($scope.type): $($scope.externalId -replace '.*/')"
+ $extUri = "?`$expand=linkedEligibleRoleAssignment,subject,roleDefinition(`$expand=resource)&`$count=true&`$filter=(roleDefinition/resource/id eq '$($scope.id)')+and+(assignmentState eq 'Eligible')&`$top=100"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/privilegedAccess/azureResources/roleAssignments" + $extUri
+ $resx = AzAPICall -AzAPICallConfiguration $azapicallConf -currentTask $currentTask -uri $uri
+
+ if ($resx.Count -gt 0) {
+
+ $users = $resx.where({ $_.subject.type -eq 'user' })
+ if ($users.Count -gt 0) {
+ ResolveObjectIds -objectIds $users.subject.id -showActivity
+ }
+
+ foreach ($entry in $resx) {
+ $scopeId = $scope.externalId -replace '.*/'
+ if ($scope.type -eq 'managementgroup') {
+ $ScopeType = 'MG'
+ $ManagementGroupId = $scopeId
+ $SubscriptionId = ''
+ $SubscriptionDisplayName = ''
+ if ($htManagementGroupsMgPath.($scopeId)) {
+ $MgDetails = $htManagementGroupsMgPath.($scopeId)
+ $ManagementGroupDisplayName = $MgDetails.DisplayName
+ $ScopeDisplayName = $MgDetails.DisplayName
+ $MgPath = $MgDetails.path
+ $MgLevel = $MgDetails.level
+ }
+ else {
+ $ManagementGroupDisplayName = 'notAccessible'
+ $ScopeDisplayName = 'notAccessible'
+ $MgPath = 'notAccessible'
+ $MgLevel = 'notAccessible'
+ }
+
+ if ($entry.memberType -eq 'direct') {
+ $script:htPIMEligibleDirect.($entry.id) = @{}
+ $script:htPIMEligibleDirect.($entry.id).clear = $scopeId
+ if ($scopeId -eq $ManagementGroupDisplayName) {
+ $script:htPIMEligibleDirect.($entry.id).enriched = "$($scopeId) [Level $($MgLevel)]"
+ }
+ else {
+ $script:htPIMEligibleDirect.($entry.id).enriched = "$($ManagementGroupDisplayName) ($($scopeId)) [Level $($MgLevel)]"
+ }
+ }
+ }
+ if ($scope.type -eq 'subscription') {
+ $ScopeType = 'Sub'
+ #$ManagementGroupId = ''
+ $SubscriptionId = $scopeId
+ if ($htSubscriptionsMgPath.($scopeId)) {
+ $MgDetails = $htSubscriptionsMgPath.($scopeId)
+ $SubscriptionDisplayName = $MgDetails.DisplayName
+ $ScopeDisplayName = $MgDetails.DisplayName
+ $MgPath = $MgDetails.path
+ $MgLevel = $MgDetails.level
+ $ManagementGroupId = $MgDetails.Parent
+ $ManagementGroupDisplayName = $MgDetails.ParentName
+ }
+ else {
+ $SubscriptionDisplayName = 'notAccessible'
+ $ScopeDisplayName = 'notAccessible'
+ $MgPath = 'notAccessible'
+ $MgLevel = 'notAccessible'
+ }
+ #$ManagementGroupDisplayName = ''
+
+ }
+
+ if ($entry.subject.type -eq 'user') {
+ if ($htPrincipals.($entry.subject.id)) {
+ $userDetail = $htPrincipals.($entry.subject.id)
+ $principalType = "$($userDetail.type) $($userDetail.userType)"
+ }
+ else {
+ $principalType = $entry.subject.type
+ }
+ }
+ else {
+ $principalType = $entry.subject.type
+ }
+
+ $roleType = 'undefined'
+ if ($entry.roleDefinition.type -eq 'BuiltInRole') { $roleType = 'Builtin' }
+ if ($entry.roleDefinition.type -eq 'CustomRole') { $roleType = 'Custom' }
+
+ $null = $script:arrayPIMEligible.Add([PSCustomObject]@{
+ ScopeType = $ScopeType
+ ScopeId = $scopeId
+ ScopeDisplayName = $ScopeDisplayName
+ ManagementGroupId = $ManagementGroupId
+ ManagementGroupDisplayName = $ManagementGroupDisplayName
+ SubscriptionId = $SubscriptionId
+ SubscriptionDisplayName = $SubscriptionDisplayName
+ MgPath = $MgPath
+ MgLevel = $MgLevel
+ RoleId = $entry.roleDefinition.externalId
+ RoleIdGuid = $entry.roleDefinition.externalId -replace '.*/'
+ RoleType = $roleType
+ RoleName = $entry.roleDefinition.displayName
+ IdentityObjectId = $entry.subject.id
+ IdentityType = $principalType
+ IdentityDisplayName = $entry.subject.displayName
+ IdentityPrincipalName = $entry.subject.principalName
+ PIMId = $entry.id
+ PIMInheritance = $entry.memberType
+ PIMInheritedFromClear = ''
+ PIMInheritedFrom = ''
+ PIMStartDateTime = $entry.startDateTime
+ PIMEndDateTime = $entry.endDateTime
+ })
+ }
+ }
+ }
+
+ } -ThrottleLimit $ThrottleLimit
+
+ foreach ($entry in $arrayPIMEligible) {
+ if ($entry.PIMInheritance -eq 'inherited') {
+ $entry.PIMInheritedFromClear = $htPIMEligibleDirect.($entry.PIMId).clear
+ $entry.PIMInheritedFrom = $htPIMEligibleDirect.($entry.PIMId).enriched
+ }
+ }
+
+ $script:arrayPIMEligibleGrouped = $arrayPIMEligible | Group-Object -Property ScopeType
+ foreach ($entry in $arrayPIMEligibleGrouped) {
+ Write-Host " Found $($entry.Count) PIM Eligible assignments for $($entry.Name)s"
+ }
+ }
+
+ $end = Get-Date
+ Write-Host "Getting PIM Eligible assignments processing duration: $((New-TimeSpan -Start $start -End $end).TotalMinutes) minutes ($((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds)"
+}
+function getPolicyHash {
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory)]
+ [string]
+ $json
+ )
+ return [System.BitConverter]::ToString([System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($json)))
+}
+function getPolicyRemediation {
+ $currentTask = 'Getting NonCompliant (dine/modify)'
+ Write-Host $currentTask
+ #ref: https://learn.microsoft.com/en-us/rest/api/azureresourcegraph/resourcegraph(2021-03-01)/resources/resources
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01"
+ $method = 'POST'
+
+ if ($ManagementGroupsOnly) {
+ $queryNonCompliant = @'
+ policyresources
+ | where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/' and (properties.policyDefinitionAction =~ 'deployifnotexists' or properties.policyDefinitionAction =~ 'modify') and properties.complianceState =~ 'NonCompliant'
+ | summarize count() by assignmentScope = tostring(properties.policyAssignmentScope), assignmentName = tostring(properties.policyAssignmentName), assignmentId = tostring(properties.policyAssignmentId), definitionName = tostring(properties.policyDefinitionName), definitionId = tostring(properties.policyDefinitionId), policyDefinitionReferenceId = tostring(properties.policyDefinitionReferenceId), effect = tostring(properties.policyDefinitionAction)
+ | sort by count_, assignmentId, definitionId, policyDefinitionReferenceId, effect
+'@
+ }
+ else {
+ $queryNonCompliant = @'
+ policyresources
+ | where (properties.policyDefinitionAction =~ 'deployifnotexists' or properties.policyDefinitionAction =~ 'modify') and properties.complianceState =~ 'NonCompliant'
+ | summarize count() by assignmentScope = tostring(properties.policyAssignmentScope), assignmentName = tostring(properties.policyAssignmentName), assignmentId = tostring(properties.policyAssignmentId), definitionName = tostring(properties.policyDefinitionName), definitionId = tostring(properties.policyDefinitionId), policyDefinitionReferenceId = tostring(properties.policyDefinitionReferenceId), effect = tostring(properties.policyDefinitionAction)
+ | sort by count_, assignmentId, definitionId, policyDefinitionReferenceId, effect
+'@
+ }
+
+
+ $body = @"
+ {
+ "query": "$($queryNonCompliant)",
+ "managementGroups":[
+ "$($ManagementGroupId)"
+ ],
+ "options": {
+ "`$top": 1000
+ }
+ }
+"@
+
+ $getNonCompliant = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -body $body -listenOn 'Content'
+ $script:arrayRemediatable = [System.Collections.ArrayList]@()
+ Write-Host " Found $($getNonCompliant.Count) remediatable Policy definitions"
+ if ($getNonCompliant.Count -gt 0) {
+ Write-Host ' Enriching remediatable assignments with displayNames'
+ foreach ($nonCompliant in $getNonCompliant) {
+
+ if ($htCacheAssignmentsPolicy.($nonCompliant.assignmentId.toLower())) {
+ if ($htCacheAssignmentsPolicy.($nonCompliant.assignmentId.toLower()).assignment.properties.policyDefinitionId -like '*/providers/Microsoft.Authorization/policySetDefinitions/*') {
+ $policyAssignmentPolicyOrPolicySet = 'policySetDefinition'
+ $policySetDefinitionId = $htCacheAssignmentsPolicy.($nonCompliant.assignmentId.toLower()).assignment.properties.policyDefinitionId
+ $policySetDefinitionDisplayName = $htCacheDefinitionsPolicySet.($policySetDefinitionId.ToLower()).DisplayName
+ $policySetDefinitionName = $policySetDefinitionId -replace '.*/'
+ $policySetDefinitionType = $htCacheDefinitionsPolicySet.($policySetDefinitionId.ToLower()).Type
+ }
+ elseif ($htCacheAssignmentsPolicy.($nonCompliant.assignmentId.toLower()).assignment.properties.policyDefinitionId -like '*/providers/Microsoft.Authorization/policyDefinitions/*') {
+ $policyAssignmentPolicyOrPolicySet = 'policyDefinition'
+ $policySetDefinitionId = 'n/a'
+ $policySetDefinitionDisplayName = 'n/a'
+ $policySetDefinitionName = 'n/a'
+ $policySetDefinitionType = 'n/a'
+ }
+ else {
+ throw "unexpected .policyDefinitionId: $($htCacheAssignmentsPolicy.($nonCompliant.assignmentId.toLower()).assignment.properties)"
+ }
+
+ switch ($nonCompliant.assignmentId) {
+ { $_ -like '/subscriptions/*' } {
+ $policyAssignmentScopeType = 'Sub'
+ }
+ { $_ -like '/subscriptions/*/resourcegroups/*' } {
+ $policyAssignmentScopeType = 'RG'
+ }
+ { $_ -like '/providers/Microsoft.Management/managementGroups/*' } {
+ $policyAssignmentScopeType = 'MG'
+ }
+ default {
+ $policyAssignmentScopeType = 'notDetected'
+ }
+ }
+
+ $null = $script:arrayRemediatable.Add([PSCustomObject]@{
+ policyAssignmentScopeType = $policyAssignmentScopeType
+ policyAssignmentScope = $nonCompliant.assignmentScope
+ policyAssignmentId = $nonCompliant.assignmentId
+ policyAssignmentName = $nonCompliant.assignmentName
+ policyAssignmentDisplayName = $htCacheAssignmentsPolicy.($nonCompliant.assignmentId.toLower()).assignment.properties.displayName
+ policyAssignmentPolicyOrPolicySet = $policyAssignmentPolicyOrPolicySet
+ effect = $nonCompliant.effect
+ policyDefinitionId = $nonCompliant.definitionId
+ policyDefinitionName = $nonCompliant.definitionName
+ policyDefinitionDisplayName = $htCacheDefinitionsPolicy.($nonCompliant.definitionId.toLower()).Json.properties.displayName
+ policyDefinitionType = $htCacheDefinitionsPolicy.($nonCompliant.definitionId.toLower()).Type
+ policySetPolicyDefinitionReferenceId = $nonCompliant.policyDefinitionReferenceId
+ policySetDefinitionId = $policySetDefinitionId
+ policySetDefinitionName = $policySetDefinitionName
+ policySetDefinitionDisplayName = $policySetDefinitionDisplayName
+ policySetDefinitionType = $policySetDefinitionType
+ nonCompliantResourcesCount = $nonCompliant.count_
+ })
+ }
+ else {
+ Write-Host " skipping `$htCacheAssignmentsPolicy.($($nonCompliant.assignmentId)) potentially an assignment on an out-of-scope subscription"
+ }
+ }
+ }
+}
+function getResourceDiagnosticsCapability {
+ Write-Host 'Checking Resource Types Diagnostics capability (1st party only)'
+ $startResourceDiagnosticsCheck = Get-Date
+ if (($resourcesAll).count -gt 0) {
+
+ $startGroupResourceIdsByType = Get-Date
+ $script:resourceTypesUnique = ($resourcesIdsAll | Group-Object -Property type)
+ $endGroupResourceIdsByType = Get-Date
+ Write-Host " GroupResourceIdsByType processing duration: $((New-TimeSpan -Start $startGroupResourceIdsByType -End $endGroupResourceIdsByType).TotalSeconds) seconds)"
+ $resourceTypesUniqueCount = ($resourceTypesUnique | Measure-Object).count
+ Write-Host " $($resourceTypesUniqueCount) unique Resource Types"
+ $script:resourceTypesSummarizedArray = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
+
+ $script:resourceTypesDiagnosticsArray = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
+ $microsoftResourceTypes = $resourceTypesUnique.where({ $_.Name.StartsWith('microsoft') })
+ if ($microsoftResourceTypes.Count -gt 0) {
+ $microsoftResourceTypes | ForEach-Object -Parallel {
+ $resourceTypesUniqueGroup = $_
+ $resourcetype = $resourceTypesUniqueGroup.Name
+ #region UsingVARs
+ #fromOtherFunctions
+ $azAPICallConf = $using:azAPICallConf
+ $scriptPath = $using:ScriptPath
+ #Array&HTs
+ $ExcludedResourceTypesDiagnosticsCapable = $using:ExcludedResourceTypesDiagnosticsCapable
+ $resourceTypesDiagnosticsArray = $using:resourceTypesDiagnosticsArray
+ $htResourceTypesUniqueResource = $using:htResourceTypesUniqueResource
+ $resourceTypesSummarizedArray = $using:resourceTypesSummarizedArray
+ #endregion UsingVARs
+
+ $skipThisResourceType = $false
+ if (($ExcludedResourceTypesDiagnosticsCapable).Count -gt 0) {
+ foreach ($excludedResourceType in $ExcludedResourceTypesDiagnosticsCapable) {
+ if ($excludedResourceType -eq $resourcetype) {
+ $skipThisResourceType = $true
+ }
+ }
+ }
+
+ if ($skipThisResourceType -eq $false) {
+ $resourceCount = $resourceTypesUniqueGroup.Count
+
+ #thx @Jim Britt (Microsoft) https://github.com/JimGBritt/AzurePolicy/tree/master/AzureMonitor/Scripts Create-AzDiagPolicy.ps1
+ $responseJSON = ''
+ $logCategories = @()
+ $metrics = $false
+ $logs = $false
+
+ $resourceAvailability = ($resourceCount - 1)
+ $counterTryForResourceType = 0
+ do {
+ $counterTryForResourceType++
+ if ($resourceCount -gt 1) {
+ $resourceId = $resourceTypesUniqueGroup.Group.Id[$resourceAvailability]
+ }
+ else {
+ $resourceId = $resourceTypesUniqueGroup.Group.Id
+ }
+
+ $resourceAvailability = $resourceAvailability - 1
+ if ($resourceId -like '*+*') {
+ Write-Host "resourceId '$resourceId' contains bad character '+'; skipping resourceId"
+ $responseJSON = 'skipResource'
+ }
+ else {
+ $currentTask = "Checking if ResourceType '$resourceType' is capable for Resource Diagnostics using $counterTryForResourceType ResourceId: '$($resourceId)'"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/$($resourceId)/providers/microsoft.insights/diagnosticSettingsCategories?api-version=2021-05-01-preview"
+ $method = 'GET'
+ $responseJSON = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri ([uri]::EscapeUriString($uri)) -method $method -currentTask $currentTask
+ }
+
+ if ($responseJSON -ne 'skipResource') {
+ if ($responseJSON -eq 'ResourceTypeOrResourceProviderNotSupported') {
+ Write-Host " ResourceTypeOrResourceProviderNotSupported | The resource type '$($resourcetype)' does not support diagnostic settings."
+
+ }
+ else {
+ Write-Host " ResourceTypeSupported | The resource type '$($resourcetype)' supports diagnostic settings."
+ }
+ }
+ else {
+ Write-Host "resId '$resourceId' skipped"
+ }
+ }
+ until ($resourceAvailability -lt 0 -or $responseJSON -ne 'skipResource')
+
+ if ($resourceAvailability -lt 0 -and $responseJSON -eq 'skipResource') {
+ Write-Host "tried for all available resourceIds ($($resourceCount)) for resourceType $resourceType, but seems all resourceIds needed to be skipped"
+ $null = $script:resourceTypesDiagnosticsArray.Add([PSCustomObject]@{
+ ResourceType = $resourcetype
+ Metrics = "n/a - $responseJSON"
+ Logs = "n/a - $responseJSON"
+ LogCategories = 'n/a'
+ ResourceCount = $resourceCount
+ })
+ }
+ else {
+ if ($responseJSON) {
+ foreach ($response in $responseJSON) {
+ if ($response.properties.categoryType -eq 'Metrics') {
+ $metrics = $true
+ }
+ if ($response.properties.categoryType -eq 'Logs') {
+ $logs = $true
+ $logCategories += $response.name
+ }
+ }
+ }
+
+ $null = $script:resourceTypesDiagnosticsArray.Add([PSCustomObject]@{
+ ResourceType = $resourcetype
+ Metrics = $metrics
+ Logs = $logs
+ LogCategories = $logCategories
+ ResourceCount = $resourceCount
+ })
+ }
+ }
+ else {
+ Write-Host "Skipping ResourceType $($resourcetype) as per parameter '-ExcludedResourceTypesDiagnosticsCapable'"
+ }
+ } -ThrottleLimit $ThrottleLimit
+ }
+ else {
+ Write-Host ' No 1st party Resource Types at all'
+ }
+
+ }
+ else {
+ Write-Host ' No Resources at all'
+ }
+ $endResourceDiagnosticsCheck = Get-Date
+ Write-Host "Checking Resource Types Diagnostics capability duration: $((New-TimeSpan -Start $startResourceDiagnosticsCheck -End $endResourceDiagnosticsCheck).TotalMinutes) minutes ($((New-TimeSpan -Start $startResourceDiagnosticsCheck -End $endResourceDiagnosticsCheck).TotalSeconds) seconds)"
+}
+function getSubscriptions {
+ $startGetSubscriptions = Get-Date
+ $currentTask = 'Getting all Subscriptions'
+ Write-Host "$currentTask"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions?api-version=2020-01-01"
+ $method = 'GET'
+ $requestAllSubscriptionsAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask
+
+ $script:htAllSubscriptionsFromAPI = @{}
+ $script:htSubscriptionsFromOtherTenants = @{}
+
+ Write-Host " $($requestAllSubscriptionsAPI.Count) Subscriptions returned"
+ foreach ($subscription in $requestAllSubscriptionsAPI) {
+
+ if ($subscription.tenantId -ne $azAPICallConf['checkcontext'].tenant.id) {
+ Write-Host " Finding: $($subscription.displayName) ($($subscription.subscriptionId)) belongs to foreign tenant '$($subscription.tenantId)' - Azure Governance Visualizer: excluding this Subscripion" -ForegroundColor DarkRed
+ $script:htSubscriptionsFromOtherTenants.($subscription.subscriptionId) = @{}
+ $script:htSubscriptionsFromOtherTenants.($subscription.subscriptionId).subDetails = $subscription
+ }
+ else {
+ $script:htAllSubscriptionsFromAPI.($subscription.subscriptionId) = @{}
+ $script:htAllSubscriptionsFromAPI.($subscription.subscriptionId).subDetails = $subscription
+ }
+ }
+ Write-Host " $($htAllSubscriptionsFromAPI.Keys.Count) Subscriptions relevant"
+
+ $endGetSubscriptions = Get-Date
+ Write-Host "Getting all Subscriptions duration: $((New-TimeSpan -Start $startGetSubscriptions -End $endGetSubscriptions).TotalSeconds) seconds"
+}
+function getTenantDetails {
+ $currentTask = 'Get Tenant details'
+ Write-Host $currentTask
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/tenants?api-version=2020-01-01"
+ $method = 'GET'
+ $tenantDetailsResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask
+
+ if (($tenantDetailsResult).count -gt 0) {
+ $tenantDetails = $tenantDetailsResult | Where-Object { $_.tenantId -eq ($azAPICallConf['checkContext']).Tenant.Id }
+ if ($tenantDetails.displayName) {
+ $script:tenantDisplayName = $tenantDetails.displayName
+ Write-Host " Tenant DisplayName: $tenantDisplayName"
+ }
+ else {
+ Write-Host ' Tenant DisplayName: could not be retrieved'
+ }
+
+ if ($tenantDetails.defaultDomain) {
+ $script:tenantDefaultDomain = $tenantDetails.defaultDomain
+ }
+ }
+ else {
+ Write-Host ' something unexpected'
+ }
+}
+function handleCloudEnvironment {
+ Write-Host "Environment: $($azAPICallConf['checkContext'].Environment.Name)"
+ if ($DoAzureConsumption) {
+ if ($azAPICallConf['checkContext'].Environment.Name -eq 'AzureChinaCloud') {
+ Write-Host 'Azure Billing not supported in AzureChinaCloud, skipping Consumption..'
+ $script:DoAzureConsumption = $false
+ }
+ }
+}
+function NamingValidation($toCheck) {
+ $checks = @(':', '/', '\', '<', '>', '|', '"')
+ $array = @()
+ foreach ($check in $checks) {
+ if ($toCheck -like "*$($check)*") {
+ $array += $check
+ }
+ }
+ if ($toCheck -match '\*') {
+ $array += '*'
+ }
+ if ($toCheck -match '\?') {
+ $array += '?'
+ }
+ return $array
+}
+function prepareData {
+ Write-Host 'Preparing Data'
+ $startPreparingArrays = Get-Date
+ $script:optimizedTableForPathQuery = ($newTable | Select-Object -Property level, mg*, subscription*) | Sort-Object -Property level, mgid, subscriptionId -Unique
+ $hlperOptimizedTableForPathQuery = $optimizedTableForPathQuery.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) } )
+ $script:optimizedTableForPathQueryMgAndSub = ($hlperOptimizedTableForPathQuery | Select-Object -Property level, mg*, subscription*) | Sort-Object -Property level, mgid, mgname, mgparentId, mgparentName, subscriptionId, subscription -Unique
+ $script:optimizedTableForPathQueryMg = ($optimizedTableForPathQuery.where( { [String]::IsNullOrEmpty($_.SubscriptionId) } ) | Select-Object -Property level, mgid, mgName, mgparentid, mgparentName) | Sort-Object -Property level, mgid, mgname, mgparentId, mgparentName -Unique
+ $script:optimizedTableForPathQuerySub = ($hlperOptimizedTableForPathQuery | Select-Object -Property subscription*) | Sort-Object -Property subscriptionId -Unique
+
+ foreach ($entry in $optimizedTableForPathQuery) {
+ $script:htMgDetails.($entry.mgId) = @{}
+ $mgSubs = $optimizedTableForPathQueryMgAndSub.where( { $_.mgId -eq $entry.mgId } )
+ $script:htMgDetails.($entry.mgId).subscriptionsCount = $mgSubs.Count
+ $script:htMgDetails.($entry.mgId).subscriptions = $mgSubs
+ $script:htMgDetails.($entry.mgId).details = $entry
+ $mgChildren = ($optimizedTableForPathQueryMg.where( { $_.mgParentId -eq $entry.mgId } )).MgId
+ $script:htMgDetails.($entry.mgId).mgChildren = $mgChildren
+ $script:htMgDetails.($entry.mgId).mgChildrenCount = $mgChildren.Count
+ }
+
+ foreach ($entry in $optimizedTableForPathQueryMgAndSub) {
+ $script:htSubDetails.($entry.SubscriptionId) = @{}
+ $script:htSubDetails.($entry.SubscriptionId).details = $optimizedTableForPathQueryMgAndSub.where( { $_.SubscriptionId -eq $entry.SubscriptionId } )
+ }
+
+ $script:parentMgBaseQuery = ($optimizedTableForPathQueryMg.where( { $_.MgParentId -eq $getMgParentId } ))
+ $script:parentMgNamex = $parentMgBaseQuery.mgParentName | Get-Unique
+ $script:parentMgIdx = $parentMgBaseQuery.mgParentId | Get-Unique
+
+ $endPreparingArrays = Get-Date
+ Write-Host "Preparing Arrays duration: $((New-TimeSpan -Start $startPreparingArrays -End $endPreparingArrays).TotalMinutes) minutes ($((New-TimeSpan -Start $startPreparingArrays -End $endPreparingArrays).TotalSeconds) seconds)"
+}
+function processAADGroups {
+ if ($NoPIMEligibility) {
+ Write-Host 'Resolving AAD Groups (for which a RBAC Role assignment exists)'
+ }
+ else {
+ Write-Host 'Resolving AAD Groups (for which a RBAC Role assignment or PIM Eligibility exists)'
+ }
+
+ Write-Host " Users known as Guest count: $($htUserTypesGuest.Keys.Count) (before Resolving AAD Groups)"
+ $startAADGroupsResolveMembers = Get-Date
+
+ $roleAssignmentsforGroups = ($roleAssignmentsUniqueById.where( { $_.RoleAssignmentIdentityObjectType -eq 'Group' } ) | Select-Object -Property RoleAssignmentIdentityObjectId, RoleAssignmentIdentityDisplayname) | Sort-Object -Property RoleAssignmentIdentityObjectId -Unique
+ $optimizedTableForAADGroupsQuery = [System.Collections.ArrayList]@()
+ if ($roleAssignmentsforGroups.Count -gt 0) {
+ foreach ($roleAssignmentforGroups in $roleAssignmentsforGroups) {
+ $null = $optimizedTableForAADGroupsQuery.Add($roleAssignmentforGroups)
+ }
+ }
+
+ $aadGroupsCount = ($optimizedTableForAADGroupsQuery).Count
+ Write-Host " $aadGroupsCount Groups from RoleAssignments"
+
+ if (-not $NoPIMEligibility) {
+ $PIMEligibleGroups = $arrayPIMEligible.where({ $_.IdentityType -eq 'Group' }) | Select-Object IdentityObjectId, IdentityDisplayName | Sort-Object -Property IdentityObjectId -Unique
+ $cntPIMEligibleGroupsTotal = 0
+ $cntPIMEligibleGroupsNotCoveredFromRoleAssignments = 0
+ foreach ($PIMEligibleGroup in $PIMEligibleGroups) {
+ $cntPIMEligibleGroupsTotal++
+ if ($optimizedTableForAADGroupsQuery.RoleAssignmentIdentityObjectId -notcontains $PIMEligibleGroup.IdentityObjectId) {
+ $cntPIMEligibleGroupsNotCoveredFromRoleAssignments++
+ $null = $optimizedTableForAADGroupsQuery.Add([PSCustomObject]@{
+ RoleAssignmentIdentityObjectId = $PIMEligibleGroup.IdentityObjectId
+ RoleAssignmentIdentityDisplayname = $PIMEligibleGroup.IdentityDisplayName
+ })
+ }
+ }
+ Write-Host " $cntPIMEligibleGroupsTotal Groups from PIM Eligibility; $cntPIMEligibleGroupsNotCoveredFromRoleAssignments Groups added ($($cntPIMEligibleGroupsTotal - $cntPIMEligibleGroupsNotCoveredFromRoleAssignments) already covered in RoleAssignments)"
+ $aadGroupsCount = ($optimizedTableForAADGroupsQuery).Count
+ Write-Host " $aadGroupsCount Groups from RoleAssignments and PIM Eligibility"
+ }
+
+ if ($aadGroupsCount -gt 0) {
+
+ switch ($aadGroupsCount) {
+ { $_ -gt 0 } { $indicator = 1 }
+ { $_ -gt 10 } { $indicator = 5 }
+ { $_ -gt 50 } { $indicator = 10 }
+ { $_ -gt 100 } { $indicator = 20 }
+ { $_ -gt 250 } { $indicator = 25 }
+ { $_ -gt 500 } { $indicator = 50 }
+ { $_ -gt 1000 } { $indicator = 100 }
+ { $_ -gt 10000 } { $indicator = 250 }
+ }
+
+ Write-Host " processing $($aadGroupsCount) AAD Groups (indicating progress in steps of $indicator)"
+
+ $optimizedTableForAADGroupsQuery | ForEach-Object -Parallel {
+ $aadGroupIdWithRoleAssignment = $_
+ #region UsingVARs
+ #fromOtherFunctions
+ $AADGroupMembersLimit = $using:AADGroupMembersLimit
+ $azAPICallConf = $using:azAPICallConf
+ $scriptPath = $using:ScriptPath
+ #Array&HTs
+ $htAADGroupsDetails = $using:htAADGroupsDetails
+ $arrayGroupRoleAssignmentsOnServicePrincipals = $using:arrayGroupRoleAssignmentsOnServicePrincipals
+ $arrayGroupRequestResourceNotFound = $using:arrayGroupRequestResourceNotFound
+ $arrayProgressedAADGroups = $using:arrayProgressedAADGroups
+ $htAADGroupsExeedingMemberLimit = $using:htAADGroupsExeedingMemberLimit
+ $indicator = $using:indicator
+ $htUserTypesGuest = $using:htUserTypesGuest
+ $htServicePrincipals = $using:htServicePrincipals
+ #other
+ $function:getGroupmembers = $using:funcGetGroupmembers
+ #endregion UsingVARs
+
+ $rndom = Get-Random -Minimum 10 -Maximum 750
+ Start-Sleep -Millisecond $rndom
+
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/groups/$($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId)/transitiveMembers/`$count"
+ $method = 'GET'
+ $aadGroupMembersCount = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask "getGroupMembersCountTransitive $($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId)" -listenOn 'Content' -consistencyLevel 'eventual'
+
+ if ($aadGroupMembersCount -eq 'Request_ResourceNotFound') {
+ $null = $script:arrayGroupRequestResourceNotFound.Add([PSCustomObject]@{
+ groupId = $aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId
+ })
+ }
+ else {
+ if ($aadGroupMembersCount -gt $AADGroupMembersLimit) {
+ Write-Host " Group exceeding limit ($($AADGroupMembersLimit)); memberCount: $aadGroupMembersCount; Group: $($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityDisplayname) ($($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId)); Members will not be resolved adjust the limit using parameter -AADGroupMembersLimit"
+ $script:htAADGroupsDetails.($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId) = @{}
+ $script:htAADGroupsDetails.($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId).MembersAllCount = $aadGroupMembersCount
+ $script:htAADGroupsDetails.($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId).MembersUsersCount = 'n/a'
+ $script:htAADGroupsDetails.($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId).MembersGroupsCount = 'n/a'
+ $script:htAADGroupsDetails.($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId).MembersServicePrincipalsCount = 'n/a'
+ }
+ else {
+ getGroupmembers -aadGroupId $aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId -aadGroupDisplayName $aadGroupIdWithRoleAssignment.RoleAssignmentIdentityDisplayname
+ }
+ }
+
+ $null = $script:arrayProgressedAADGroups.Add($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId)
+ $processedAADGroupsCount = $null
+ $processedAADGroupsCount = ($arrayProgressedAADGroups).Count
+ if ($processedAADGroupsCount) {
+ if ($processedAADGroupsCount % $indicator -eq 0) {
+ Write-Host " $processedAADGroupsCount AAD Groups processed"
+ }
+ }
+ } -ThrottleLimit ($ThrottleLimit * 2)
+ }
+ else {
+ Write-Host " processing $($aadGroupsCount) AAD Groups"
+ }
+
+ $arrayGroupRequestResourceNotFoundCount = ($arrayGroupRequestResourceNotFound).Count
+ if ($arrayGroupRequestResourceNotFoundCount -gt 0) {
+ Write-Host "$arrayGroupRequestResourceNotFoundCount Groups could not be checked for Memberships"
+ }
+
+ Write-Host " processed $($arrayProgressedAADGroups.Count) AAD Groups"
+ $endAADGroupsResolveMembers = Get-Date
+ Write-Host "Resolving AAD Groups duration: $((New-TimeSpan -Start $startAADGroupsResolveMembers -End $endAADGroupsResolveMembers).TotalMinutes) minutes ($((New-TimeSpan -Start $startAADGroupsResolveMembers -End $endAADGroupsResolveMembers).TotalSeconds) seconds)"
+ Write-Host " Users known as Guest count: $($htUserTypesGuest.Keys.Count) (after Resolving AAD Groups)"
+}
+function processALZPolicyVersionChecker {
+ $start = Get-Date
+ Write-Host "Processing 'Azure Landing Zones (ALZ) Policy Version Checker' base data"
+ $ALZRepositoryURI = 'https://github.com/Azure/Enterprise-Scale.git'
+ $workingPath = Get-Location
+ Write-Host " Working directory is '$($workingPath)'"
+ $ALZFolderName = "ALZ_$(Get-Date -Format $FileTimeStampFormat)"
+ $ALZPath = "$($OutputPath)/$($ALZFolderName)"
+
+ if (-not (Test-Path -LiteralPath "$($ALZPath)")) {
+ Write-Host " Creating temporary directory '$($ALZPath)'"
+ $null = mkdir $ALZPath
+ }
+ else {
+ Write-Host " Unexpected: The path '$($ALZPath)' already exists"
+ throw
+ }
+
+ Write-Host " Switching to temporary directory '$($ALZPath)'"
+ Set-Location $ALZPath
+ $ALZCloneSuccess = $false
+
+ try {
+ Write-Host " Try cloning '$($ALZRepositoryURI)'"
+ git clone $ALZRepositoryURI
+ if (-not (Test-Path -LiteralPath "$($ALZPath)/Enterprise-Scale" -PathType Container)) {
+ $ALZCloneSuccess = $false
+ Write-Host " Cloning '$($ALZRepositoryURI)' failed"
+ Write-Host " Setting switch parameter '-NoALZPolicyVersionChecker' to true"
+ $script:NoALZPolicyVersionChecker = $true
+ $script:azAPICallConf['htParameters'].NoALZPolicyVersionChecker = $true
+ Write-Host " Switching back to working directory '$($workingPath)'"
+ Set-Location $workingPath
+ }
+ else {
+ Write-Host " Cloning '$($ALZRepositoryURI)' succeeded"
+ $ALZCloneSuccess = $true
+ }
+ }
+ catch {
+ $_
+ Write-Host " Cloning '$($ALZRepositoryURI)' failed"
+ Write-Host " Setting switch parameter '-NoALZPolicyVersionChecker' to true"
+ $script:NoALZPolicyVersionChecker = $true
+ $script:azAPICallConf['htParameters'].NoALZPolicyVersionChecker = $true
+ Write-Host " Switching back to working directory '$($workingPath)'"
+ Set-Location $workingPath
+ }
+
+ if ($ALZCloneSuccess) {
+ Write-Host " Switching to directory '$($ALZPath)/Enterprise-Scale'"
+ Set-Location "$($ALZPath)/Enterprise-Scale"
+
+ $allESLZPolicies = @{}
+ $allESLZPolicySets = @{}
+ $allESLZPolicyHashes = @{}
+ $allESLZPolicySetHashes = @{}
+
+ #Write-Host " Processing ALZ Data Policy definitions"
+ $gitHist = (git log --format="%ai`t%H`t%an`t%ae`t%s" -- ./eslzArm/managementGroupTemplates/policyDefinitions/dataPolicies.json) | ConvertFrom-Csv -Delimiter "`t" -Header ('Date', 'CommitId', 'Author', 'Email', 'Subject')
+ $commitCount = 0
+ $processDataPolicies = $true
+ foreach ($commit in $gitHist | Sort-Object -Property Date) {
+ if ($processDataPolicies) {
+ if ($commit.CommitId -eq '3476914f9ba9a8f3f641a25497dfb24a4efa1017') {
+ $processDataPolicies = $false
+ continue
+ }
+ #Write-Host "processing commit (dataPolicies) $($commit.CommitId)"
+ $commitCount++
+ $jsonRaw = git show "$($commit.CommitId):eslzArm/managementGroupTemplates/policyDefinitions/dataPolicies.json"
+
+ $jsonESLZPolicies = $jsonRaw | ConvertFrom-Json
+ if (($jsonESLZPolicies.variables.policies.policyDefinitions).Count -eq 0) {
+ }
+ else {
+ $eslzPolicies = $jsonESLZPolicies.variables.policies.policyDefinitions
+ foreach ($policyDefinition in $eslzPolicies) {
+ $policyJsonConv = ($policyDefinition | ConvertTo-Json -Depth 99) -replace '\[\[', '['
+ $policyJsonRebuild = $policyJsonConv | ConvertFrom-Json
+ $policyJsonRule = $policyJsonRebuild.properties.policyRule | ConvertTo-Json -Depth 99
+ $hash = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonRule))
+ $stringHash = [System.BitConverter]::ToString($hash)
+
+ if (-not $allESLZPolicies.($policyJsonRebuild.name)) {
+ $allESLZPolicies.($policyJsonRebuild.name) = @{}
+ $allESLZPolicies.($policyJsonRebuild.name).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicies.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicies.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ $allESLZPolicies.($policyJsonRebuild.name).name = $policyJsonRebuild.name
+ $allESLZPolicies.($policyJsonRebuild.name).metadataSource = ''
+
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ else {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'obsolete'
+
+ if ($allESLZPolicies.($policyJsonRebuild.name).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicies.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicies.($policyJsonRebuild.name).$stringHash) {
+ $allESLZPolicies.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ }
+ }
+
+ #hsh
+ if (-not $allESLZPolicyHashes.($stringHash)) {
+ $allESLZPolicyHashes.($stringHash) = @{}
+ $allESLZPolicyHashes.($stringHash).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicyHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicyHashes.($stringHash).name = $policyJsonRebuild.name
+ $allESLZPolicyHashes.($stringHash).metadataSource = ''
+
+ $allESLZPolicyHashes.($stringHash).status = 'obsolete'
+ }
+ else {
+ $allESLZPolicyHashes.($stringHash).status = 'obsolete'
+ if ($allESLZPolicyHashes.($stringHash).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicyHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicyHashes.($stringHash).($policyJsonRebuild.name)) {
+ $allESLZPolicyHashes.($stringHash).($policyJsonRebuild.name) = $policyJsonRebuild.name
+ }
+ }
+ }
+ }
+ }
+ }
+
+ #Write-Host " Processing ALZ Policy and Set definitions"
+ $gitHist = (git log --format="%ai`t%H`t%an`t%ae`t%s" -- ./eslzArm/managementGroupTemplates/policyDefinitions/policies.json) | ConvertFrom-Csv -Delimiter "`t" -Header ('Date', 'CommitId', 'Author', 'Email', 'Subject')
+ $commitCount = 0
+ $doNewALZPolicyReadingApproach = $false
+ foreach ($commit in $gitHist | Sort-Object -Property Date) {
+
+ if ($commit.CommitId -eq '3476914f9ba9a8f3f641a25497dfb24a4efa1017') {
+ $doNewALZPolicyReadingApproach = $true
+ }
+ #Write-Host "processing commit $($commit.CommitId) - doNewALZPolicyReadingApproach: $doNewALZPolicyReadingApproach"
+ $commitCount++
+
+ $jsonRaw = git show "$($commit.CommitId):eslzArm/managementGroupTemplates/policyDefinitions/policies.json"
+
+ if ($doNewALZPolicyReadingApproach) {
+ $jsonESLZPolicies = $jsonRaw -replace '\[\[', '[' | ConvertFrom-Json
+ [regex]$extractVariableName = "(?<=\[variables\(')[^']+"
+ $refsPolicyDefinitionsAll = $extractVariableName.Matches($jsonESLZPolicies.variables.loadPolicyDefinitions.All).Value
+ $refsPolicyDefinitionsAzureCloud = $extractVariableName.Matches($jsonESLZPolicies.variables.loadPolicyDefinitions.AzureCloud).Value
+ $refsPolicyDefinitionsAzureChinaCloud = $extractVariableName.Matches($jsonESLZPolicies.variables.loadPolicyDefinitions.AzureChinaCloud).Value
+ $refsPolicyDefinitionsAzureUSGovernment = $extractVariableName.Matches($jsonESLZPolicies.variables.loadPolicyDefinitions.AzureUSGovernment).Value
+ $refsPolicySetDefinitionsAll = $extractVariableName.Matches($jsonESLZPolicies.variables.loadPolicySetDefinitions.All).Value
+ $refsPolicySetDefinitionsAzureCloud = $extractVariableName.Matches($jsonESLZPolicies.variables.loadPolicySetDefinitions.AzureCloud).Value
+ $refsPolicySetDefinitionsAzureChinaCloud = $extractVariableName.Matches($jsonESLZPolicies.variables.loadPolicySetDefinitions.AzureChinaCloud).Value
+ $refsPolicySetDefinitionsAzureUSGovernment = $extractVariableName.Matches($jsonESLZPolicies.variables.loadPolicySetDefinitions.AzureUSGovernment).Value
+ $listPolicyDefinitionsAzureCloud = $refsPolicyDefinitionsAll + $refsPolicyDefinitionsAzureCloud
+ $listPolicyDefinitionsAzureChinaCloud = $refsPolicyDefinitionsAll + $refsPolicyDefinitionsAzureChinaCloud
+ $listPolicyDefinitionsAzureUSGovernment = $refsPolicyDefinitionsAll + $refsPolicyDefinitionsAzureUSGovernment
+ $listPolicySetDefinitionsAzureCloud = $refsPolicySetDefinitionsAll + $refsPolicySetDefinitionsAzureCloud
+ $listPolicySetDefinitionsAzureChinaCloud = $refsPolicySetDefinitionsAll + $refsPolicySetDefinitionsAzureChinaCloud
+ $listPolicySetDefinitionsAzureUSGovernment = $refsPolicySetDefinitionsAll + $refsPolicySetDefinitionsAzureUSGovernment
+ $policyDefinitionsAzureCloud = $listPolicyDefinitionsAzureCloud.ForEach({ $jsonESLZPolicies.variables.$_ })
+ $policyDefinitionsAzureChinaCloud = $listPolicyDefinitionsAzureChinaCloud.ForEach({ $jsonESLZPolicies.variables.$_ })
+ $policyDefinitionsAzureUSGovernment = $listPolicyDefinitionsAzureUSGovernment.ForEach({ $jsonESLZPolicies.variables.$_ })
+ $policySetDefinitionsAzureCloud = $listPolicySetDefinitionsAzureCloud.ForEach({ $jsonESLZPolicies.variables.$_ })
+ $policySetDefinitionsAzureChinaCloud = $listPolicySetDefinitionsAzureChinaCloud.ForEach({ $jsonESLZPolicies.variables.$_ })
+ $policySetDefinitionsAzureUSGovernment = $listPolicySetDefinitionsAzureUSGovernment.ForEach({ $jsonESLZPolicies.variables.$_ })
+
+ switch ($azAPICallConf['checkContext'].Environment.Name) {
+ 'Azurecloud' {
+ $policyDefinitionsData = $policyDefinitionsAzureCloud
+ $policySetDefinitionsData = $policySetDefinitionsAzureCloud
+ }
+ 'AzureChinaCloud' {
+ $policyDefinitionsData = $policyDefinitionsAzureChinaCloud
+ $policySetDefinitionsData = $policySetDefinitionsAzureChinaCloud
+ }
+ 'AzureUSGovernment' {
+ $policyDefinitionsData = $policyDefinitionsAzureUSGovernment
+ $policySetDefinitionsData = $policySetDefinitionsAzureUSGovernment
+ }
+ }
+
+ foreach ($policyDefinition in $policyDefinitionsData) {
+
+ $policyJsonRebuild = $policyDefinition | ConvertFrom-Json
+ $policyJsonRule = $policyJsonRebuild.properties.policyRule | ConvertTo-Json -Depth 99
+ $hash = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonRule))
+ $stringHash = [System.BitConverter]::ToString($hash)
+
+ if (-not $allESLZPolicies.($policyJsonRebuild.name)) {
+ $allESLZPolicies.($policyJsonRebuild.name) = @{}
+ $allESLZPolicies.($policyJsonRebuild.name).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicies.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicies.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ $allESLZPolicies.($policyJsonRebuild.name).name = $policyJsonRebuild.name
+ $allESLZPolicies.($policyJsonRebuild.name).metadataSource = $policyJsonRebuild.properties.metadata.source
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'prod'
+ }
+ else {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ }
+ else {
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'prod'
+ }
+ else {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ $allESLZPolicies.($policyJsonRebuild.name).metadataSource = $policyJsonRebuild.properties.metadata.source
+ if ($allESLZPolicies.($policyJsonRebuild.name).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicies.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicies.($policyJsonRebuild.name).$stringHash) {
+ $allESLZPolicies.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ }
+ }
+
+ #hsh
+ if (-not $allESLZPolicyHashes.($stringHash)) {
+ $allESLZPolicyHashes.($stringHash) = @{}
+ $allESLZPolicyHashes.($stringHash).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicyHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicyHashes.($stringHash).name = $policyJsonRebuild.name
+ $allESLZPolicyHashes.($stringHash).metadataSource = $policyJsonRebuild.properties.metadata.source
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicyHashes.($stringHash).status = 'prod'
+ }
+ else {
+ $allESLZPolicyHashes.($stringHash).status = 'obsolete'
+ }
+ }
+ else {
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicyHashes.($stringHash).status = 'prod'
+ }
+ else {
+ $allESLZPolicyHashes.($stringHash).status = 'obsolete'
+ }
+ $allESLZPolicyHashes.($stringHash).metadataSource = $policyJsonRebuild.properties.metadata.source
+ if ($allESLZPolicyHashes.($stringHash).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicyHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicyHashes.($stringHash).($policyJsonRebuild.name)) {
+ $allESLZPolicyHashes.($stringHash).($policyJsonRebuild.name) = $policyJsonRebuild.name
+ }
+ }
+ }
+
+ foreach ($policySetDefinition in $policySetDefinitionsData) {
+
+ $policyJsonRebuild = $policySetDefinition | ConvertFrom-Json
+ $policyJsonParameters = $policyJsonRebuild.properties.parameters | ConvertTo-Json -Depth 99
+ $policyJsonPolicyDefinitions = $policyJsonRebuild.properties.policyDefinitions | ConvertTo-Json -Depth 99
+ $hashParameters = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonParameters))
+ $stringHashParameters = [System.BitConverter]::ToString($hashParameters)
+ $hashPolicyDefinitions = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonPolicyDefinitions))
+ $stringHashPolicyDefinitions = [System.BitConverter]::ToString($hashPolicyDefinitions)
+ $stringHash = "$($stringHashParameters)_$($stringHashPolicyDefinitions)"
+
+ if (-not $allESLZPolicySets.($policyJsonRebuild.name)) {
+ $allESLZPolicySets.($policyJsonRebuild.name) = @{}
+ $allESLZPolicySets.($policyJsonRebuild.name).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicySets.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicySets.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ $allESLZPolicySets.($policyJsonRebuild.name).name = $policyJsonRebuild.name
+ $allESLZPolicySets.($policyJsonRebuild.name).metadataSource = $policyJsonRebuild.properties.metadata.source
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicySets.($policyJsonRebuild.name).status = 'prod'
+ }
+ else {
+ $allESLZPolicySets.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ }
+ else {
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicySets.($policyJsonRebuild.name).status = 'prod'
+ }
+ else {
+ $allESLZPolicySets.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ $allESLZPolicySets.($policyJsonRebuild.name).metadataSource = $policyJsonRebuild.properties.metadata.source
+ if ($allESLZPolicySets.($policyJsonRebuild.name).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicySets.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicySets.($policyJsonRebuild.name).$stringHash) {
+ $allESLZPolicySets.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ }
+ }
+
+ #hsh
+ if (-not $allESLZPolicySetHashes.($stringHash)) {
+ $allESLZPolicySetHashes.($stringHash) = @{}
+ $allESLZPolicySetHashes.($stringHash).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicySetHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicySetHashes.($stringHash).name = $policyJsonRebuild.name
+ $allESLZPolicySetHashes.($stringHash).metadataSource = $policyJsonRebuild.properties.metadata.source
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicySetHashes.($stringHash).status = 'prod'
+ }
+ else {
+ $allESLZPolicySetHashes.($stringHash).status = 'obsolete'
+ }
+ }
+ else {
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicySetHashes.($stringHash).status = 'prod'
+ }
+ else {
+ $allESLZPolicySetHashes.($stringHash).status = 'obsolete'
+ }
+ $allESLZPolicySetHashes.($stringHash).metadataSource = $policyJsonRebuild.properties.metadata.source
+ if ($allESLZPolicySetHashes.($stringHash).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicySetHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicySetHashes.($stringHash).($policyJsonRebuild.name)) {
+ $allESLZPolicySetHashes.($stringHash).($policyJsonRebuild.name) = $policyJsonRebuild.name
+ }
+ }
+ }
+ }
+ else {
+ $jsonESLZPolicies = $jsonRaw | ConvertFrom-Json
+ if (($jsonESLZPolicies.variables.policies.policyDefinitions).Count -eq 0) {
+ }
+ else {
+
+ $eslzPolicies = $jsonESLZPolicies.variables.policies.policyDefinitions
+ foreach ($policyDefinition in $eslzPolicies) {
+ $policyJsonConv = ($policyDefinition | ConvertTo-Json -Depth 99) -replace '\[\[', '['
+ $policyJsonRebuild = $policyJsonConv | ConvertFrom-Json
+ $policyJsonRule = $policyJsonRebuild.properties.policyRule | ConvertTo-Json -Depth 99
+ $hash = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonRule))
+ $stringHash = [System.BitConverter]::ToString($hash)
+
+ if (-not $allESLZPolicies.($policyJsonRebuild.name)) {
+ $allESLZPolicies.($policyJsonRebuild.name) = @{}
+ $allESLZPolicies.($policyJsonRebuild.name).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicies.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicies.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ $allESLZPolicies.($policyJsonRebuild.name).name = $policyJsonRebuild.name
+ $allESLZPolicies.($policyJsonRebuild.name).metadataSource = ''
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'prod'
+ }
+ else {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ }
+ else {
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'prod'
+ }
+ else {
+ $allESLZPolicies.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ if ($allESLZPolicies.($policyJsonRebuild.name).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicies.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicies.($policyJsonRebuild.name).$stringHash) {
+ $allESLZPolicies.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ }
+ }
+
+ #hsh
+ if (-not $allESLZPolicyHashes.($stringHash)) {
+ $allESLZPolicyHashes.($stringHash) = @{}
+ $allESLZPolicyHashes.($stringHash).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicyHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicyHashes.($stringHash).name = $policyJsonRebuild.name
+ $allESLZPolicyHashes.($stringHash).metadataSource = ''
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicyHashes.($stringHash).status = 'prod'
+ }
+ else {
+ $allESLZPolicyHashes.($stringHash).status = 'obsolete'
+ }
+ }
+ else {
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicyHashes.($stringHash).status = 'prod'
+ }
+ else {
+ $allESLZPolicyHashes.($stringHash).status = 'obsolete'
+ }
+ if ($allESLZPolicyHashes.($stringHash).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicyHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicyHashes.($stringHash).($policyJsonRebuild.name)) {
+ $allESLZPolicyHashes.($stringHash).($policyJsonRebuild.name) = $policyJsonRebuild.name
+ }
+ }
+ }
+
+ $eslzPolicySets = $jsonESLZPolicies.variables.initiatives.policySetDefinitions
+ foreach ($policySetDefinition in $eslzPolicySets) {
+
+ $policyJsonConv = ($policySetDefinition | ConvertTo-Json -Depth 99) -replace '\[\[', '['
+ $policyJsonRebuild = $policyJsonConv | ConvertFrom-Json
+ $policyJsonParameters = $policyJsonRebuild.properties.parameters | ConvertTo-Json -Depth 99
+ $policyJsonPolicyDefinitions = $policyJsonRebuild.properties.policyDefinitions | ConvertTo-Json -Depth 99
+ $hashParameters = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonParameters))
+ $stringHashParameters = [System.BitConverter]::ToString($hashParameters)
+ $hashPolicyDefinitions = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonPolicyDefinitions))
+ $stringHashPolicyDefinitions = [System.BitConverter]::ToString($hashPolicyDefinitions)
+ $stringHash = "$($stringHashParameters)_$($stringHashPolicyDefinitions)"
+
+ if (-not $allESLZPolicySets.($policyJsonRebuild.name)) {
+ $allESLZPolicySets.($policyJsonRebuild.name) = @{}
+ $allESLZPolicySets.($policyJsonRebuild.name).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicySets.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicySets.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ $allESLZPolicySets.($policyJsonRebuild.name).name = $policyJsonRebuild.name
+ $allESLZPolicySets.($policyJsonRebuild.name).metadataSource = ''
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicySets.($policyJsonRebuild.name).status = 'prod'
+ }
+ else {
+ $allESLZPolicySets.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ }
+ else {
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicySets.($policyJsonRebuild.name).status = 'prod'
+ }
+ else {
+ $allESLZPolicySets.($policyJsonRebuild.name).status = 'obsolete'
+ }
+ if ($allESLZPolicySets.($policyJsonRebuild.name).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicySets.($policyJsonRebuild.name).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicySets.($policyJsonRebuild.name).$stringHash) {
+ $allESLZPolicySets.($policyJsonRebuild.name).$stringHash = $policyJsonRebuild.properties.metadata.version
+ }
+ }
+
+ #hsh
+ if (-not $allESLZPolicySetHashes.($stringHash)) {
+ $allESLZPolicySetHashes.($stringHash) = @{}
+ $allESLZPolicySetHashes.($stringHash).version = [System.Collections.ArrayList]@()
+ $null = $allESLZPolicySetHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ $allESLZPolicySetHashes.($stringHash).name = $policyJsonRebuild.name
+ $allESLZPolicySetHashes.($stringHash).metadataSource = ''
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicySetHashes.($stringHash).status = 'prod'
+ }
+ else {
+ $allESLZPolicySetHashes.($stringHash).status = 'obsolete'
+ }
+ }
+ else {
+ if ($commitCount -eq $gitHist.Count) {
+ $allESLZPolicySetHashes.($stringHash).status = 'prod'
+ }
+ else {
+ $allESLZPolicySetHashes.($stringHash).status = 'obsolete'
+ }
+ if ($allESLZPolicySetHashes.($stringHash).version -notcontains $policyJsonRebuild.properties.metadata.version) {
+ $null = $allESLZPolicySetHashes.($stringHash).version.Add($policyJsonRebuild.properties.metadata.version)
+ }
+ if (-not $allESLZPolicySetHashes.($stringHash).($policyJsonRebuild.name)) {
+ $allESLZPolicySetHashes.($stringHash).($policyJsonRebuild.name) = $policyJsonRebuild.name
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ Write-Host " $($allESLZPolicies.Keys.Count) Azure Landing Zones (ALZ) Policy definitions ($($allESLZPolicies.Values.where({$_.status -eq 'Prod'}).Count) productive)"
+ Write-Host " $($allESLZPolicySets.Keys.Count) Azure Landing Zones (ALZ) PolicySet definitions ($($allESLZPolicySets.Values.where({$_.status -eq 'Prod'}).Count) productive)"
+
+ $arrayObsoleteALZPolicies = @(
+ 'Deny-PublicEndpoint-Aks',
+ 'Deny-PublicEndpoint-CosmosDB',
+ 'Deny-PublicEndpoint-KeyVault',
+ 'Deny-PublicEndpoint-MySQL',
+ 'Deny-PublicEndpoint-PostgreSql',
+ 'Deny-PublicEndpoint-Sql',
+ 'Deny-PublicEndpoint-Storage',
+ 'Deploy-ASC-Standard',
+ 'Deploy-Diagnostics-ActivityLog',
+ 'Deploy-Diagnostics-AKS',
+ 'Deploy-Diagnostics-Batch',
+ 'Deploy-Diagnostics-DataLakeStore',
+ 'Deploy-Diagnostics-EventHub',
+ 'Deploy-Diagnostics-KeyVault',
+ 'Deploy-Diagnostics-LogicAppsWF',
+ 'Deploy-Diagnostics-PublicIP',
+ 'Deploy-Diagnostics-RecoveryVault',
+ 'Deploy-Diagnostics-SearchServices',
+ 'Deploy-Diagnostics-ServiceBus',
+ 'Deploy-Diagnostics-SQLDBs',
+ 'Deploy-Diagnostics-StreamAnalytics',
+ 'Deploy-DNSZoneGroup-For-Blob-PrivateEndpoint',
+ 'Deploy-DNSZoneGroup-For-File-PrivateEndpoint',
+ 'Deploy-DNSZoneGroup-For-KeyVault-PrivateEndpoint',
+ 'Deploy-DNSZoneGroup-For-Queue-PrivateEndpoint',
+ 'Deploy-DNSZoneGroup-For-Sql-PrivateEndpoint',
+ 'Deploy-DNSZoneGroup-For-Table-PrivateEndpoint',
+ 'Deploy-HUB',
+ 'Deploy-LA-Config',
+ 'Deploy-Log-Analytics',
+ 'Deploy-vHUB',
+ 'Deploy-vNet',
+ 'Deploy-vWAN'
+ )
+ foreach ($obsoleteALZPolicy in $arrayObsoleteALZPolicies) {
+ if (-not $alzPolicies.($obsoleteALZPolicy)) {
+ $script:alzPolicies.($obsoleteALZPolicy) = @{}
+ $script:alzPolicies.($obsoleteALZPolicy).latestVersion = ''
+ $script:alzPolicies.($obsoleteALZPolicy).status = 'obsolete'
+ $script:alzPolicies.($obsoleteALZPolicy).policyName = $obsoleteALZPolicy
+ $script:alzPolicies.($obsoleteALZPolicy).metadataSource = ''
+ }
+ }
+
+ foreach ($entry in $allESLZPolicies.keys | Sort-Object) {
+ $thisOne = $allESLZPolicies.($entry)
+ $latestVersion = ([array]($thisOne.version | Sort-Object -Descending))[0]
+ $script:alzPolicies.($entry) = @{}
+ $script:alzPolicies.($entry).latestVersion = $latestVersion
+ $script:alzPolicies.($entry).status = $thisOne.status
+ $script:alzPolicies.($entry).policyName = $thisOne.name
+ $script:alzPolicies.($entry).metadataSource = $thisOne.name
+ }
+
+ foreach ($entry in $allESLZPolicyHashes.keys | Sort-Object) {
+ $thisOne = $allESLZPolicyHashes.($entry)
+ $latestVersion = ([array]($thisOne.version | Sort-Object -Descending))[0]
+ $script:alzPolicyHashes.($entry) = @{}
+ $script:alzPolicyHashes.($entry).latestVersion = $latestVersion
+ $script:alzPolicyHashes.($entry).status = $thisOne.status
+ $script:alzPolicyHashes.($entry).policyName = $thisOne.name
+ $script:alzPolicyHashes.($entry).metadataSource = $thisOne.metadataSource
+ }
+
+ $script:alzPolicySets.'Deploy-Diag-LogAnalytics' = @{}
+ $script:alzPolicySets.'Deploy-Diag-LogAnalytics'.latestVersion = '1.0.0'
+ $script:alzPolicySets.'Deploy-Diag-LogAnalytics'.status = 'obsolete'
+ $script:alzPolicySets.'Deploy-Diag-LogAnalytics'.policySetName = 'Deploy-Diag-LogAnalytics'
+ foreach ($entry in $allESLZPolicySets.keys | Sort-Object) {
+ $thisOne = $allESLZPolicySets.($entry)
+ $latestVersion = ([array]($thisOne.version | Sort-Object -Descending))[0]
+ $script:alzPolicySets.($entry) = @{}
+ $script:alzPolicySets.($entry).latestVersion = $latestVersion
+ $script:alzPolicySets.($entry).status = $thisOne.status
+ $script:alzPolicySets.($entry).policySetName = $thisOne.name
+ $script:alzPolicySets.($entry).metadataSource = $thisOne.metadataSource
+ }
+
+ foreach ($entry in $allESLZPolicySetHashes.keys | Sort-Object) {
+ $thisOne = $allESLZPolicySetHashes.($entry)
+ $latestVersion = ([array]($thisOne.version | Sort-Object -Descending))[0]
+ $script:alzPolicySetHashes.($entry) = @{}
+ $script:alzPolicySetHashes.($entry).latestVersion = $latestVersion
+ $script:alzPolicySetHashes.($entry).status = $thisOne.status
+ $script:alzPolicySetHashes.($entry).policySetName = $thisOne.name
+ $script:alzPolicySetHashes.($entry).metadataSource = $thisOne.metadataSource
+ }
+
+ Write-Host " Switching back to working directory '$($workingPath)'"
+ Set-Location $workingPath
+
+ Write-Host " Removing temporary directory '$($ALZPath)'"
+ Remove-Item -Recurse -Force $ALZPath
+ }
+
+ $end = Get-Date
+ Write-Host " Processing 'Azure Landing Zones (ALZ) Policy Version Checker' base data duration: $((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds"
+}
+function processApplications {
+ Write-Host 'Processing Service Principals - Applications'
+ $script:servicePrincipalsOfTypeApplication = $htServicePrincipals.Keys.where( { $htServicePrincipals.($_).servicePrincipalType -eq 'Application' -and $htServicePrincipals.($_).appOwnerOrganizationId -eq $azAPICallConf['checkContext'].Subscription.TenantId } )
+ if ($azAPICallConf['htParameters'].userType -eq 'Guest') {
+ #checking if Guest has enough permissions
+ $app4Test = $htServicePrincipals.($servicePrincipalsOfTypeApplication[0])
+ $currentTask = "getApp Test $($app4Test.appId)"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/v1.0/applications?`$filter=appId eq '$($app4Test.appId)'"
+ $method = 'GET'
+ $testGetApplication = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask
+ if ($testGetApplication -eq 'skipApplications') {
+ $skipApplications = $true
+ Write-Host ' Guest account does not have enough permissions, skipping Applications (Secrets & Certificates)'
+ }
+ }
+ if (-not $skipApplications) {
+ $startSPApp = Get-Date
+ $currentDateUTC = (Get-Date).ToUniversalTime()
+ $script:arrayApplicationRequestResourceNotFound = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
+ $servicePrincipalsOfTypeApplication | ForEach-Object -Parallel {
+
+ #region UsingVARs
+ $currentDateUTC = $using:currentDateUTC
+ #fromOtherFunctions
+ $azAPICallConf = $using:azAPICallConf
+ $scriptPath = $using:ScriptPath
+ #Array&HTs
+ $arrayApplicationRequestResourceNotFound = $using:arrayApplicationRequestResourceNotFound
+ $htAppDetails = $using:htAppDetails
+ $htServicePrincipals = $using:htServicePrincipals
+ #endregion UsingVARs
+
+ $sp = $htServicePrincipals.($_)
+
+ $currentTask = "getApp $($sp.appId)"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/v1.0/applications?`$filter=appId eq '$($sp.appId)'"
+ $method = 'GET'
+ $getApplication = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask
+
+ if ($getApplication -eq 'Request_ResourceNotFound') {
+ $null = $script:arrayApplicationRequestResourceNotFound.Add([PSCustomObject]@{
+ appId = $sp.appId
+ })
+ }
+ else {
+ if (($getApplication).Count -eq 0) {
+ Write-Host "$($sp.appId) no data returned / seems non existent?"
+ }
+ else {
+ $script:htAppDetails.($sp.id) = @{}
+ $script:htAppDetails.($sp.id).servicePrincipalType = $sp.servicePrincipalType
+ $script:htAppDetails.($sp.id).spGraphDetails = $sp
+ $script:htAppDetails.($sp.id).appGraphDetails = $getApplication
+
+ $appPasswordCredentialsCount = ($getApplication.passwordCredentials).count
+ if ($appPasswordCredentialsCount -gt 0) {
+ $script:htAppDetails.($sp.id).appPasswordCredentialsCount = $appPasswordCredentialsCount
+ $appPasswordCredentialsExpiredCount = 0
+ $appPasswordCredentialsGracePeriodExpiryCount = 0
+ $appPasswordCredentialsExpiryOKCount = 0
+ $appPasswordCredentialsExpiryOKMoreThan2YearsCount = 0
+ foreach ($appPasswordCredential in $getApplication.passwordCredentials) {
+ $passwordExpiryTotalDays = (New-TimeSpan -Start $currentDateUTC -End $appPasswordCredential.endDateTime).TotalDays
+ if ($passwordExpiryTotalDays -lt 0) {
+ $appPasswordCredentialsExpiredCount++
+ }
+ elseif ($passwordExpiryTotalDays -lt $AADServicePrincipalExpiryWarningDays) {
+ $appPasswordCredentialsGracePeriodExpiryCount++
+ }
+ else {
+ if ($passwordExpiryTotalDays -gt 730) {
+ $appPasswordCredentialsExpiryOKMoreThan2YearsCount++
+ }
+ else {
+ $appPasswordCredentialsExpiryOKCount++
+ }
+ }
+ }
+ $script:htAppDetails.($sp.id).appPasswordCredentialsExpiredCount = $appPasswordCredentialsExpiredCount
+ $script:htAppDetails.($sp.id).appPasswordCredentialsGracePeriodExpiryCount = $appPasswordCredentialsGracePeriodExpiryCount
+ $script:htAppDetails.($sp.id).appPasswordCredentialsExpiryOKCount = $appPasswordCredentialsExpiryOKCount
+ $script:htAppDetails.($sp.id).appPasswordCredentialsExpiryOKMoreThan2YearsCount = $appPasswordCredentialsExpiryOKMoreThan2YearsCount
+ }
+
+ $appKeyCredentialsCount = ($getApplication.keyCredentials).count
+ if ($appKeyCredentialsCount -gt 0) {
+ $script:htAppDetails.($sp.id).appKeyCredentialsCount = $appKeyCredentialsCount
+ $appKeyCredentialsExpiredCount = 0
+ $appKeyCredentialsGracePeriodExpiryCount = 0
+ $appKeyCredentialsExpiryOKCount = 0
+ $appKeyCredentialsExpiryOKMoreThan2YearsCount = 0
+ foreach ($appKeyCredential in $getApplication.keyCredentials) {
+ $keyCredentialExpiryTotalDays = (New-TimeSpan -Start $currentDateUTC -End $appKeyCredential.endDateTime).TotalDays
+ if ($keyCredentialExpiryTotalDays -lt 0) {
+ $appKeyCredentialsExpiredCount++
+ }
+ elseif ($keyCredentialExpiryTotalDays -lt $AADServicePrincipalExpiryWarningDays) {
+ $appKeyCredentialsGracePeriodExpiryCount++
+ }
+ else {
+ if ($keyCredentialExpiryTotalDays -gt 730) {
+ $appKeyCredentialsExpiryOKMoreThan2YearsCount++
+ }
+ else {
+ $appKeyCredentialsExpiryOKCount++
+ }
+ }
+ }
+ $script:htAppDetails.($sp.id).appKeyCredentialsExpiredCount = $appKeyCredentialsExpiredCount
+ $script:htAppDetails.($sp.id).appKeyCredentialsGracePeriodExpiryCount = $appKeyCredentialsGracePeriodExpiryCount
+ $script:htAppDetails.($sp.id).appKeyCredentialsExpiryOKCount = $appKeyCredentialsExpiryOKCount
+ $script:htAppDetails.($sp.id).appKeyCredentialsExpiryOKMoreThan2YearsCount = $appKeyCredentialsExpiryOKMoreThan2YearsCount
+ }
+ }
+ }
+
+ } -ThrottleLimit ($ThrottleLimit * 2)
+
+ $endSPApp = Get-Date
+ Write-Host "Processing Service Principals - Applications duration: $((New-TimeSpan -Start $startSPApp -End $endSPApp).TotalMinutes) minutes ($((New-TimeSpan -Start $startSPApp -End $endSPApp).TotalSeconds) seconds)"
+ }
+}
+function processDataCollection {
+ [CmdletBinding()]Param(
+ [string]$mgId
+ )
+
+ Write-Host ' CustomDataCollection ManagementGroups'
+ $startMgLoop = Get-Date
+
+ $allManagementGroupsFromEntitiesChildOfRequestedMg = $arrayEntitiesFromAPI.where( { $_.type -eq 'Microsoft.Management/managementGroups' -and ($_.Name -eq $mgId -or $_.properties.parentNameChain -contains $mgId) })
+ $allManagementGroupsFromEntitiesChildOfRequestedMgCount = ($allManagementGroupsFromEntitiesChildOfRequestedMg).Count
+ Write-Host " $allManagementGroupsFromEntitiesChildOfRequestedMgCount Management Groups: $(($allManagementGroupsFromEntitiesChildOfRequestedMg.Name | Sort-Object) -join ', ')"
+ $mgBatch = ($allManagementGroupsFromEntitiesChildOfRequestedMg | Group-Object -Property { ($_.properties.parentNameChain).Count }) | Sort-Object -Property Name
+ Write-Host " $(($mgBatch | Measure-Object).Count) batches of Management Groups to process:"
+
+ $btchCnt = 0
+ foreach ($btch in $mgBatch) {
+ $btchCnt++
+ $listOfMGs = @()
+ foreach ($btchMg in $btch.Group | Sort-Object -Property name) {
+ if ($btchMg.name -eq $btchMg.Properties.displayName) {
+ $listOfMGs += $btchMg.name
+ }
+ else {
+ $listOfMGs += "$($btchMg.name) ($($btchMg.Properties.displayName))"
+ }
+ }
+ Write-Host " Batch#$($btchCnt) - $($listOfMGs.Count) Management Groups: $($listOfMGs -join ', ')"
+ }
+
+ foreach ($batchLevel in $mgBatch) {
+ Write-Host " Processing Management Groups L$($batchLevel.Name) ($($batchLevel.Count) Management Groups)"
+
+ showMemoryUsage
+
+ $batchLevel.Group | ForEach-Object -Parallel {
+ $mgdetail = $_
+ #region UsingVARs
+ #Parameters MG&Sub related
+ $CsvDelimiter = $using:CsvDelimiter
+ $CsvDelimiterOpposite = $using:CsvDelimiterOpposite
+ $ManagementGroupId = $using:ManagementGroupId
+ #fromOtherFunctions
+ $azAPICallConf = $using:azAPICallConf
+ $scriptPath = $using:ScriptPath
+ #Array&HTs
+ $newTable = $using:newTable
+ $customDataCollectionDuration = $using:customDataCollectionDuration
+ $htSubscriptionTagList = $using:htSubscriptionTagList
+ $htResourceTypesUniqueResource = $using:htResourceTypesUniqueResource
+ $htAllTagList = $using:htAllTagList
+ $htSubscriptionTags = $using:htSubscriptionTags
+ $htCacheDefinitionsPolicy = $using:htCacheDefinitionsPolicy
+ $htCacheDefinitionsPolicySet = $using:htCacheDefinitionsPolicySet
+ $htCacheDefinitionsRole = $using:htCacheDefinitionsRole
+ $htCacheDefinitionsBlueprint = $using:htCacheDefinitionsBlueprint
+ $htRoleDefinitionIdsUsedInPolicy = $using:htRoleDefinitionIdsUsedInPolicy
+ $htCachePolicyComplianceMG = $using:htCachePolicyComplianceMG
+ $htCachePolicyComplianceResponseTooLargeMG = $using:htCachePolicyComplianceResponseTooLargeMG
+ $htCacheAssignmentsRole = $using:htCacheAssignmentsRole
+ $htCacheAssignmentsRBACOnResourceGroupsAndResources = $using:htCacheAssignmentsRBACOnResourceGroupsAndResources
+ $htCacheAssignmentsBlueprint = $using:htCacheAssignmentsBlueprint
+ $htCacheAssignmentsPolicy = $using:htCacheAssignmentsPolicy
+ $htPolicyAssignmentExemptions = $using:htPolicyAssignmentExemptions
+ $htManagementGroupsMgPath = $using:htManagementGroupsMgPath
+ $LimitPOLICYPolicyDefinitionsScopedManagementGroup = $using:LimitPOLICYPolicyDefinitionsScopedManagementGroup
+ $LimitPOLICYPolicySetDefinitionsScopedManagementGroup = $using:LimitPOLICYPolicySetDefinitionsScopedManagementGroup
+ $LimitPOLICYPolicyAssignmentsManagementGroup = $using:LimitPOLICYPolicyAssignmentsManagementGroup
+ $LimitPOLICYPolicySetAssignmentsManagementGroup = $using:LimitPOLICYPolicySetAssignmentsManagementGroup
+ $LimitRBACRoleAssignmentsManagementGroup = $using:LimitRBACRoleAssignmentsManagementGroup
+ $arrayEntitiesFromAPI = $using:arrayEntitiesFromAPI
+ $allManagementGroupsFromEntitiesChildOfRequestedMgCount = $using:allManagementGroupsFromEntitiesChildOfRequestedMgCount
+ $arrayDataCollectionProgressMg = $using:arrayDataCollectionProgressMg
+ $arrayDiagnosticSettingsMgSub = $using:arrayDiagnosticSettingsMgSub
+ $htMgAtScopePolicyAssignments = $using:htMgAtScopePolicyAssignments
+ $htMgAtScopePoliciesScoped = $using:htMgAtScopePoliciesScoped
+ $htMgAtScopeRoleAssignments = $using:htMgAtScopeRoleAssignments
+ $htMgASCSecureScore = $using:htMgASCSecureScore
+ $htRoleAssignmentsFromAPIInheritancePrevention = $using:htRoleAssignmentsFromAPIInheritancePrevention
+ $htNamingValidation = $using:htNamingValidation
+ $htPrincipals = $using:htPrincipals
+ $htServicePrincipals = $using:htServicePrincipals
+ $htUserTypesGuest = $using:htUserTypesGuest
+ $htRoleAssignmentsPIM = $using:htRoleAssignmentsPIM
+ $alzPolicies = $using:alzPolicies
+ $alzPolicySets = $using:alzPolicySets
+ $alzPolicyHashes = $using:alzPolicyHashes
+ $alzPolicySetHashes = $using:alzPolicySetHashes
+ $htDoARMRoleAssignmentScheduleInstances = $using:htDoARMRoleAssignmentScheduleInstances
+ $ValidPolicyEffects = $using:ValidPolicyEffects
+ #other
+ $function:addRowToTable = $using:funcAddRowToTable
+ $function:namingValidation = $using:funcNamingValidation
+ $function:resolveObjectIds = $using:funcResolveObjectIds
+ $function:testGuid = $using:funcTestGuid
+
+ $function:dataCollectionMGSecureScore = $using:funcDataCollectionMGSecureScore
+ $function:dataCollectionDiagnosticsMG = $using:funcDataCollectionDiagnosticsMG
+ $function:dataCollectionPolicyComplianceStates = $using:funcDataCollectionPolicyComplianceStates
+ $function:dataCollectionBluePrintDefinitionsMG = $using:funcDataCollectionBluePrintDefinitionsMG
+ $function:dataCollectionPolicyExemptions = $using:funcDataCollectionPolicyExemptions
+ $function:dataCollectionPolicyDefinitions = $using:funcDataCollectionPolicyDefinitions
+ $function:dataCollectionPolicySetDefinitions = $using:funcDataCollectionPolicySetDefinitions
+ $function:dataCollectionPolicyAssignmentsMG = $using:funcDataCollectionPolicyAssignmentsMG
+ $function:dataCollectionRoleDefinitions = $using:funcDataCollectionRoleDefinitions
+ $function:dataCollectionRoleAssignmentsMG = $using:funcDataCollectionRoleAssignmentsMG
+ $function:detectPolicyEffect = $using:funcDetectPolicyEffect
+
+ #endregion usingVARS
+ $builtInPolicyDefinitionsCount = $using:builtInPolicyDefinitionsCount
+
+ $addRowToTableDone = $false
+
+ $MgDetailThis = $htManagementGroupsMgPath.($mgdetail.Name)
+ $MgParentId = $MgDetailThis.Parent
+ $hierarchyLevel = $MgDetailThis.ParentNameChainCount
+
+ if ($MgParentId -eq '__TenantRoot__') {
+ $MgParentId = 'TenantRoot'
+ $MgParentName = $MgParentId
+ }
+ else {
+ $MgParentName = $htManagementGroupsMgPath.($MgParentId).DisplayName
+ }
+
+ $rndom = Get-Random -Minimum 10 -Maximum 750
+ Start-Sleep -Millisecond $rndom
+ $startMgLoopThis = Get-Date
+
+ if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) {
+
+ #namingValidation
+ if (-not [string]::IsNullOrEmpty($mgdetail.properties.displayName)) {
+ $namingValidationResult = NamingValidation -toCheck $mgdetail.properties.displayName
+ if ($namingValidationResult.Count -gt 0) {
+ $script:htNamingValidation.ManagementGroup.($mgdetail.Name) = @{}
+ $script:htNamingValidation.ManagementGroup.($mgdetail.Name).nameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.ManagementGroup.($mgdetail.Name).name = $mgdetail.properties.displayName
+ }
+ }
+
+ $targetMgOrSub = 'MG'
+ $baseParameters = @{
+ scopeId = $mgdetail.Name
+ scopeDisplayName = $mgdetail.properties.displayName
+ }
+
+ #ManagementGroupASCSecureScore
+ $mgAscSecureScoreResult = DataCollectionMGSecureScore -Id $mgdetail.Name
+
+ $addRowToTableParameters = @{
+ hierarchyLevel = $hierarchyLevel
+ mgParentId = $mgParentId
+ mgParentName = $mgParentName
+ mgAscSecureScoreResult = $mgAscSecureScoreResult
+ }
+
+ #mg diag
+ DataCollectionDiagnosticsMG @baseParameters
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ #MGPolicyCompliance
+ DataCollectionPolicyComplianceStates @baseParameters -TargetMgOrSub $targetMgOrSub
+ }
+
+ #MGBlueprintDefinitions
+ $functionReturn = DataCollectionBluePrintDefinitionsMG @baseParameters @addRowToTableParameters
+ if ($functionReturn.'addRowToTableDone') {
+ $addRowToTableDone = $true
+ }
+
+ #MGPolicyExemptions
+ DataCollectionPolicyExemptions @baseParameters -TargetMgOrSub $targetMgOrSub
+
+ #MGPolicyDefinitions
+ $functionReturn = DataCollectionPolicyDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub
+ $policyDefinitionsScopedCount = $functionReturn.'PolicyDefinitionsScopedCount'
+
+ #MGPolicySetDefinitions
+ $functionReturn = DataCollectionPolicySetDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub
+ $policySetDefinitionsScopedCount = $functionReturn.'PolicySetDefinitionsScopedCount'
+
+ if (-not $htMgAtScopePoliciesScoped.($mgdetail.Name)) {
+ $script:htMgAtScopePoliciesScoped.($mgdetail.Name) = @{}
+ $script:htMgAtScopePoliciesScoped.($mgdetail.Name).ScopedCount = $policyDefinitionsScopedCount + $policySetDefinitionsScopedCount
+ }
+
+ $scopedPolicyCounts = @{
+ policyDefinitionsScopedCount = $policyDefinitionsScopedCount
+ policySetDefinitionsScopedCount = $policySetDefinitionsScopedCount
+ }
+
+ #MgPolicyAssignments
+ $functionReturn = DataCollectionPolicyAssignmentsMG @baseParameters @addRowToTableParameters @scopedPolicyCounts
+ if ($functionReturn.'addRowToTableDone') {
+ $addRowToTableDone = $true
+ }
+
+ #MGRoleDefinitions
+ DataCollectionRoleDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub
+
+ #MGRoleAssignments
+ $functionReturn = DataCollectionRoleAssignmentsMG @baseParameters @addRowToTableParameters
+ if ($functionReturn.'addRowToTableDone') {
+ $addRowToTableDone = $true
+ }
+
+ if ($addRowToTableDone -ne $true) {
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $mgdetail.properties.displayName `
+ -mgId $mgdetail.Name `
+ -mgParentId $mgParentId `
+ -mgParentName $mgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult
+ }
+ }
+ else {
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $mgdetail.properties.displayName `
+ -mgId $mgdetail.Name `
+ -mgParentId $mgParentId `
+ -mgParentName $mgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult
+ }
+
+
+ $endMgLoopThis = Get-Date
+ $null = $script:customDataCollectionDuration.Add([PSCustomObject]@{
+ Type = 'Mg'
+ Id = $mgdetail.Name
+ DurationSec = (New-TimeSpan -Start $startMgLoopThis -End $endMgLoopThis).TotalSeconds
+ })
+
+ $null = $script:arrayDataCollectionProgressMg.Add($mgdetail.Name)
+ $progressCount = ($arrayDataCollectionProgressMg).Count
+ Write-Host " $($progressCount)/$($allManagementGroupsFromEntitiesChildOfRequestedMgCount) Management Groups processed"
+
+ } -ThrottleLimit $ThrottleLimit
+ }
+
+ $endMgLoop = Get-Date
+ Write-Host " CustomDataCollection ManagementGroups processing duration: $((New-TimeSpan -Start $startMgLoop -End $endMgLoop).TotalMinutes) minutes ($((New-TimeSpan -Start $startMgLoop -End $endMgLoop).TotalSeconds) seconds)"
+
+ apiCallTracking -stage 'CustomDataCollection ManagementGroups' -spacing ' '
+
+ #test
+ if ($builtInPolicyDefinitionsCount -ne ($($htCacheDefinitionsPolicy).Values.where({ $_.Type -eq 'BuiltIn' }).Count) -or $builtInPolicyDefinitionsCount -ne ((($htCacheDefinitionsPolicy).Values.where( { $_.Type -eq 'BuiltIn' } )).Count)) {
+ Write-Host "$builtInPolicyDefinitionsCount -ne $($($htCacheDefinitionsPolicy).Values.where({$_.Type -eq 'BuiltIn'}).Count) OR $builtInPolicyDefinitionsCount -ne $((($htCacheDefinitionsPolicy).Values.where( {$_.Type -eq 'BuiltIn'} )).Count)"
+ Write-Host 'Listing all PolicyDefinitions:'
+ foreach ($tmpPolicyDefinitionId in ($($htCacheDefinitionsPolicy).Keys | Sort-Object)) {
+ Write-Host $tmpPolicyDefinitionId
+ }
+ }
+
+
+ #region SUBSCRIPTION
+ Write-Host ' CustomDataCollection Subscriptions'
+
+ if ($outOfScopeSubscriptions.Count -gt 0) {
+ Write-Host " CustomDataCollection $($outOfScopeSubscriptions.Count) Subscriptions excluded" -ForegroundColor yellow
+ $outOfScopeSubscriptionsGroupedByOutOfScopeReason = $outOfScopeSubscriptions | Group-Object -Property outOfScopeReason
+ foreach ($exclusionreason in $outOfScopeSubscriptionsGroupedByOutOfScopeReason) {
+ Write-Host " $($exclusionreason.Count): $($exclusionreason.Name)"
+ }
+ }
+
+ Write-Host " CustomDataCollection Subscriptions will process $subsToProcessInCustomDataCollectionCount of $childrenSubscriptionsCount"
+
+ $startSubLoop = Get-Date
+ if ($subsToProcessInCustomDataCollectionCount -gt 0) {
+
+ $counterBatch = [PSCustomObject] @{ Value = 0 }
+ $batchSize = 100
+ if ($subsToProcessInCustomDataCollectionCount -gt 500) {
+ $batchSize = 200
+ }
+ Write-Host " Subscriptions Batch size: $batchSize"
+
+ $subscriptionsBatch = $subsToProcessInCustomDataCollection | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) }
+ $batchCnt = 0
+ foreach ($batch in $subscriptionsBatch) {
+ $startBatch = Get-Date
+ $batchCnt++
+ Write-Host " processing Batch #$batchCnt/$(($subscriptionsBatch | Measure-Object).Count) ($(($batch.Group | Measure-Object).Count) Subscriptions)"
+ showMemoryUsage
+
+ $batch.Group | ForEach-Object -Parallel {
+ $startSubLoopThis = Get-Date
+ $childMgSubDetail = $_
+ #region UsingVARs
+ #Parameters MG&Sub related
+ $CsvDelimiter = $using:CsvDelimiter
+ $CsvDelimiterOpposite = $using:CsvDelimiterOpposite
+ #Parameters Sub related
+ #fromOtherFunctions
+ $azAPICallConf = $using:azAPICallConf
+ $scriptPath = $using:ScriptPath
+ #Array&HTs
+ $newTable = $using:newTable
+ $storageAccounts = $using:storageAccounts
+ $resourcesAll = $using:resourcesAll
+ $resourcesIdsAll = $using:resourcesIdsAll
+ $resourceGroupsAll = $using:resourceGroupsAll
+ $customDataCollectionDuration = $using:customDataCollectionDuration
+ $htSubscriptionsMgPath = $using:htSubscriptionsMgPath
+ $htManagementGroupsMgPath = $using:htManagementGroupsMgPath
+ $htResourceProvidersAll = $using:htResourceProvidersAll
+ $arrayFeaturesAll = $using:arrayFeaturesAll
+ $htSubscriptionTagList = $using:htSubscriptionTagList
+ $htResourceTypesUniqueResource = $using:htResourceTypesUniqueResource
+ $htAllTagList = $using:htAllTagList
+ $htSubscriptionTags = $using:htSubscriptionTags
+ $htCacheDefinitionsPolicy = $using:htCacheDefinitionsPolicy
+ $htCacheDefinitionsPolicySet = $using:htCacheDefinitionsPolicySet
+ $htCacheDefinitionsRole = $using:htCacheDefinitionsRole
+ $htCacheDefinitionsBlueprint = $using:htCacheDefinitionsBlueprint
+ $htRoleDefinitionIdsUsedInPolicy = $using:htRoleDefinitionIdsUsedInPolicy
+ $htCachePolicyComplianceSUB = $using:htCachePolicyComplianceSUB
+ $htCachePolicyComplianceResponseTooLargeSUB = $using:htCachePolicyComplianceResponseTooLargeSUB
+ $htCacheAssignmentsRole = $using:htCacheAssignmentsRole
+ $htCacheAssignmentsRBACOnResourceGroupsAndResources = $using:htCacheAssignmentsRBACOnResourceGroupsAndResources
+ $htCacheAssignmentsBlueprint = $using:htCacheAssignmentsBlueprint
+ $htCacheAssignmentsPolicyOnResourceGroupsAndResources = $using:htCacheAssignmentsPolicyOnResourceGroupsAndResources
+ $htCacheAssignmentsPolicy = $using:htCacheAssignmentsPolicy
+ $htPolicyAssignmentExemptions = $using:htPolicyAssignmentExemptions
+ $htResourceLocks = $using:htResourceLocks
+ $LimitPOLICYPolicyDefinitionsScopedSubscription = $using:LimitPOLICYPolicyDefinitionsScopedSubscription
+ $LimitPOLICYPolicySetDefinitionsScopedSubscription = $using:LimitPOLICYPolicySetDefinitionsScopedSubscription
+ $LimitPOLICYPolicyAssignmentsSubscription = $using:LimitPOLICYPolicyAssignmentsSubscription
+ $LimitPOLICYPolicySetAssignmentsSubscription = $using:LimitPOLICYPolicySetAssignmentsSubscription
+ $childrenSubscriptionsCount = $using:childrenSubscriptionsCount
+ $subsToProcessInCustomDataCollectionCount = $using:subsToProcessInCustomDataCollectionCount
+ $arrayDataCollectionProgressSub = $using:arrayDataCollectionProgressSub
+ $arraySubResourcesAddArrayDuration = $using:arraySubResourcesAddArrayDuration
+ $htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI
+ $arrayEntitiesFromAPI = $using:arrayEntitiesFromAPI
+ $arrayDiagnosticSettingsMgSub = $using:arrayDiagnosticSettingsMgSub
+ $htMgASCSecureScore = $using:htMgASCSecureScore
+ $htRoleAssignmentsFromAPIInheritancePrevention = $using:htRoleAssignmentsFromAPIInheritancePrevention
+ $htNamingValidation = $using:htNamingValidation
+ $htPrincipals = $using:htPrincipals
+ $htServicePrincipals = $using:htServicePrincipals
+ $htUserTypesGuest = $using:htUserTypesGuest
+ $arrayDefenderPlans = $using:arrayDefenderPlans
+ $arrayDefenderPlansSubscriptionsSkipped = $using:arrayDefenderPlansSubscriptionsSkipped
+ $arrayUserAssignedIdentities4Resources = $using:arrayUserAssignedIdentities4Resources
+ $htSubscriptionsRoleAssignmentLimit = $using:htSubscriptionsRoleAssignmentLimit
+ $arrayPsRule = $using:arrayPsRule
+ $arrayPSRuleTracking = $using:arrayPSRuleTracking
+ $htClassicAdministrators = $using:htClassicAdministrators
+ $htRoleAssignmentsPIM = $using:htRoleAssignmentsPIM
+ $alzPolicies = $using:alzPolicies
+ $alzPolicySets = $using:alzPolicySets
+ $alzPolicyHashes = $using:alzPolicyHashes
+ $alzPolicySetHashes = $using:alzPolicySetHashes
+ $htDoARMRoleAssignmentScheduleInstances = $using:htDoARMRoleAssignmentScheduleInstances
+ $htDefenderEmailContacts = $using:htDefenderEmailContacts
+ $arrayVNets = $using:arrayVNets
+ $arrayPrivateEndPoints = $using:arrayPrivateEndPoints
+ $htResourceProvidersRef = $using:htResourceProvidersRef
+ $arrayPrivateEndPointsFromResourceProperties = $using:arrayPrivateEndPointsFromResourceProperties
+ $htResourcePropertiesConvertfromJSONFailed = $using:htResourcePropertiesConvertfromJSONFailed
+ $htAvailablePrivateEndpointTypes = $using:htAvailablePrivateEndpointTypes
+ $arrayAdvisorScores = $using:arrayAdvisorScores
+ $ValidPolicyEffects = $using:ValidPolicyEffects
+ #$htResourcesWithProperties = $using:htResourcesWithProperties
+ #other
+ $function:addRowToTable = $using:funcAddRowToTable
+ $function:namingValidation = $using:funcNamingValidation
+ $function:resolveObjectIds = $using:funcResolveObjectIds
+ $function:testGuid = $using:funcTestGuid
+ $function:dataCollectionMGSecureScore = $using:funcDataCollectionMGSecureScore
+ $function:dataCollectionDefenderPlans = $using:funcDataCollectionDefenderPlans
+ $function:dataCollectionDiagnosticsSub = $using:funcDataCollectionDiagnosticsSub
+ $function:dataCollectionResources = $using:funcDataCollectionResources
+ $function:dataCollectionStorageAccounts = $using:funcDataCollectionStorageAccounts
+ $function:dataCollectionResourceGroups = $using:funcDataCollectionResourceGroups
+ $function:dataCollectionResourceProviders = $using:funcDataCollectionResourceProviders
+ $function:dataCollectionFeatures = $using:funcDataCollectionFeatures
+ $function:dataCollectionResourceLocks = $using:funcDataCollectionResourceLocks
+ $function:dataCollectionTags = $using:funcDataCollectionTags
+ $function:dataCollectionPolicyComplianceStates = $using:funcDataCollectionPolicyComplianceStates
+ $function:dataCollectionASCSecureScoreSub = $using:funcDataCollectionASCSecureScoreSub
+ $function:dataCollectionBluePrintDefinitionsSub = $using:funcDataCollectionBluePrintDefinitionsSub
+ $function:dataCollectionBluePrintAssignmentsSub = $using:funcDataCollectionBluePrintAssignmentsSub
+ $function:dataCollectionPolicyExemptions = $using:funcDataCollectionPolicyExemptions
+ $function:dataCollectionPolicyDefinitions = $using:funcDataCollectionPolicyDefinitions
+ $function:dataCollectionPolicySetDefinitions = $using:funcDataCollectionPolicySetDefinitions
+ $function:dataCollectionPolicyAssignmentsSub = $using:funcDataCollectionPolicyAssignmentsSub
+ $function:dataCollectionRoleDefinitions = $using:funcDataCollectionRoleDefinitions
+ $function:dataCollectionRoleAssignmentsSub = $using:funcDataCollectionRoleAssignmentsSub
+ $function:dataCollectionClassicAdministratorsSub = $using:funcDataCollectionClassicAdministratorsSub
+ $function:dataCollectionDefenderEmailContacts = $using:funcDataCollectionDefenderEmailContacts
+ $function:dataCollectionVNets = $using:funcDataCollectionVNets
+ $function:dataCollectionPrivateEndpoints = $using:funcDataCollectionPrivateEndpoints
+ $function:dataCollectionAdvisorScores = $using:funcDataCollectionAdvisorScores
+ $function:detectPolicyEffect = $using:funcDetectPolicyEffect
+ #endregion UsingVARs
+
+ $addRowToTableDone = $false
+
+ $childMgSubId = $childMgSubDetail.subscriptionId
+ $childMgSubDisplayName = $childMgSubDetail.subscriptionName
+ $hierarchyInfo = $htSubscriptionsMgPath.($childMgSubDetail.subscriptionId)
+ $hierarchyLevel = $hierarchyInfo.level
+ $childMgId = $hierarchyInfo.Parent
+ $childMgDisplayName = $hierarchyInfo.ParentName
+ $childMgMgPath = $hierarchyInfo.pathDelimited
+ $childMgParentNameChain = $hierarchyInfo.ParentNameChain
+ $childMgParentNameChainDelimited = $hierarchyInfo.ParentNameChainDelimited
+ $childMgParentInfo = $htManagementGroupsMgPath.($childMgId)
+ $childMgParentId = $childMgParentInfo.Parent
+ $childMgParentName = $htManagementGroupsMgPath.($childMgParentInfo.Parent).DisplayName
+
+ #namingValidation
+ if (-not [string]::IsNullOrEmpty($childMgSubDisplayName)) {
+ $namingValidationResult = NamingValidation -toCheck $childMgSubDisplayName
+ if ($namingValidationResult.Count -gt 0) {
+
+ $script:htNamingValidation.Subscription.($childMgSubId) = @{}
+ $script:htNamingValidation.Subscription.($childMgSubId).displayNameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.Subscription.($childMgSubId).displayName = $childMgSubDisplayName
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) {
+ $currentSubscription = $htAllSubscriptionsFromAPI.($childMgSubId).subDetails
+ $subscriptionQuotaId = $currentSubscription.subscriptionPolicies.quotaId
+ $subscriptionState = $currentSubscription.state
+
+ $targetMgOrSub = 'Sub'
+ $baseParameters = @{
+ scopeId = $childMgSubId
+ scopeDisplayName = $childMgSubDisplayName
+ subscriptionQuotaId = $subscriptionQuotaId
+ }
+
+ if (-not $azAPICallConf['htParameters'].ManagementGroupsOnly) {
+ #mgSecureScore
+ $mgAscSecureScoreResult = DataCollectionMGSecureScore -Id $childMgId
+
+ #defenderPlans
+ $dataCollectionDefenderPlansParameters = @{
+ ChildMgMgPath = $childMgMgPath
+ }
+ DataCollectionDefenderPlans @baseParameters @dataCollectionDefenderPlansParameters
+
+ #defenderEmailContacts
+ DataCollectionDefenderEmailContacts @baseParameters
+
+ #advisorScores
+ $dataCollectionAdvisorScoresParameters = @{
+ ChildMgMgPath = $childMgMgPath
+ }
+ DataCollectionAdvisorScores @baseParameters @dataCollectionAdvisorScoresParameters
+
+ if (-not $azAPICallConf['htParameters'].NoNetwork) {
+ #VNets
+ DataCollectionVNets @baseParameters
+ #PE
+ DataCollectionPrivateEndpoints @baseParameters
+ }
+
+ #diagnostics
+ $dataCollectionDiagnosticsSubParameters = @{
+ ChildMgMgPath = $childMgMgPath
+ ChildMgId = $childMgId
+ }
+ DataCollectionDiagnosticsSub @baseParameters @dataCollectionDiagnosticsSubParameters
+
+ if ($azAPICallConf['htParameters'].NoStorageAccountAccessAnalysis -eq $false) {
+ #resources
+ $dataCollectionStorageAccountsParameters = @{
+ ChildMgMgPath = $childMgMgPath
+ ChildMgParentNameChainDelimited = $childMgParentNameChainDelimited
+ }
+ DataCollectionStorageAccounts @baseParameters @dataCollectionStorageAccountsParameters
+ }
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #resources
+ $dataCollectionResourcesParameters = @{
+ ChildMgMgPath = $childMgMgPath
+ ChildMgParentNameChainDelimited = $childMgParentNameChainDelimited
+ }
+ DataCollectionResources @baseParameters @dataCollectionResourcesParameters
+ }
+
+ #resourceGroups
+ DataCollectionResourceGroups @baseParameters
+
+ #resourceProviders
+ if ($azAPICallConf['htParameters'].NoResourceProvidersAtAll -eq $false) {
+ DataCollectionResourceProviders @baseParameters
+ }
+
+ #features
+ DataCollectionFeatures @baseParameters -MgParentNameChain $childMgParentNameChain
+
+ #resourceLocks
+ DataCollectionResourceLocks @baseParameters
+
+ #tags
+ $subscriptionTagsReturn = DataCollectionTags @baseParameters
+ $subscriptionTags = $subscriptionTagsReturn.subscriptionTags
+ $subscriptionTagsCount = $subscriptionTagsReturn.subscriptionTagsCount
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ #SubscriptionPolicyCompliance
+ DataCollectionPolicyComplianceStates @baseParameters -TargetMgOrSub $targetMgOrSub
+ }
+
+ #SubscriptionASCSecureScore
+ $subscriptionASCSecureScore = DataCollectionASCSecureScoreSub @baseParameters
+
+ $addRowToTableParameters = @{
+ hierarchyLevel = $hierarchyLevel
+ childMgDisplayName = $childMgDisplayName
+ childMgId = $childMgId
+ childMgParentId = $childMgParentId
+ childMgParentName = $childMgParentName
+ mgAscSecureScoreResult = $mgAscSecureScoreResult
+ #subscriptionQuotaId = $subscriptionQuotaId
+ subscriptionState = $subscriptionState
+ subscriptionASCSecureScore = $subscriptionASCSecureScore
+ subscriptionTags = $subscriptionTags
+ subscriptionTagsCount = $subscriptionTagsCount
+ }
+
+ #SubscriptionBlueprintDefinitions
+ $functionReturn = DataCollectionBluePrintDefinitionsSub @baseParameters @addRowToTableParameters
+ if ($functionReturn.'addRowToTableDone') {
+ $addRowToTableDone = $true
+ }
+
+ #SubscriptionBlueprintAssignments
+ $functionReturn = DataCollectionBluePrintAssignmentsSub @baseParameters @addRowToTableParameters
+ if ($functionReturn.'addRowToTableDone') {
+ $addRowToTableDone = $true
+ }
+
+ #SubscriptionPolicyExemptions
+ DataCollectionPolicyExemptions @baseParameters -TargetMgOrSub $targetMgOrSub
+
+ #SubscriptionPolicyDefinitions
+ $functionReturn = DataCollectionPolicyDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub
+ $policyDefinitionsScopedCount = $functionReturn.'PolicyDefinitionsScopedCount'
+
+ #SubscriptionPolicySets
+ $functionReturn = DataCollectionPolicySetDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub
+ $policySetDefinitionsScopedCount = $functionReturn.'PolicySetDefinitionsScopedCount'
+
+ $scopedPolicyCounts = @{
+ policyDefinitionsScopedCount = $policyDefinitionsScopedCount
+ policySetDefinitionsScopedCount = $policySetDefinitionsScopedCount
+ }
+
+ #SubscriptionPolicyAssignments
+ $functionReturn = DataCollectionPolicyAssignmentsSub @baseParameters @addRowToTableParameters @scopedPolicyCounts
+ if ($functionReturn.'addRowToTableDone') {
+ $addRowToTableDone = $true
+ }
+
+ #SubscriptionRoleDefinitions
+ DataCollectionRoleDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub
+
+ #SubscriptionRoleAssignments
+ $functionReturn = DataCollectionRoleAssignmentsSub @baseParameters @addRowToTableParameters
+ if ($functionReturn.'addRowToTableDone') {
+ $addRowToTableDone = $true
+ }
+
+ #SubscriptionClassicAdministrators
+ dataCollectionClassicAdministratorsSub @baseParameters -SubscriptionMgPath $childMgMgPath
+ }
+
+ if ($addRowToTableDone -ne $true) {
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $childMgDisplayName `
+ -mgId $childMgId `
+ -mgParentId $childMgParentId `
+ -mgParentName $childMgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Subscription $childMgSubDisplayName `
+ -SubscriptionId $childMgSubId `
+ -SubscriptionASCSecureScore $subscriptionASCSecureScore
+ }
+ }
+ else {
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $childMgDisplayName `
+ -mgId $childMgId `
+ -mgParentId $childMgParentId `
+ -mgParentName $childMgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Subscription $childMgSubDisplayName `
+ -SubscriptionId $childMgSubId `
+ -SubscriptionASCSecureScore $subscriptionASCSecureScore
+ }
+ $endSubLoopThis = Get-Date
+ $null = $script:customDataCollectionDuration.Add([PSCustomObject]@{
+ Type = 'SUB'
+ Id = $childMgSubId
+ DurationSec = (New-TimeSpan -Start $startSubLoopThis -End $endSubLoopThis).TotalSeconds
+ })
+
+ $null = $script:arrayDataCollectionProgressSub.Add($childMgSubId)
+ $progressCount = ($arrayDataCollectionProgressSub).Count
+ Write-Host " $($progressCount)/$($subsToProcessInCustomDataCollectionCount) Subscriptions processed"
+
+ } -ThrottleLimit $ThrottleLimit
+
+ $endBatch = Get-Date
+ Write-Host " Batch #$batchCnt processing duration: $((New-TimeSpan -Start $startBatch -End $endBatch).TotalMinutes) minutes ($((New-TimeSpan -Start $startBatch -End $endBatch).TotalSeconds) seconds)"
+ }
+
+ $endSubLoop = Get-Date
+ Write-Host " CustomDataCollection Subscriptions processing duration: $((New-TimeSpan -Start $startSubLoop -End $endSubLoop).TotalMinutes) minutes ($((New-TimeSpan -Start $startSubLoop -End $endSubLoop).TotalSeconds) seconds)"
+ if ($azAPICallConf['htParameters'].DoPSRule -eq $true) {
+ if ($arrayPSRuleTracking.Count -gt 0) {
+ $durationPSRuleTotalSeconds = (($arrayPSRuleTracking.duration | Measure-Object -Sum).Sum)
+ Write-Host " CustomDataCollection Subscriptions 'PSRule for Azure' processing duration (in sum): $($durationPSRuleTotalSeconds / 60) minutes ($($durationPSRuleTotalSeconds) seconds)"
+ }
+ }
+ #test
+ Write-Host " built-in PolicyDefinitions: $($($htCacheDefinitionsPolicy).Values.where({$_.Type -eq 'BuiltIn'}).Count)"
+ Write-Host " custom PolicyDefinitions: $($($htCacheDefinitionsPolicy).Values.where({$_.Type -eq 'Custom'}).Count)"
+ Write-Host " all PolicyDefinitions: $($($htCacheDefinitionsPolicy).Values.Count)"
+ }
+ #endregion SUBSCRIPTION
+
+ $durationDataMG = $customDataCollectionDuration.where( { $_.Type -eq 'MG' } )
+ $durationDataSUB = $customDataCollectionDuration.where( { $_.Type -eq 'SUB' } )
+ $durationMGAverageMaxMin = ($durationDataMG.DurationSec | Measure-Object -Average -Maximum -Minimum)
+ $durationSUBAverageMaxMin = ($durationDataSUB.DurationSec | Measure-Object -Average -Maximum -Minimum)
+ Write-Host "Collecting custom data for $($arrayEntitiesFromAPIManagementGroupsCount) ManagementGroups Avg/Max/Min duration in seconds: Average: $([math]::Round($durationMGAverageMaxMin.Average,4)); Maximum: $([math]::Round($durationMGAverageMaxMin.Maximum,4)); Minimum: $([math]::Round($durationMGAverageMaxMin.Minimum,4))"
+ Write-Host "Collecting custom data for $($arrayEntitiesFromAPISubscriptionsCount) Subscriptions Avg/Max/Min duration in seconds: Average: $([math]::Round($durationSUBAverageMaxMin.Average,4)); Maximum: $([math]::Round($durationSUBAverageMaxMin.Maximum,4)); Minimum: $([math]::Round($durationSUBAverageMaxMin.Minimum,4))"
+
+ apiCallTracking -stage 'CustomDataCollection ManagementGroups and Subscriptions' -spacing ' '
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+
+ $script:resourcesAllGroupedBySubcriptionId = $resourcesAll | Group-Object -Property subscriptionId
+
+ $totaldurationSubResourcesAddArray = ($arraySubResourcesAddArrayDuration.DurationSec | Measure-Object -Sum).Sum
+ Write-Host "Collecting custom data total duration writing the subResourcesArray: $totaldurationSubResourcesAddArray seconds"
+
+ if (-not $azAPICallConf['htParameters'].HierarchyMapOnly -and -not $azAPICallConf['htParameters'].ManagementGroupsOnly) {
+ if (-not $NoCsvExport) {
+
+ #fluctuation
+ Write-Host 'Process Resource fluctuation'
+ $start = Get-Date
+ if (Test-Path -Filter "*$($ManagementGroupId)_ResourcesAll.csv" -LiteralPath "$($outputPath)") {
+ $startImportPrevious = Get-Date
+ $doResourceFluctuation = $true
+
+ try {
+ $previous = Get-ChildItem -Path $outputPath -Filter "*$($ManagementGroupId)_ResourcesAll.csv" | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1 -ErrorAction Stop
+ $importPrevious = Import-Csv -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($previous.Name)" -Encoding utf8 -Delimiter $CsvDelimiter | Select-Object -ExpandProperty id
+ Write-Host " Import previous ($($previous.Name)) duration: $((New-TimeSpan -Start $startImportPrevious -End (Get-Date)).TotalSeconds) seconds"
+ }
+ catch {
+ Write-Host " FAILED: importing previous CSV '$($outputPath)$($DirectorySeparatorChar)$($previous.Name)' OR it does not exist (*$($ManagementGroupId)_ResourcesAll.csv)"
+ $doResourceFluctuation = $false
+ }
+
+ if ($doResourceFluctuation) {
+ #$importPrevious.Count
+
+ #https://gist.github.com/fatherjack/4c91cc6832b8b02d1b7319716a5fba52
+ function Compare-StringSet {
+ <#
+ .SYNOPSIS
+ Compare two sets of strings and see the matched and unmatched elements from each input
+
+ .DESCRIPTION
+ Compares sets of
+
+ .PARAMETER Ref
+ The reference set of values to be compared
+
+ .PARAMETER Diff
+ The difference set of values to be compared
+
+ .PARAMETER CaseSensitive
+ Enables a case-sensitive comparison
+
+ .EXAMPLE
+ $ref, $dif = @(
+ , @('a', 'b', 'c')
+ , @('b', 'c', 'd')
+ )
+ $Sets = Compare-StringSet $ref $dif
+ $Sets.RefOnly
+
+ $Sets.DiffOnly
+
+ $Sets.Both
+
+ This example sets up two arrays with some similar values and then passes them both to the Compare-StringSet function. the results of this are stored in the variable $Sets.
+ $Sets is an object that has three properties - RefOnly, DiffOnly, and Both. These are sets of the incoming values where they intersect or not.
+
+ .EXAMPLE
+ $ref, $dif = @(
+ , @('tree', 'house', 'football')
+ , @('dog', 'cat', 'tree', 'house', 'Football')
+ )
+ $Sets = Compare-StringSet $ref $dif -CaseSensitive
+ $Sets.RefOnly
+ $Sets.DiffOnly
+ $Sets.Both
+
+ This example sets up two arrays with some similar values and then passes them both to the Compare-StringSet function using the -CaseSensitive switch. The results of this are stored in the variable $Sets.
+ $Sets is an object that has three properties - RefOnly, DiffOnly, and Both.
+
+ Because of the -CaseSensitive switch usage 'football' is shown as in RefOnly and 'Football' is shown as in DiffOnly.
+
+ .NOTES
+ From https://gist.github.com/IISResetMe/57ce7b76e1001974a4f7170e10775875
+ #>
+
+ param(
+ [string[]]$Ref,
+ [string[]]$Diff,
+
+ [switch]$CaseSensitive
+ )
+
+ $Comparer = if ($CaseSensitive) {
+ [System.StringComparer]::InvariantCulture
+ }
+ else {
+ [System.StringComparer]::InvariantCultureIgnoreCase
+ }
+
+ $Results = [ordered]@{
+ RefOnly = @()
+ Both = @()
+ DiffOnly = @()
+ }
+
+ $temp = [System.Collections.Generic.HashSet[string]]::new($Ref, $Comparer)
+ $temp.IntersectWith($Diff)
+ $Results['Both'] = $temp
+
+ #$temp = [System.Collections.Generic.HashSet[string]]::new($Ref, [System.StringComparer]::CurrentCultureIgnoreCase)
+ $temp = [System.Collections.Generic.HashSet[string]]::new($Ref, $Comparer)
+ $temp.ExceptWith($Diff)
+ $Results['RefOnly'] = $temp
+
+ #$temp = [System.Collections.Generic.HashSet[string]]::new($Diff, [System.StringComparer]::CurrentCultureIgnoreCase)
+ $temp = [System.Collections.Generic.HashSet[string]]::new($Diff, $Comparer)
+ $temp.ExceptWith($Ref)
+ $Results['DiffOnly'] = $temp
+
+ return [pscustomobject]$Results
+ }
+
+ Write-Host " Comparing previous ($($importPrevious.Count)) with latest ($($resourcesIdsAll.Count))"
+ $start = Get-Date
+ $x = Compare-StringSet $importPrevious $resourcesIdsAll.id
+ Write-Host ' unique values in previous (deleted):' $x.RefOnly.Count
+ Write-Host " values that are contained in previous and latest: $($x.Both.Count)"
+ Write-Host ' unique values in latest (added):' $x.DiffOnly.Count
+ $end = Get-Date
+ Write-Host " Compare previous with latest duration: $((New-TimeSpan -Start $start -End $end).TotalMinutes) mins ($((New-TimeSpan -Start $start -End $end).TotalSeconds) sec)"
+
+ $script:arrayResourceFluctuationFinal = [System.Collections.ArrayList]@()
+
+ #ADDED
+ $arrayAdded = [System.Collections.ArrayList]@()
+ $arrayAddedAndRemoved = [System.Collections.ArrayList]@()
+ foreach ($resource in $x.DiffOnly) {
+ $resourceSplitted = $resource.split('/')
+ #$resourceSplitted
+
+ $null = $arrayAdded.Add([PSCustomObject]@{
+ subscriptionId = $resourceSplitted[2]
+ resourceType0 = $resourceSplitted[6]
+ resourceType1 = $resourceSplitted[7]
+ resourceType2 = $resourceSplitted[9]
+ resourceType3 = $resourceSplitted[11]
+ })
+
+ $subDetails = $htSubscriptionsMgPath.($resourceSplitted[2])
+ $null = $arrayAddedAndRemoved.Add([pscustomobject]@{
+ action = 'add'
+ subscriptionId = $resourceSplitted[2]
+ subscriptionName = $subDetails.displayName
+ mgPath = $subDetails.pathDelimited
+ resourceId = $resource
+ resourceType0 = $resourceSplitted[6]
+ resourceType1 = $resourceSplitted[7]
+ resourceType2 = $resourceSplitted[9]
+ resourceType3 = $resourceSplitted[11]
+ })
+
+ if ($resourceSplitted.Count -gt 13) {
+ Write-Host ' Unforeseen Resource type!'
+ Write-Host " Please report this Resource type at $($GithubRepository): '$resource'"
+ }
+ }
+
+ if ($arrayAdded.Count -gt 0) {
+ $arrayGroupedByResourceType = $arrayAdded | Group-Object -Property resourceType0, resourceType1, resourceType2, resourceType3
+ foreach ($resourceType in $arrayGroupedByResourceType) {
+ $arrayGroupedBySubscription = $arrayGroupedByResourceType.where({ $_.Name -eq $resourceType.Name }).Group | Group-Object -Property subscriptionId | Select-Object -ExcludeProperty Group
+ $null = $arrayResourceFluctuationFinal.Add([PSCustomObject]@{
+ Event = 'Added'
+ ResourceType = ($resourceType.Name -replace ', ', '/')
+ 'Resource count' = $resourceType.Count
+ 'Subscription count' = ($arrayGroupedBySubscription | Measure-Object).Count
+ })
+ }
+ }
+
+ #REMOVED
+ $arrayRemoved = [System.Collections.ArrayList]@()
+ foreach ($resource in $x.RefOnly) {
+ $resourceSplitted = $resource.split('/')
+ #$resourceSplitted
+
+ $null = $arrayRemoved.Add([PSCustomObject]@{
+ subscriptionId = $resourceSplitted[2]
+ resourceType0 = $resourceSplitted[6]
+ resourceType1 = $resourceSplitted[7]
+ resourceType2 = $resourceSplitted[9]
+ resourceType3 = $resourceSplitted[11]
+ })
+
+ $subDetails = $htSubscriptionsMgPath.($resourceSplitted[2])
+ $null = $arrayAddedAndRemoved.Add([pscustomobject]@{
+ action = 'remove'
+ subscriptionId = $resourceSplitted[2]
+ subscriptionName = $subDetails.displayName
+ mgPath = $subDetails.pathDelimited
+ resourceId = $resource
+ resourceType0 = $resourceSplitted[6]
+ resourceType1 = $resourceSplitted[7]
+ resourceType2 = $resourceSplitted[9]
+ resourceType3 = $resourceSplitted[11]
+ })
+
+ if ($resourceSplitted.Count -gt 13) {
+ Write-Host ' Unforeseen Resource type!'
+ Write-Host " Please report this Resource type at $($GithubRepository): '$resource'"
+ }
+ }
+
+ if ($arrayRemoved.Count -gt 0) {
+ $arrayGroupedByResourceType = $arrayRemoved | Group-Object -Property resourceType0, resourceType1, resourceType2, resourceType3
+ foreach ($resourceType in $arrayGroupedByResourceType) {
+ $arrayGroupedBySubscription = $arrayGroupedByResourceType.where({ $_.Name -eq $resourceType.Name }).Group | Group-Object -Property subscriptionId | Select-Object -ExcludeProperty Group
+ $null = $arrayResourceFluctuationFinal.Add([PSCustomObject]@{
+ Event = 'Removed'
+ ResourceType = ($resourceType.Name -replace ', ', '/')
+ 'Resource count' = $resourceType.Count
+ 'Subscription count' = ($arrayGroupedBySubscription | Measure-Object).Count
+ })
+ }
+ }
+ }
+ }
+ else {
+ Write-Host " Process Resource fluctuation skipped, no previous output (*$($ManagementGroupId)_ResourcesAll.csv) found"
+ }
+
+ if ($arrayResourceFluctuationFinal.Count -gt 0 -and $doResourceFluctuation) {
+ if (-not $NoCsvExport) {
+ #DataCollection Export of Resource fluctuation
+ Write-Host " Exporting ResourceFluctuation CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourceFluctuation.csv'"
+ $arrayResourceFluctuationFinal | Sort-Object -Property ResourceType | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourceFluctuation.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+
+ Write-Host " Exporting ResourceFluctuation detailed CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourceFluctuationDetailed.csv'"
+ $arrayAddedAndRemoved | Sort-Object -Property Resource | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourceFluctuationDetailed.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+ }
+ Write-Host "Process Resource fluctuation duration: $((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds) seconds"
+
+ #DataCollection Export of All Resources
+ if ($resourcesIdsAll.Count -gt 0) {
+ if (-not $NoCsvExport) {
+ Write-Host "Exporting ResourcesAll CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourcesAll.csv'"
+ $resourcesIdsAll | Sort-Object -Property id | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_ResourcesAll.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+ }
+ else {
+ Write-Host "Not Exporting ResourcesAll CSV, as there are $($resourcesIdsAll.Count) resources"
+ }
+ }
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].LargeTenant -eq $false -or $azAPICallConf['htParameters'].PolicyAtScopeOnly -eq $false -or $azAPICallConf['htParameters'].RBACAtScopeOnly -eq $false) {
+ if (($azAPICallConf['checkContext']).Tenant.Id -ne $ManagementGroupId) {
+ addRowToTable `
+ -level (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain | Measure-Object).Count - 1) `
+ -mgName $getMgParentName `
+ -mgId $getMgParentId `
+ -mgParentId "'upperScopes'" `
+ -mgParentName 'upperScopes'
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].LargeTenant -eq $true -or $azAPICallConf['htParameters'].PolicyAtScopeOnly -eq $true) {
+ if (($azAPICallConf['checkContext']).Tenant.Id -ne $ManagementGroupId) {
+ $currentTask = "Policy assignments ('$($ManagementGroupId)')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementgroups/$($ManagementGroupId)/providers/Microsoft.Authorization/policyAssignments?`$filter=atScope()&api-version=2021-06-01"
+ $method = 'GET'
+ $upperScopesPolicyAssignments = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $upperScopesPolicyAssignments = $upperScopesPolicyAssignments | Where-Object { $_.properties.scope -ne "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)" }
+ $upperScopesPolicyAssignmentsPolicyCount = (($upperScopesPolicyAssignments | Where-Object { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' })).count
+ $upperScopesPolicyAssignmentsPolicySetCount = (($upperScopesPolicyAssignments | Where-Object { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/' })).count
+ $upperScopesPolicyAssignmentsPolicyAtScopeCount = (($upperScopesPolicyAssignments | Where-Object { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -and $_.Id -match "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)" })).count
+ $upperScopesPolicyAssignmentsPolicySetAtScopeCount = (($upperScopesPolicyAssignments | Where-Object { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/' -and $_.Id -match "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)" })).count
+ $upperScopesPolicyAssignmentsPolicyAndPolicySetAtScopeCount = ($upperScopesPolicyAssignmentsPolicyAtScopeCount + $upperScopesPolicyAssignmentsPolicySetAtScopeCount)
+ foreach ($L0mgmtGroupPolicyAssignment in $upperScopesPolicyAssignments) {
+
+ if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -OR $L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') {
+ if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/') {
+ $PolicyVariant = 'Policy'
+ $Id = ($L0mgmtGroupPolicyAssignment.properties.policydefinitionid).ToLower()
+ $Def = ($htCacheDefinitionsPolicy).($Id)
+ $PolicyAssignmentScope = $L0mgmtGroupPolicyAssignment.Properties.Scope
+ #$PolicyAssignmentNotScopes = $L0mgmtGroupPolicyAssignment.Properties.NotScopes -join "$CsvDelimiterOpposite "
+ $PolicyAssignmentId = ($L0mgmtGroupPolicyAssignment.Id).ToLower()
+ $PolicyAssignmentName = $L0mgmtGroupPolicyAssignment.Name
+ $PolicyAssignmentDisplayName = $L0mgmtGroupPolicyAssignment.Properties.DisplayName
+ if (($L0mgmtGroupPolicyAssignment.Properties.Description).length -eq 0) {
+ $PolicyAssignmentDescription = 'no description given'
+ }
+ else {
+ $PolicyAssignmentDescription = $L0mgmtGroupPolicyAssignment.Properties.Description
+ }
+
+ if ($L0mgmtGroupPolicyAssignment.identity) {
+ $PolicyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId
+ }
+ else {
+ $PolicyAssignmentIdentity = 'n/a'
+ }
+
+ if ($Def.Type -eq 'Custom') {
+ $policyDefintionScope = $Def.Scope
+ $policyDefintionScopeMgSub = $Def.ScopeMgSub
+ $policyDefintionScopeId = $Def.ScopeId
+ }
+ else {
+ $policyDefintionScope = 'n/a'
+ $policyDefintionScopeMgSub = 'n/a'
+ $policyDefintionScopeId = 'n/a'
+ }
+
+ $assignedBy = 'n/a'
+ $createdBy = ''
+ $createdOn = ''
+ $updatedBy = ''
+ $updatedOn = ''
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata) {
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.assignedBy) {
+ $assignedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.assignedBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.createdBy) {
+ $createdBy = $L0mgmtGroupPolicyAssignment.properties.metadata.createdBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.createdOn) {
+ $createdOn = $L0mgmtGroupPolicyAssignment.properties.metadata.createdOn
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy) {
+ $updatedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn) {
+ $updatedOn = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn
+ }
+ }
+
+ if (($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message) {
+ $nonComplianceMessage = ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message
+ }
+ else {
+ $nonComplianceMessage = ''
+ }
+
+ $formatedPolicyAssignmentParameters = ''
+ $hlp = $L0mgmtGroupPolicyAssignment.Properties.Parameters
+ if (-not [string]::IsNullOrEmpty($hlp)) {
+ $arrayPolicyAssignmentParameters = @()
+ $arrayPolicyAssignmentParameters = foreach ($parameterName in $hlp.PSObject.Properties.Name | Sort-Object) {
+ "$($parameterName)=$($hlp.($parameterName).Value -join "$($CsvDelimiter) ")"
+ }
+ $formatedPolicyAssignmentParameters = $arrayPolicyAssignmentParameters -join "$($CsvDelimiterOpposite) "
+ }
+
+ #mgSecureScore
+ $mgAscSecureScoreResult = ''
+
+ addRowToTable `
+ -level (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain).Count - 1) `
+ -mgName $getMgParentName `
+ -mgId $getMgParentId `
+ -mgParentId "'upperScopes'" `
+ -mgParentName 'upperScopes' `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Policy $Def.DisplayName `
+ -PolicyDescription $Def.Description `
+ -PolicyVariant $PolicyVariant `
+ -PolicyType $Def.Type `
+ -PolicyCategory $Def.Category `
+ -PolicyDefinitionIdGuid (($Def.Id) -replace '.*/') `
+ -PolicyDefinitionId $Def.PolicyDefinitionId `
+ -PolicyDefintionScope $policyDefintionScope `
+ -PolicyDefintionScopeMgSub $policyDefintionScopeMgSub `
+ -PolicyDefintionScopeId $policyDefintionScopeId `
+ -PolicyDefinitionsScopedLimit $LimitPOLICYPolicyDefinitionsScopedManagementGroup `
+ -PolicyDefinitionsScopedCount $PolicyDefinitionsScopedCount `
+ -PolicySetDefinitionsScopedLimit $LimitPOLICYPolicySetDefinitionsScopedManagementGroup `
+ -PolicySetDefinitionsScopedCount $PolicySetDefinitionsScopedCount `
+ -PolicyDefinitionEffectDefault ($htCacheDefinitionsPolicy).(($Def.PolicyDefinitionId)).effectDefaultValue `
+ -PolicyDefinitionEffectFixed ($htCacheDefinitionsPolicy).(($Def.PolicyDefinitionId)).effectFixedValue `
+ -PolicyAssignmentScope $PolicyAssignmentScope `
+ -PolicyAssignmentScopeMgSubRg 'Mg' `
+ -PolicyAssignmentScopeName ($PolicyAssignmentScope -replace '.*/', '') `
+ -PolicyAssignmentNotScopes $L0mgmtGroupPolicyAssignment.Properties.NotScopes `
+ -PolicyAssignmentId $PolicyAssignmentId `
+ -PolicyAssignmentName $PolicyAssignmentName `
+ -PolicyAssignmentDisplayName $PolicyAssignmentDisplayName `
+ -PolicyAssignmentDescription $PolicyAssignmentDescription `
+ -PolicyAssignmentEnforcementMode $L0mgmtGroupPolicyAssignment.Properties.EnforcementMode `
+ -PolicyAssignmentNonComplianceMessages $L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages `
+ -PolicyAssignmentIdentity $PolicyAssignmentIdentity `
+ -PolicyAssignmentLimit $LimitPOLICYPolicyAssignmentsManagementGroup `
+ -PolicyAssignmentCount $upperScopesPolicyAssignmentsPolicyCount `
+ -PolicyAssignmentAtScopeCount $upperScopesPolicyAssignmentsPolicyAtScopeCount `
+ -PolicyAssignmentParameters $L0mgmtGroupPolicyAssignment.Properties.Parameters `
+ -PolicyAssignmentParametersFormated $formatedPolicyAssignmentParameters `
+ -PolicyAssignmentAssignedBy $assignedBy `
+ -PolicyAssignmentCreatedBy $createdBy `
+ -PolicyAssignmentCreatedOn $createdOn `
+ -PolicyAssignmentUpdatedBy $updatedBy `
+ -PolicyAssignmentUpdatedOn $updatedOn `
+ -PolicySetAssignmentLimit $LimitPOLICYPolicySetAssignmentsManagementGroup `
+ -PolicySetAssignmentCount $upperScopesPolicyAssignmentsPolicySetCount `
+ -PolicySetAssignmentAtScopeCount $upperScopesPolicyAssignmentsPolicySetAtScopeCount `
+ -PolicyAndPolicySetAssignmentAtScopeCount $upperScopesPolicyAssignmentsPolicyAndPolicySetAtScopeCount
+ }
+
+ if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') {
+ $PolicyVariant = 'PolicySet'
+ $Id = ($L0mgmtGroupPolicyAssignment.properties.policydefinitionid).ToLower()
+ $Def = ($htCacheDefinitionsPolicySet).($Id)
+ $PolicyAssignmentScope = $L0mgmtGroupPolicyAssignment.Properties.Scope
+ #$PolicyAssignmentNotScopes = $L0mgmtGroupPolicyAssignment.Properties.NotScopes -join "$CsvDelimiterOpposite "
+ $PolicyAssignmentId = ($L0mgmtGroupPolicyAssignment.Id).ToLower()
+ $PolicyAssignmentName = $L0mgmtGroupPolicyAssignment.Name
+ $PolicyAssignmentDisplayName = $L0mgmtGroupPolicyAssignment.Properties.DisplayName
+ if (($L0mgmtGroupPolicyAssignment.Properties.Description).length -eq 0) {
+ $PolicyAssignmentDescription = 'no description given'
+ }
+ else {
+ $PolicyAssignmentDescription = $L0mgmtGroupPolicyAssignment.Properties.Description
+ }
+
+ if ($L0mgmtGroupPolicyAssignment.identity) {
+ $PolicyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId
+ }
+ else {
+ $PolicyAssignmentIdentity = 'n/a'
+ }
+
+ if ($Def.Type -eq 'Custom') {
+ $policyDefintionScope = $Def.Scope
+ $policyDefintionScopeMgSub = $Def.ScopeMgSub
+ $policyDefintionScopeId = $Def.ScopeId
+ }
+ else {
+ $policyDefintionScope = 'n/a'
+ $policyDefintionScopeMgSub = 'n/a'
+ $policyDefintionScopeId = 'n/a'
+ }
+
+ $assignedBy = 'n/a'
+ $createdBy = ''
+ $createdOn = ''
+ $updatedBy = ''
+ $updatedOn = ''
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata) {
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.assignedBy) {
+ $assignedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.assignedBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.createdBy) {
+ $createdBy = $L0mgmtGroupPolicyAssignment.properties.metadata.createdBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.createdOn) {
+ $createdOn = $L0mgmtGroupPolicyAssignment.properties.metadata.createdOn
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy) {
+ $updatedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn) {
+ $updatedOn = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn
+ }
+ }
+
+ if (($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message) {
+ $nonComplianceMessage = ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message
+ }
+ else {
+ $nonComplianceMessage = ''
+ }
+
+ $formatedPolicyAssignmentParameters = ''
+ $hlp = $L0mgmtGroupPolicyAssignment.Properties.Parameters
+ if (-not [string]::IsNullOrEmpty($hlp)) {
+ $arrayPolicyAssignmentParameters = @()
+ $arrayPolicyAssignmentParameters = foreach ($parameterName in $hlp.PSObject.Properties.Name | Sort-Object) {
+ "$($parameterName)=$($hlp.($parameterName).Value -join "$($CsvDelimiter) ")"
+ }
+ $formatedPolicyAssignmentParameters = $arrayPolicyAssignmentParameters -join "$($CsvDelimiterOpposite) "
+ }
+
+ addRowToTable `
+ -level (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain).Count - 1) `
+ -mgName $getMgParentName `
+ -mgId $getMgParentId `
+ -mgParentId "'upperScopes'" `
+ -mgParentName 'upperScopes' `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Policy $Def.DisplayName `
+ -PolicyDescription $Def.Description `
+ -PolicyVariant $PolicyVariant `
+ -PolicyType $Def.Type `
+ -PolicyCategory $Def.Category `
+ -PolicyDefinitionIdGuid (($Def.Id) -replace '.*/') `
+ -PolicyDefinitionId $Def.PolicyDefinitionId `
+ -PolicyDefintionScope $policyDefintionScope `
+ -PolicyDefintionScopeMgSub $policyDefintionScopeMgSub `
+ -PolicyDefintionScopeId $policyDefintionScopeId `
+ -PolicyDefinitionsScopedLimit $LimitPOLICYPolicyDefinitionsScopedManagementGroup `
+ -PolicyDefinitionsScopedCount $PolicyDefinitionsScopedCount `
+ -PolicySetDefinitionsScopedLimit $LimitPOLICYPolicySetDefinitionsScopedManagementGroup `
+ -PolicySetDefinitionsScopedCount $PolicySetDefinitionsScopedCount `
+ -PolicyAssignmentScope $PolicyAssignmentScope `
+ -PolicyAssignmentScopeMgSubRg 'Mg' `
+ -PolicyAssignmentScopeName ($PolicyAssignmentScope -replace '.*/', '') `
+ -PolicyAssignmentNotScopes $L0mgmtGroupPolicyAssignment.Properties.NotScopes `
+ -PolicyAssignmentId $PolicyAssignmentId `
+ -PolicyAssignmentName $PolicyAssignmentName `
+ -PolicyAssignmentDisplayName $PolicyAssignmentDisplayName `
+ -PolicyAssignmentDescription $PolicyAssignmentDescription `
+ -PolicyAssignmentEnforcementMode $L0mgmtGroupPolicyAssignment.Properties.EnforcementMode `
+ -PolicyAssignmentNonComplianceMessages $L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages `
+ -PolicyAssignmentIdentity $PolicyAssignmentIdentity `
+ -PolicyAssignmentLimit $LimitPOLICYPolicyAssignmentsManagementGroup `
+ -PolicyAssignmentCount $upperScopesPolicyAssignmentsPolicyCount `
+ -PolicyAssignmentAtScopeCount $upperScopesPolicyAssignmentsPolicyAtScopeCount `
+ -PolicyAssignmentParameters $L0mgmtGroupPolicyAssignment.Properties.Parameters `
+ -PolicyAssignmentParametersFormated $formatedPolicyAssignmentParameters `
+ -PolicyAssignmentAssignedBy $assignedBy `
+ -PolicyAssignmentCreatedBy $createdBy `
+ -PolicyAssignmentCreatedOn $createdOn `
+ -PolicyAssignmentUpdatedBy $updatedBy `
+ -PolicyAssignmentUpdatedOn $updatedOn `
+ -PolicySetAssignmentLimit $LimitPOLICYPolicySetAssignmentsManagementGroup `
+ -PolicySetAssignmentCount $upperScopesPolicyAssignmentsPolicySetCount `
+ -PolicySetAssignmentAtScopeCount $upperScopesPolicyAssignmentsPolicySetAtScopeCount `
+ -PolicyAndPolicySetAssignmentAtScopeCount $upperScopesPolicyAssignmentsPolicyAndPolicySetAtScopeCount
+ }
+ }
+ }
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].LargeTenant -eq $true -or $azAPICallConf['htParameters'].RBACAtScopeOnly -eq $true) {
+
+ #RoleAssignment API (system metadata e.g. createdOn)
+ $currentTask = "Role assignments API '$($ManagementGroupId)'"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01"
+ $method = 'GET'
+ $roleAssignmentsFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask
+
+ if ($roleAssignmentsFromAPI.Count -gt 0) {
+ $principalsToResolve = @()
+ $principalsToResolve = foreach ($ra in $roleAssignmentsFromAPI.properties | Sort-Object -Property principalId -Unique) {
+ if (-not $htPrincipals.($ra.principalId)) {
+ $ra.principalId
+ }
+ }
+
+ if ($principalsToResolve.Count -gt 0) {
+ ResolveObjectIds -objectIds $principalsToResolve
+ }
+ }
+
+ #$upperScopesRoleAssignments = GetRoleAssignments -Scope "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)" -scopeDetails "getRoleAssignments upperScopes (Mg)"
+ $upperScopesRoleAssignments = $roleAssignmentsFromAPI
+
+ $upperScopesRoleAssignmentsLimitUtilization = (($upperScopesRoleAssignments | Where-Object { $_.properties.scope -eq "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)" })).count
+ #tenantLevelRoleAssignments
+ if (-not $htMgAtScopeRoleAssignments.'tenantLevelRoleAssignments') {
+ $tenantLevelRoleAssignmentsCount = (($upperScopesRoleAssignments | Where-Object { $_.id -like '/providers/Microsoft.Authorization/roleAssignments/*' })).count
+ $htMgAtScopeRoleAssignments.'tenantLevelRoleAssignments' = @{}
+ $htMgAtScopeRoleAssignments.'tenantLevelRoleAssignments'.AssignmentsCount = $tenantLevelRoleAssignmentsCount
+ }
+
+ foreach ($upperScopesRoleAssignment in $upperScopesRoleAssignments) {
+
+ $roleAssignmentId = ($upperScopesRoleAssignment.id).ToLower()
+
+ if ($upperScopesRoleAssignment.properties.scope -ne "/providers/Microsoft.Management/managementGroups/$ManagementGroupId") {
+ $roleDefinitionId = $upperScopesRoleAssignment.properties.roleDefinitionId
+ $roleDefinitionIdGuid = $roleDefinitionId -replace '.*/'
+
+ if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) {
+ $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'"
+ }
+ else {
+ $roleDefinitionName = ($htCacheDefinitionsRole).($roleDefinitionIdGuid).Name
+ }
+
+ if (($htPrincipals.($upperScopesRoleAssignment.properties.principalId).displayName).length -eq 0) {
+ $roleAssignmentIdentityDisplayname = 'n/a'
+ }
+ else {
+ if ($htPrincipals.($upperScopesRoleAssignment.properties.principalId).type -eq 'User') {
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $false) {
+ $roleAssignmentIdentityDisplayname = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).displayName
+ }
+ else {
+ $roleAssignmentIdentityDisplayname = 'scrubbed'
+ }
+ }
+ else {
+ $roleAssignmentIdentityDisplayname = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).displayName
+ }
+ }
+ if (-not $htPrincipals.($upperScopesRoleAssignment.properties.principalId).signInName) {
+ $roleAssignmentIdentitySignInName = 'n/a'
+ }
+ else {
+ if ($htPrincipals.($upperScopesRoleAssignment.properties.principalId).type -eq 'User') {
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $false) {
+ $roleAssignmentIdentitySignInName = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).signInName
+ }
+ else {
+ $roleAssignmentIdentitySignInName = 'scrubbed'
+ }
+ }
+ else {
+ $roleAssignmentIdentitySignInName = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).signInName
+ }
+ }
+ $roleAssignmentIdentityObjectId = $upperScopesRoleAssignment.properties.principalId
+ $roleAssignmentIdentityObjectType = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).type
+
+ $roleAssignmentScope = $upperScopesRoleAssignment.properties.scope
+ $roleAssignmentScopeName = $roleAssignmentScope -replace '.*/'
+ $roleAssignmentScopeType = 'MG'
+
+ $roleSecurityCustomRoleOwner = 0
+ if (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).Actions -eq '*' -and ((($htCacheDefinitionsRole).$($roleDefinitionIdGuid).NotActions)).length -eq 0 -and ($htCacheDefinitionsRole).$($roleDefinitionIdGuid).IsCustom -eq $True) {
+ $roleSecurityCustomRoleOwner = 1
+ }
+ $roleSecurityOwnerAssignmentSP = 0
+ if ((($htCacheDefinitionsRole).$($roleDefinitionIdGuid).Id -eq '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' -and $roleAssignmentIdentityObjectType -eq 'ServicePrincipal') -or (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).Actions -eq '*' -and ((($htCacheDefinitionsRole).$($roleDefinitionIdGuid).NotActions)).length -eq 0 -and ($htCacheDefinitionsRole).$($roleDefinitionIdGuid).IsCustom -eq $True -and $roleAssignmentIdentityObjectType -eq 'ServicePrincipal')) {
+ $roleSecurityOwnerAssignmentSP = 1
+ }
+
+ $createdBy = ''
+ $createdOn = ''
+ $createdOnUnformatted = $null
+ $updatedBy = ''
+ $updatedOn = ''
+
+ if ($upperScopesRoleAssignment.properties.createdBy) {
+ $createdBy = $upperScopesRoleAssignment.properties.createdBy
+ }
+ if ($upperScopesRoleAssignment.properties.createdOn) {
+ $createdOn = $upperScopesRoleAssignment.properties.createdOn
+ }
+ if ($upperScopesRoleAssignment.properties.updatedBy) {
+ $updatedBy = $upperScopesRoleAssignment.properties.updatedBy
+ }
+ if ($upperScopesRoleAssignment.properties.updatedOn) {
+ $updatedOn = $upperScopesRoleAssignment.properties.updatedOn
+ }
+ $createdOnUnformatted = $upperScopesRoleAssignment.properties.createdOn
+
+ if (($azAPICallConf['checkContext']).Tenant.Id -ne $ManagementGroupId) {
+ $levelToUse = (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain).Count - 1)
+ $toUseAsmgName = $getMgParentName
+ $toUseAsmgId = $getMgParentId
+ $toUseAsmgParentId = "'upperScopes'"
+ $toUseAsmgParentName = 'upperScopes'
+ }
+ else {
+ $levelToUse = (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain).Count)
+ $toUseAsmgName = $selectedManagementGroupId.DisplayName
+ $toUseAsmgId = $selectedManagementGroupId.Name
+ $toUseAsmgParentId = 'Tenant'
+ $toUseAsmgParentName = 'Tenant'
+ }
+
+ #mgSecureScore
+ $mgAscSecureScoreResult = ''
+
+ addRowToTable `
+ -level $levelToUse `
+ -mgName $toUseAsmgName `
+ -mgId $toUseAsmgId `
+ -mgParentId $toUseAsmgParentId `
+ -mgParentName $toUseAsmgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -RoleDefinitionId $roleDefinitionIdGuid `
+ -RoleDefinitionName $roleDefinitionName `
+ -RoleIsCustom ($htCacheDefinitionsRole).$($roleDefinitionIdGuid).IsCustom `
+ -RoleAssignableScopes (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).AssignableScopes -join "$CsvDelimiterOpposite ") `
+ -RoleActions (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).Actions -join "$CsvDelimiterOpposite ") `
+ -RoleNotActions (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).NotActions -join "$CsvDelimiterOpposite ") `
+ -RoleDataActions (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).DataActions -join "$CsvDelimiterOpposite ") `
+ -RoleNotDataActions (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).NotDataActions -join "$CsvDelimiterOpposite ") `
+ -RoleAssignmentIdentityDisplayname $roleAssignmentIdentityDisplayname `
+ -RoleAssignmentIdentitySignInName $roleAssignmentIdentitySignInName `
+ -RoleAssignmentIdentityObjectId $roleAssignmentIdentityObjectId `
+ -RoleAssignmentIdentityObjectType $roleAssignmentIdentityObjectType `
+ -RoleAssignmentId $roleAssignmentId `
+ -RoleAssignmentScope $roleAssignmentScope `
+ -RoleAssignmentScopeName $roleAssignmentScopeName `
+ -RoleAssignmentScopeType $roleAssignmentScopeType `
+ -RoleAssignmentCreatedBy $createdBy `
+ -RoleAssignmentCreatedOn $createdOn `
+ -RoleAssignmentCreatedOnUnformatted $createdOnUnformatted `
+ -RoleAssignmentUpdatedBy $updatedBy `
+ -RoleAssignmentUpdatedOn $updatedOn `
+ -RoleAssignmentsLimit $LimitRBACRoleAssignmentsManagementGroup `
+ -RoleAssignmentsCount $upperScopesRoleAssignmentsLimitUtilization `
+ -RoleSecurityCustomRoleOwner $roleSecurityCustomRoleOwner `
+ -RoleSecurityOwnerAssignmentSP $roleSecurityOwnerAssignmentSP `
+ -RoleAssignmentPIM 'unknown'
+ }
+ }
+ }
+}
+function processDefinitionInsights() {
+ $startDefinitionInsights = Get-Date
+ Write-Host ' Building DefinitionInsights'
+
+ $md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
+ $utf8 = New-Object -TypeName System.Text.UTF8Encoding
+
+ #region definitionInsightsAzurePolicy
+ $htmlDefinitionInsights = [System.Text.StringBuilder]::new()
+ [void]$htmlDefinitionInsights.AppendLine( @'
+
+
+'@)
+
+ #policy/policySet preQuery
+ #region preQuery
+ $htPolicyWithAssignments = @{}
+ $htPolicyWithAssignments.policy = @{}
+ $htPolicyWithAssignments.policySet = @{}
+
+ foreach ($policyOrPolicySet in $arrayPolicyAssignmentsEnriched | Sort-Object -Property PolicyAssignmentId -Unique | Group-Object -Property PolicyId, PolicyVariant) {
+ $policyOrPolicySetNameSplit = $policyOrPolicySet.name.split(', ')
+ if ($policyOrPolicySetNameSplit[1] -eq 'Policy') {
+ #policy
+ if (-not ($htPolicyWithAssignments).policy.($policyOrPolicySetNameSplit[0])) {
+ $pscustomObj = [System.Collections.ArrayList]@()
+ foreach ($entry in $policyOrPolicySet.group) {
+ $null = $pscustomObj.Add([PSCustomObject]@{
+ PolicyAssignmentId = $entry.PolicyAssignmentId
+ PolicyAssignmentDisplayName = $entry.PolicyAssignmentDisplayName
+ })
+ }
+ ($htPolicyWithAssignments).policy.($policyOrPolicySetNameSplit[0]) = @{}
+ ($htPolicyWithAssignments).policy.($policyOrPolicySetNameSplit[0]).Assignments = [array]($pscustomObj)
+ }
+ }
+ else {
+ #policySet
+ if (-not ($htPolicyWithAssignments).policySet.($policyOrPolicySetNameSplit[0])) {
+ $pscustomObj = [System.Collections.ArrayList]@()
+ foreach ($entry in $policyOrPolicySet.group) {
+ $null = $pscustomObj.Add([PSCustomObject]@{
+ PolicyAssignmentId = $entry.PolicyAssignmentId
+ PolicyAssignmentDisplayName = $entry.PolicyAssignmentDisplayName
+ })
+ }
+ ($htPolicyWithAssignments).policySet.($policyOrPolicySetNameSplit[0]) = @{}
+ ($htPolicyWithAssignments).policySet.($policyOrPolicySetNameSplit[0]).Assignments = [array]($pscustomObj)
+ }
+ }
+ }
+
+ foreach ($customPolicy in $tenantCustomPolicies) {
+ if ($htPoliciesWithAssignmentOnRgRes.($customPolicy.PolicyDefinitionId)) {
+ if (-not ($htPolicyWithAssignments).policy.($customPolicy.PolicyDefinitionId)) {
+ ($htPolicyWithAssignments).policy.($customPolicy.PolicyDefinitionId) = @{}
+ ($htPolicyWithAssignments).policy.($customPolicy.PolicyDefinitionId).Assignments = [array]($htPoliciesWithAssignmentOnRgRes.($customPolicy.PolicyDefinitionId).Assignments)
+ }
+ else {
+ $array = @()
+ $array += ($htPolicyWithAssignments).policy.($customPolicy.PolicyDefinitionId).Assignments
+ $array += $htPoliciesWithAssignmentOnRgRes.($customPolicy.PolicyDefinitionId).Assignments
+ ($htPolicyWithAssignments).policy.($customPolicy.PolicyDefinitionId).Assignments = $array
+ }
+ }
+ }
+
+ foreach ($customPolicySet in $tenantCustomPolicySets) {
+ if ($htPoliciesWithAssignmentOnRgRes.($customPolicySet.PolicyDefinitionId)) {
+ if (-not ($htPolicyWithAssignments).policySet.($customPolicySet.PolicyDefinitionId)) {
+ ($htPolicyWithAssignments).policySet.($customPolicySet.PolicyDefinitionId) = @{}
+ ($htPolicyWithAssignments).policySet.($customPolicySet.PolicyDefinitionId).Assignments = [array]($htPoliciesWithAssignmentOnRgRes.($customPolicySet.PolicyDefinitionId).Assignments)
+ }
+ else {
+ $array = @()
+ $array += ($htPolicyWithAssignments).policySet.($customPolicySet.PolicyDefinitionId).Assignments
+ $array += $htPoliciesWithAssignmentOnRgRes.($customPolicySet.PolicyDefinitionId).Assignments
+ ($htPolicyWithAssignments).policySet.($customPolicySet.PolicyDefinitionId).Assignments = $array
+ }
+ }
+ }
+ #endregion preQuery
+
+ #region definitionInsightsPolicyDefinitions
+ $startDefinitionInsightsPolicyDefinitions = Get-Date
+ Write-Host ' processing DefinitionInsights Policy definitions'
+ ShowMemoryUsage
+ $tfCount = $tenantAllPoliciesCount
+ $htmlTableId = 'definitionInsights_Policy'
+ [void]$htmlDefinitionInsights.AppendLine( @"
+
$tenantAllPoliciesCount Policy definitions
+
+
+
+
+
+ Search JSON
+
+
+
+
+ Builtin/Custom/Static
+
+
+
+
+ ALZ
+
+
+
+
+ Category
+
+
+
+
+ Deprecated
+
+
+
+
+ Preview
+
+
+
+
+ Scope Mg/Sub
+
+
+
+
+ Scope Name/Id
+
+
+
+
+ Effect default
+
+
+
+
+ hasAssignment
+
+
+
+
+ polPolAssignments
+
+
+
+
+ polhid1
+
+
+
+
+ usedInPolicySet
+
+
+
+
+ polUsedInPolicySetCount
+
+
+
+
+ polUsedInPolicySets
+
+
+
+
+ Roles
+
+
+
+
+
+
+
+
+
+
+
+JSON
+PolicyType
+ALZ
+Category
+Deprecated
+Preview
+Scope Mg/Sub
+Scope Name/Id
+effectDefaultValue
+hasAssignments
+Assignments Count
+Assignments
+UsedInPolicySet
+PolicySetsCount
+PolicySets
+Roles
+
+
+
+"@)
+
+ $cnter = 0
+ $htmlDefinitionInsightshlp = $null
+ $htmlDefinitionInsightshlp = foreach ($policy in (($htCacheDefinitionsPolicy).Values | Sort-Object @{Expression = { $_.DisplayName } }, @{Expression = { $_.PolicyDefinitionId } })) {
+
+ $cnter++
+ if ($cnter % 1000 -eq 0) {
+ Write-Host " $cnter Policy definitions processed"
+ ShowMemoryUsage
+ }
+
+ $hasAssignments = 'false'
+ $assignmentsCount = 0
+ $assignmentsDetailed = 'n/a'
+
+ if (($htPolicyWithAssignments).policy.($policy.PolicyDefinitionId)) {
+ $hasAssignments = 'true'
+ $assignments = ($htPolicyWithAssignments).policy.($policy.PolicyDefinitionId).Assignments
+ $assignmentsCount = $assignments.Count
+
+ if ($assignmentsCount -gt 0) {
+ $arrayAssignmentDetails = @()
+ $arrayAssignmentDetails = foreach ($assignment in $assignments) {
+ if ($assignment.PolicyAssignmentDisplayName -eq '') {
+ $polAssDisplayName = '#no AssignmentName given '
+ }
+ else {
+ $polAssDisplayName = $assignment.PolicyAssignmentDisplayName
+ }
+ "$($assignment.PolicyAssignmentId) ($($polAssDisplayName) )"
+ }
+ $assignmentsDetailed = $arrayAssignmentDetails -join "$CsvDelimiterOpposite "
+ }
+
+ }
+
+ $roleDefinitionIds = 'n/a'
+ if ($policy.RoleDefinitionIds -ne 'n/a') {
+ $arrayRoleDefDetails = @()
+ $arrayRoleDefDetails = foreach ($roleDef in $policy.RoleDefinitionIds) {
+ $roleDefIdOnly = $roleDef -replace '.*/'
+ if (($roleDefIdOnly).Length -ne 36) {
+ "'INVALID RoleDefId!' ($($roleDefIdOnly))"
+ }
+ else {
+ $roleDefHlp = ($htCacheDefinitionsRole).($roleDefIdOnly)
+ "'$($roleDefHlp.Name)' ($($roleDefHlp.Id))"
+ }
+ }
+ $roleDefinitionIds = $arrayRoleDefDetails -join "$CsvDelimiterOpposite "
+ }
+
+ $scopeDetails = 'n/a'
+ if ($policy.ScopeId -ne 'n/a') {
+ if ([string]::IsNullOrEmpty($policy.ScopeId)) {
+ Write-Host "unexpected IsNullOrEmpty - processing: $($policy | ConvertTo-Json -Depth 99)"
+ }
+ $scopeDetails = "$($policy.ScopeId) ($($htEntities.($policy.ScopeId).DisplayName))"
+ }
+
+ $usedInPolicySet = 'false'
+ $usedInPolicySetCount = 0
+ $usedInPolicySets = 'n/a'
+
+ if ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId)) {
+ $usedInPolicySet = 'true'
+ $usedInPolicySetCount = ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId).policySet).Count
+ $usedInPolicySets = ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId).policySet | Sort-Object) -join "$CsvDelimiterOpposite "
+ }
+
+ $json = $($policy.Json | ConvertTo-Json -Depth 99)
+ $guid = ([System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($policy.PolicyDefinitionId)))) -replace '-'
+ @"
+
+
+
+
+ Copy definition
+
+
+
+
+
+
+
+
+$($policy.Type)
+$($policy.ALZ)
+$($policy.Category -replace '<', '<' -replace '>', '>')
+$($policy.Deprecated)
+$($policy.Preview)
+$($policy.ScopeMgSub)
+$($scopeDetails -replace '<', '<' -replace '>', '>')
+$($policy.effectDefaultValue)
+$hasAssignments
+$assignmentsCount
+$assignmentsDetailed
+$usedInPolicySet
+$usedInPolicySetCount
+$usedInPolicySets
+$($roleDefinitionIds -replace '<', '<' -replace '>', '>')
+
+"@
+ }
+ [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp)
+ if ($NoDefinitionInsightsDedicatedHTML) {
+ $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlDefinitionInsights = [System.Text.StringBuilder]::new()
+ }
+ [void]$htmlDefinitionInsights.AppendLine( @"
+
+
+
+
+
+"@)
+ $endDefinitionInsightsPolicyDefinitions = Get-Date
+ Write-Host " DefinitionInsights Policy definitions duration: $((New-TimeSpan -Start $startDefinitionInsightsPolicyDefinitions -End $endDefinitionInsightsPolicyDefinitions).TotalMinutes) minutes ($((New-TimeSpan -Start $startDefinitionInsightsPolicyDefinitions -End $endDefinitionInsightsPolicyDefinitions).TotalSeconds) seconds)"
+ showMemoryUsage
+ #endregion definitionInsightsPolicyDefinitions
+
+ #region definitionInsightsPolicySetDefinitions
+ $startDefinitionInsightsPolicySetDefinitions = Get-Date
+ Write-Host ' processing DefinitionInsights PolicySet definitions'
+ ShowMemoryUsage
+ $tfCount = $tenantAllPolicySetsCount
+ $htmlTableId = 'definitionInsights_PolicySet'
+ [void]$htmlDefinitionInsights.AppendLine( @"
+
$tenantAllPolicySetsCount PolicySet definitions
+
+
+
+
+
+ Search JSON
+
+
+
+
+ Builtin/Custom
+
+
+
+
+ ALZ
+
+
+
+
+ Category
+
+
+
+
+ Deprecated
+
+
+
+
+ Preview
+
+
+
+
+ Scope Mg/Sub
+
+
+
+
+ Scope Name/Id
+
+
+
+
+ hasAssignment
+
+
+
+
+
+
+
+
+
+
+
+
+JSON
+PolicySet Type
+ALZ
+Category
+Deprecated
+Preview
+Scope Mg/Sub
+Scope Name/Id
+hasAssignments
+Assignments Count
+Assignments
+
+
+
+"@)
+ $htmlDefinitionInsightshlp = $null
+ $htmlDefinitionInsightshlp = foreach ($policySet in ($tenantAllPolicySets | Sort-Object @{Expression = { $_.DisplayName } }, @{Expression = { $_.PolicyDefinitionId } })) {
+ $hasAssignments = 'false'
+ $assignmentsCount = 0
+ $assignmentsDetailed = 'n/a'
+
+ if (($htPolicyWithAssignments).policySet.($policySet.PolicyDefinitionId)) {
+ $hasAssignments = 'true'
+ $assignments = ($htPolicyWithAssignments).policySet.($policySet.PolicyDefinitionId).Assignments
+ $assignmentsCount = ($assignments | Measure-Object).Count
+
+ if ($assignmentsCount -gt 0) {
+ $arrayAssignmentDetails = @()
+ $arrayAssignmentDetails = foreach ($assignment in $assignments) {
+ if ($assignment.PolicyAssignmentDisplayName -eq '') {
+ $polAssDisplayName = '#no AssignmentName given '
+ }
+ else {
+ $polAssDisplayName = $assignment.PolicyAssignmentDisplayName
+ }
+ "$($assignment.PolicyAssignmentId) ($($polAssDisplayName) )"
+ }
+ $assignmentsDetailed = $arrayAssignmentDetails -join "$CsvDelimiterOpposite "
+ }
+ }
+
+ $scopeDetails = 'n/a'
+ if ($policySet.ScopeId -ne 'n/a') {
+ $scopeDetails = "$($policySet.ScopeId) ($($htEntities.($policySet.ScopeId).DisplayName))"
+ }
+ $json = $($policySet.Json | ConvertTo-Json -Depth 99)
+ $guid = ([System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($policySet.PolicyDefinitionId)))) -replace '-'
+ @"
+
+
+
+
+ Copy definition
+
+
+
+
+
+
+
+
+$($policySet.Type)
+$($policySet.ALZ)
+$($policySet.Category -replace '<', '<' -replace '>', '>')
+$($policySet.Deprecated)
+$($policySet.Preview)
+$($policySet.ScopeMgSub)
+$($scopeDetails -replace '<', '<' -replace '>', '>')
+$hasAssignments
+$assignmentsCount
+$assignmentsDetailed
+
+"@
+ }
+ [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp)
+ if ($NoDefinitionInsightsDedicatedHTML) {
+ $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlDefinitionInsights = [System.Text.StringBuilder]::new()
+ }
+ [void]$htmlDefinitionInsights.AppendLine( @"
+
+
+
+
+
+"@)
+ $endDefinitionInsightsPolicySetDefinitions = Get-Date
+ Write-Host " DefinitionInsights PolicySet definitions duration: $((New-TimeSpan -Start $startDefinitionInsightsPolicySetDefinitions -End $endDefinitionInsightsPolicySetDefinitions).TotalMinutes) minutes ($((New-TimeSpan -Start $startDefinitionInsightsPolicySetDefinitions -End $endDefinitionInsightsPolicySetDefinitions).TotalSeconds) seconds)"
+ showMemoryUsage
+ #endregion definitionInsightsPolicySetDefinitions
+
+ [void]$htmlDefinitionInsights.AppendLine( @'
+
+'@)
+ #endregion definitionInsightsAzurePolicy
+
+ #region definitionInsightsAzureRBAC
+ [void]$htmlDefinitionInsights.AppendLine( @'
+
+
+'@)
+
+ #RBAC preQuery
+ $htRoleWithAssignments = @{}
+ foreach ($roleDef in $rbacAll | Sort-Object -Property RoleAssignmentId -Unique | Group-Object -Property RoleId) {
+ if (-not ($htRoleWithAssignments).($roleDef.Name)) {
+ ($htRoleWithAssignments).($roleDef.Name) = @{}
+ ($htRoleWithAssignments).($roleDef.Name).Assignments = $roleDef.group
+ }
+ }
+
+ #region definitionInsightsRoleDefinitions
+ $startDefinitionInsightsRoleDefinitions = Get-Date
+ Write-Host ' processing DefinitionInsights Role definitions'
+ ShowMemoryUsage
+ $tfCount = $tenantAllRolesCount
+ $htmlTableId = 'definitionInsights_Roles'
+ [void]$htmlDefinitionInsights.AppendLine( @"
+
$tenantAllRolesCount Role definitions
+
+
+
+
+
+ Search JSON
+
+
+
+
+ Builtin/Custom
+
+
+
+
+ Data
+
+
+
+
+ canDoRoleAssignments
+
+
+
+
+ hasAssignment
+
+
+
+
+
+
+
+
+
+
+
+
+JSON
+Role Type
+Data
+canDoRoleAssignments
+hasAssignments
+Assignments Count
+Assignments
+
+
+
+"@)
+ $arrayRoleDefinitionsForCSVExport = [System.Collections.ArrayList]@()
+ $htmlDefinitionInsightshlp = $null
+ $htmlDefinitionInsightshlp = foreach ($role in ($tenantAllRoles | Sort-Object @{Expression = { $_.Name } })) {
+ if ($role.IsCustom -eq $true) {
+ $roleType = 'Custom'
+ $AssignableScopesCount = $role.AssignableScopes.Count
+ if ($role.AssignableScopes -like '*/providers/microsoft.management/managementgroups/*') {
+ $AssignableScopesMG = $true
+ }
+ else {
+ $AssignableScopesMG = $false
+ }
+
+ }
+ else {
+ $roleType = 'Builtin'
+ $AssignableScopesCount = ''
+ $AssignableScopesMG = ''
+ }
+ if (-not [string]::IsNullOrEmpty($role.DataActions) -or -not [string]::IsNullOrEmpty($role.NotDataActions)) {
+ $roleManageData = 'true'
+ }
+ else {
+ $roleManageData = 'false'
+ }
+
+ $hasAssignments = 'false'
+ $assignmentsCount = 0
+ $assignmentsDetailed = 'n/a'
+ if (($htRoleWithAssignments).($role.Id)) {
+ $hasAssignments = 'true'
+ $assignments = ($htRoleWithAssignments).($role.Id).Assignments
+ $assignmentsCount = ($assignments).Count
+ if ($assignmentsCount -gt 0) {
+ $arrayAssignmentDetails = @()
+ $arrayAssignmentDetails = foreach ($assignment in $assignments) {
+ "$($assignment.RoleAssignmentId)"
+ }
+ $assignmentsDetailed = $arrayAssignmentDetails -join "$CsvDelimiterOpposite "
+ }
+ }
+
+ #array for exportCSV
+ if (-not $NoCsvExport) {
+ $null = $arrayRoleDefinitionsForCSVExport.Add([PSCustomObject]@{
+ Name = $role.Name
+ Id = $role.Id
+ Description = $role.Json.description
+ Type = $roleType
+ AssignmentsCount = $assignmentsCount
+ AssignableScopesCount = $AssignableScopesCount
+ AssignableScopesMG = $AssignableScopesMG
+ AssignableScopes = ($role.AssignableScopes | Sort-Object) -join "$CsvDelimiterOpposite "
+ DataRelated = $roleManageData
+ RoleAssWriteCapable = $role.RoleCanDoRoleAssignments
+ Actions = $role.Actions -join "$CsvDelimiterOpposite "
+ NotActions = $role.NotActions -join "$CsvDelimiterOpposite "
+ DataActions = $role.DataActions -join "$CsvDelimiterOpposite "
+ NotDataActions = $role.NotDataActions -join "$CsvDelimiterOpposite "
+ })
+ }
+
+ $json = $role.Json | ConvertTo-Json -Depth 99
+ $guid = $role.Id -replace '-'
+ @"
+
+
+
+
+ Copy definition
+
+
+
+
+
+
+
+
+$($roleType)
+$($roleManageData)
+$($role.RoleCanDoRoleAssignments)
+$hasAssignments
+$assignmentsCount
+$assignmentsDetailed
+
+"@
+ }
+
+ #region exportCSV
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_RoleDefinitions"
+ Write-Host " Exporting RoleDefinitions CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ $arrayRoleDefinitionsForCSVExport | Sort-Object -Property Type, Name, Id | Export-Csv -Encoding utf8 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -NoTypeInformation
+ $arrayRoleDefinitionsForCSVExport = $null
+ }
+ #endregion exportCSV
+
+ [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp)
+ if ($NoDefinitionInsightsDedicatedHTML) {
+ $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlDefinitionInsights = [System.Text.StringBuilder]::new()
+ }
+ [void]$htmlDefinitionInsights.AppendLine( @"
+
+
+
+
+
+"@)
+ $endDefinitionInsightsRoleDefinitions = Get-Date
+ Write-Host " DefinitionInsights Role definitions duration: $((New-TimeSpan -Start $startDefinitionInsightsRoleDefinitions -End $endDefinitionInsightsRoleDefinitions).TotalMinutes) minutes ($((New-TimeSpan -Start $startDefinitionInsightsRoleDefinitions -End $endDefinitionInsightsRoleDefinitions).TotalSeconds) seconds)"
+ showMemoryUsage
+ #endregion definitionInsightsRoleDefinitions
+
+ [void]$htmlDefinitionInsights.AppendLine( @'
+
+'@)
+ #endregion definitionInsightsAzureRBAC
+
+ Write-Host " NoDefinitionInsightsDedicatedHTML: $NoDefinitionInsightsDedicatedHTML"
+ if ($NoDefinitionInsightsDedicatedHTML) {
+ Write-Host ' Appending DefinitionInsights to HTML'
+ $script:html += $htmlDefinitionInsights
+ $htmlDefinitionInsights = $null
+ $script:html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $script:html = $null
+ }
+ else {
+ Write-Host " Creating dedicated DefinitionInsights HTML ($($outputPath)$($DirectorySeparatorChar)$($fileName)_DefinitionInsights.html)"
+ $htmlDefinitionInsightsDedicated = $null
+ $htmlDefinitionInsightsDedicated += $htmlDefinitionInsightsDedicatedStart
+ $htmlDefinitionInsightsDedicated += $htmlDefinitionInsights
+ $htmlDefinitionInsightsDedicated += $htmlDefinitionInsightsDedicatedEnd
+ #$htmlDefinitionInsights = $null
+ $htmlDefinitionInsightsDedicated | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_DefinitionInsights.html" -Encoding utf8 -Force
+ #$script:htmlDefinitionInsightsDedicated = $null
+
+ $htmlDefinitionInsightsNo = @"
+ DefinitionInsights has been saved to dedicated HTML file '$($outputPathGiven)$($DirectorySeparatorChar)$($fileName)_DefinitionInsights.html ' (parameter -NoDefinitionInsightsDedicatedHTML = $($NoDefinitionInsightsDedicatedHTML))
+ Open DefinitionInsights
+"@
+ $script:html += $htmlDefinitionInsightsNo
+ #$htmlDefinitionInsightsNo = $null
+ $script:html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $script:html = $null
+ }
+
+
+ $endDefinitionInsights = Get-Date
+ Write-Host " DefinitionInsights processing duration: $((New-TimeSpan -Start $startDefinitionInsights -End $endDefinitionInsights).TotalMinutes) minutes ($((New-TimeSpan -Start $startDefinitionInsights -End $endDefinitionInsights).TotalSeconds) seconds)"
+ ShowMemoryUsage
+}
+function processDiagramMermaid() {
+ if ($ManagementGroupId -ne $azAPICallConf['checkContext'].Tenant.Id) {
+ $optimizedTableForPathQueryMg = $optimizedTableForPathQueryMg.where({ $_.mgParentId -ne "'upperScopes'" })
+ }
+ $mgLevels = ($optimizedTableForPathQueryMg | Sort-Object -Property Level -Unique).Level
+
+ foreach ($mgLevel in $mgLevels) {
+ $mgsInLevel = ($optimizedTableForPathQueryMg.where( { $_.Level -eq $mgLevel } )).MgId | Get-Unique
+ foreach ($mgInLevel in $mgsInLevel) {
+ $mgDetails = ($optimizedTableForPathQueryMg.where( { $_.Level -eq $mgLevel -and $_.MgId -eq $mgInLevel } ))
+ $mgName = $mgDetails.MgName | Get-Unique
+ $mgParentId = $mgDetails.mgParentId | Get-Unique
+ $mgParentName = $mgDetails.mgParentName | Get-Unique
+ if ($mgInLevel -ne $getMgParentId) {
+ $null = $script:arrayMgs.Add($mgInLevel)
+ }
+
+ if ($mgParentName -eq $mgParentId) {
+ $mgParentNameId = $mgParentName
+ }
+ else {
+ $mgParentNameId = "$mgParentName $mgParentId"
+ }
+
+ if ($mgName -eq $mgInLevel) {
+ $mgNameId = $mgName
+ }
+ else {
+ $mgNameId = "$mgName $mgInLevel"
+ }
+ $script:markdownhierarchyMgs += @"
+$mgParentId(`"$mgParentNameId`") --> $mgInLevel(`"$mgNameId`")`n
+"@
+ $subsUnderMg = ($optimizedTableForPathQueryMgAndSub.where( { -not [string]::IsNullOrEmpty($_.SubscriptionId) -and $_.Level -eq $mgLevel -and $_.MgId -eq $mgInLevel } )).SubscriptionId
+ if (($subsUnderMg | Measure-Object).count -gt 0) {
+ foreach ($subUnderMg in $subsUnderMg) {
+ $null = $script:arraySubs.Add("SubsOf$mgInLevel")
+ $mgDetalsN = ($optimizedTableForPathQueryMg.where( { $_.Level -eq $mgLevel -and $_.MgId -eq $mgInLevel } ))
+ $mgName = $mgDetalsN.MgName | Get-Unique
+ $mgParentId = $mgDetalsN.MgParentId | Get-Unique
+ $mgParentName = $mgDetalsN.MgParentName | Get-Unique
+ $subName = ($optimizedTableForPathQuery.where( { $_.Level -eq $mgLevel -and $_.MgId -eq $mgInLevel -and $_.SubscriptionId -eq $subUnderMg } )).Subscription | Get-Unique
+ $script:markdownTable += @"
+| $mgLevel | $mgName | $mgInLevel | $mgParentName | $mgParentId | $subName | $($subUnderMg -replace '.*/') |`n
+"@
+ }
+ $mgName = ($optimizedTableForPathQueryMg.where( { $_.Level -eq $mgLevel -and $_.MgId -eq $mgInLevel } )).MgName | Get-Unique
+ if ($mgName -eq $mgInLevel) {
+ $mgNameId = $mgName
+ }
+ else {
+ $mgNameId = "$mgName $mgInLevel"
+ }
+ $script:markdownhierarchySubs += @"
+$mgInLevel(`"$mgNameId`") --> SubsOf$mgInLevel(`"$(($subsUnderMg | Measure-Object).count)`")`n
+"@
+ }
+ else {
+ $mgDetailsM = ($optimizedTableForPathQueryMg.where( { $_.Level -eq $mgLevel -and $_.MgId -eq $mgInLevel } ))
+ $mgName = $mgDetailsM.MgName | Get-Unique
+ $mgParentId = $mgDetailsM.MgParentId | Get-Unique
+ $mgParentName = $mgDetailsM.MgParentName | Get-Unique
+ $script:markdownTable += @"
+| $mgLevel | $mgName | $mgInLevel | $mgParentName | $mgParentId | none | none |`n
+"@
+ }
+
+ if (($script:outOfScopeSubscriptions | Measure-Object).count -gt 0) {
+ $subsoosUnderMg = ($outOfScopeSubscriptions | Where-Object { $_.Level -eq $mgLevel -and $_.ManagementGroupId -eq $mgInLevel }).SubscriptionId | Get-Unique
+ if (($subsoosUnderMg | Measure-Object).count -gt 0) {
+ foreach ($subUnderMg in $subsoosUnderMg) {
+ $null = $script:arraySubsOos.Add("SubsoosOf$mgInLevel")
+ $mgDetalsN = ($optimizedTableForPathQueryMg.where( { $_.Level -eq $mgLevel -and $_.ManagementGroupId -eq $mgInLevel } ))
+ $mgName = $mgDetalsN.MgName | Get-Unique
+ }
+ $mgName = ($outOfScopeSubscriptions | Where-Object { $_.Level -eq $mgLevel -and $_.ManagementGroupId -eq $mgInLevel }).ManagementGroupName | Get-Unique
+ if ($mgName -eq $mgInLevel) {
+ $mgNameId = $mgName
+ }
+ else {
+ $mgNameId = "$mgName $mgInLevel"
+ }
+ $script:markdownhierarchySubs += @"
+$mgInLevel(`"$mgNameId`") --> SubsoosOf$mgInLevel(`"$(($subsoosUnderMg | Measure-Object).count)`")`n
+"@
+ }
+ }
+ }
+ }
+}
+function processHierarchyMapOnly {
+ foreach ($entity in $htEntities.values) {
+ if ($entity.parentNameChain -contains $ManagementGroupID -or $entity.Id -eq $ManagementGroupId) {
+
+ if ($entity.type -eq '/subscriptions') {
+ $hlpEntityParent = $htEntities.(($entity.parent))
+ addRowToTable `
+ -level (($entity.ParentNameChain).Count - 1) `
+ -mgName $hlpEntityParent.displayName `
+ -mgId ($entity.parent) `
+ -mgParentId $hlpEntityParent.Parent `
+ -mgParentName $hlpEntityParent.ParentDisplayName `
+ -Subscription $entity.DisplayName `
+ -SubscriptionId $entity.Id
+ }
+ if ($entity.type -eq 'Microsoft.Management/managementGroups') {
+ addRowToTable `
+ -level ($entity.ParentNameChain).Count `
+ -mgName $entity.displayname `
+ -mgId $entity.id `
+ -mgParentId $entity.Parent `
+ -mgParentName $entity.ParentDisplayName
+ }
+ }
+ }
+}
+function processHierarchyMapOnlyCustomData {
+ Write-Host 'HierarchyMapOnly with custom data' -ForegroundColor Yellow
+ Write-Host ' Parameter HierarchyMapOnly:' $HierarchyMapOnly
+ Write-Host ' Check if HierarchyMapOnlyCustomDataJSON is valid JSON'
+ try {
+ $HierarchyMapOnlyCustomDataConvertedAsHashTable = $HierarchyMapOnlyCustomDataJSON | ConvertFrom-Json -AsHashtable
+ $hierarchyMapOnlyCustomData = @{}
+ foreach ($key in $HierarchyMapOnlyCustomDataConvertedAsHashTable.Keys) {
+ $hierarchyMapOnlyCustomData.$key = $HierarchyMapOnlyCustomDataConvertedAsHashTable.$key | ConvertTo-Json | ConvertFrom-Json
+ }
+ Write-Host ' HierarchyMapOnlyCustomDataJSON is valid JSON' -ForegroundColor Green
+ }
+ catch {
+ throw 'HierarchyMapOnlyCustomDataJSON is not valid JSON'
+ }
+
+ Write-Host ' Parameter hierarchyMapOnlyCustomData count:' $hierarchyMapOnlyCustomData.Keys.Count
+
+ #validate
+ Write-Host ' ManagementGroupId validation'
+ if (-not $ManagementGroupId) {
+ throw 'ManagementGroupId validation failed - please provide ManagementGroupId (parameter -ManagementGroupId)'
+ }
+ else {
+ if ($hierarchyMapOnlyCustomData.$ManagementGroupId) {
+ Write-Host " ManagementGroupId '$ManagementGroupId' is available in 'hierarchyMapOnlyCustomData'"
+ }
+ else {
+ throw "ManagementGroupId validation failed - Given ManagementGroupId '$ManagementGroupId' is NOT available in 'hierarchyMapOnlyCustomData'"
+ }
+ Write-Host " ManagementGroupId validation passed '$ManagementGroupId'" -ForegroundColor Green
+ }
+
+ Write-Host ' CustomData validation'
+ if ($hierarchyMapOnlyCustomData.Keys.Count -gt 0) {
+ Write-Host ' Checking Keys (sanity check on first item)'
+ $requiredKeys = @('Id', 'ParentId', 'ParentNameChain', 'ParentDisplayName', 'DisplayName', 'type')
+ $firstItem = $hierarchyMapOnlyCustomData.($($hierarchyMapOnlyCustomData.Keys)[0])
+ foreach ($requiredKey in $requiredKeys) {
+ if (($firstitem | Get-Member -Name $requiredKey)) {
+ Write-Host " Key:$($requiredKey) exists" -ForegroundColor Green
+ }
+ else {
+ Write-Host " CustomData validation failed - required key:$($requiredKey) missing" -ForegroundColor DarkRed
+ Write-Host " The following keys are expected: $($requiredKeys -join ', ')"
+ throw "CustomData validation failed - required key:$($requiredKey) missing"
+ }
+ }
+
+ Write-Host ' Checking for existence of Management Groups'
+ $HierarchyMapOnlyCustomDataHroupedByType = $hierarchyMapOnlyCustomData.values | Group-Object -Property type
+ if ($HierarchyMapOnlyCustomDataHroupedByType.Name -notcontains 'Microsoft.Management/managementGroups') {
+ Write-Host ' CustomData validation failed - Custom data does not contain Manangement Groups'
+ throw 'CustomData validation failed - Custom data does not contain Manangement Groups'
+ }
+ else {
+ Write-Host ' Checking for existence of Management Groups passed' -ForegroundColor Green
+ }
+ foreach ($type in $HierarchyMapOnlyCustomDataHroupedByType) {
+ Write-Host " Custom Data contains $($type.Count) x type: '$($type.name)'"
+ }
+
+ Write-Host ' CustomData validation passed' -ForegroundColor Green
+ }
+ else {
+ Write-Host " CustomData validation failed - no data (`$hierarchyMapOnlyCustomData.Keys.Count: $($hierarchyMapOnlyCustomData.Keys.Count))"
+ throw "CustomData validation failed - no data (`$hierarchyMapOnlyCustomData.Keys.Count: $($hierarchyMapOnlyCustomData.Keys.Count))"
+ }
+ $script:htEntities = $hierarchyMapOnlyCustomData
+}
+function processManagedIdentities {
+ Write-Host 'Processing Service Principals - Managed Identities'
+ $startSPMI = Get-Date
+ $script:servicePrincipalsOfTypeManagedIdentity = $htServicePrincipals.Keys.where( { $htServicePrincipals.($_).servicePrincipalType -eq 'ManagedIdentity' } )
+ $script:servicePrincipalsOfTypeManagedIdentityCount = $servicePrincipalsOfTypeManagedIdentity.Count
+ if ($servicePrincipalsOfTypeManagedIdentityCount -gt 0) {
+ foreach ($sp in $servicePrincipalsOfTypeManagedIdentity) {
+ $hlpSp = $htServicePrincipals.($sp)
+ if ($hlpSp.alternativeNames -gt 0) {
+ foreach ($usageentry in $hlpSp.alternativeNames) {
+ if ($usageentry -like '*/providers/Microsoft.Authorization/policyAssignments/*') {
+ $script:htManagedIdentityForPolicyAssignment.($hlpSp.Id) = @{}
+ $script:htManagedIdentityForPolicyAssignment.($hlpSp.Id).policyAssignmentId = $usageentry.ToLower()
+ $script:htPolicyAssignmentManagedIdentity.($usageentry.ToLower()) = @{}
+ $script:htPolicyAssignmentManagedIdentity.($usageentry.ToLower()).miObjectId = $hlpSp.id
+ if (-not $htManagedIdentityDisplayName.($hlpSp.displayName)) {
+ $script:htManagedIdentityDisplayName.("$($hlpSp.displayName)_$($usageentry.ToLower())") = $hlpSp
+ }
+ }
+ }
+ }
+ }
+ }
+ $endSPMI = Get-Date
+ Write-Host "Processing Service Principals - Managed Identities duration: $((New-TimeSpan -Start $startSPMI -End $endSPMI).TotalMinutes) minutes ($((New-TimeSpan -Start $startSPMI -End $endSPMI).TotalSeconds) seconds)"
+}
+function processNetwork {
+ $start = Get-Date
+ Write-Host "Processing Network enrichment ($($arrayVNets.Count) Virtual Networks)"
+
+ $htVNets = @{}
+ foreach ($vnet in $arrayVNets) {
+ $htVNets.($vnet.id) = $vnet
+ }
+
+ $script:htSubnets = @{}
+ $script:arrayVirtualNetworks = [System.Collections.ArrayList]@()
+ $script:arraySubnets = [System.Collections.ArrayList]@()
+
+ foreach ($vnet in $arrayVNets) {
+
+ #region peerings
+ $vnetIdSplit = ($vnet.id -split '/')
+ $subscriptionId = $vnetIdSplit[2]
+
+ $subscriptionName = 'n/a'
+ $MGPath = 'n/a'
+ if ($htSubscriptionsMgPath.($subscriptionId)) {
+ $subHelper = $htSubscriptionsMgPath.($subscriptionId)
+ $subscriptionName = $subHelper.displayName
+ $MGPath = $subHelper.ParentNameChainDelimited
+ }
+
+ $subnetsWithPrivateEndPointsCount = 0
+ if ($vnet.properties.subnets.properties.privateEndpoints.id.Count -gt 0) {
+ $subnetsWithPrivateEndPointsCount = $vnet.properties.subnets.where({ $_.properties.privateEndpoints.id.Count -gt 0 }).Count
+ }
+
+ $subnetsWithConnectedDevicesCount = 0
+ if ($vnet.properties.subnets.properties.ipConfigurations.id.Count -gt 0) {
+ $subnetsWithConnectedDevicesCount = $vnet.properties.subnets.where({ $_.properties.ipConfigurations.id.Count -gt 0 }).Count
+ }
+
+ $vnetResourceGroup = $vnetIdSplit[4]
+ if ($vnet.properties.virtualNetworkPeerings.id.Count -gt 0) {
+ foreach ($peering in $vnet.properties.virtualNetworkPeerings) {
+ $remotevnetIdSplit = ($peering.properties.remoteVirtualNetwork.id -split '/')
+ $remotesubscriptionId = $remotevnetIdSplit[2]
+
+ $remotesubscriptionName = 'n/a'
+ $remoteMGPath = 'n/a'
+ $peeringXTenant = 'unknown'
+ if ($htSubscriptionsMgPath.($remotesubscriptionId)) {
+ $peeringXTenant = 'false'
+ $remotesubHelper = $htSubscriptionsMgPath.($remotesubscriptionId)
+ $remotesubscriptionName = $remotesubHelper.displayName
+ $remoteMGPath = $remotesubHelper.ParentNameChainDelimited
+ }
+ else {
+ if ($htUnknownTenantsForSubscription.($remotesubscriptionId)) {
+ $remoteTenantId = $htUnknownTenantsForSubscription.($remotesubscriptionId).TenantId
+ $remoteMGPath = $remoteTenantId
+ if ($remoteTenantId -eq $azApiCallConf['checkcontext'].tenant.id) {
+ $peeringXTenant = 'false'
+ }
+ else {
+ $peeringXTenant = 'true'
+ }
+ }
+ else {
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($remotesubscriptionId)?api-version=2020-01-01"
+ $remoteTenantId = AzAPICall -AzAPICallConfiguration $azApiCallConf -uri $uri -listenOn 'content' -currentTask "getTenantId for subscriptionId '$($remotesubscriptionId)'"
+ if ($remoteTenantId.id -like '/subscriptions/*') {
+ #sub actually could be resolved but not available in htSubscriptionsMgPath
+ Write-Host "SubscriptionId '$($remotesubscriptionId)' (tenantId: '$($remoteTenantId.tenantId)' (current context tenantId: '$($azapiCallConf['checkContext'].tenant.Id)')) was not captured by getSubscriptions/getEntities, however could be fully resolved with direct get call (ARM subscription API)" -ForegroundColor Magenta
+ $remoteMGPath = $remoteTenantId.tenantId
+ if ($azapiCallConf['checkContext'].tenant.Id -eq $remoteTenantId.tenantId) {
+ $peeringXTenant = 'false'
+ }
+ else {
+ $peeringXTenant = 'true'
+ }
+ }
+ else {
+ $arrayRemoteMGPath = @()
+ foreach ($remoteId in $remoteTenantId) {
+ if ($remoteId -eq 'SubscriptionNotFound Tenant unknown') {
+ $remoteMGPath = 'unknown'
+ $peeringXTenant = 'n/a'
+ }
+ else {
+ $objectGuid = [System.Guid]::empty
+ if ([System.Guid]::TryParse($remoteId, [System.Management.Automation.PSReference]$ObjectGuid)) {
+ if ($remoteId -in $MSTenantIds) {
+ $arrayRemoteMGPath += "$remoteId (MS)"
+ }
+ else {
+ $arrayRemoteMGPath += $remoteId
+ }
+ if ($remoteId -eq $azApiCallConf['checkcontext'].tenant.id) {
+ $peeringXTenant = 'false'
+ }
+ else {
+ $peeringXTenant = 'true'
+ }
+ }
+ $script:htUnknownTenantsForSubscription.($remotesubscriptionId) = @{}
+ $script:htUnknownTenantsForSubscription.($remotesubscriptionId).TenantId = $arrayRemoteMGPath -join ', '
+ $remoteMGPath += $arrayRemoteMGPath -join ', '
+ }
+ }
+ }
+ }
+ }
+
+ $remotevnetName = $remotevnetIdSplit[8]
+ $remotevnetResourceGroup = $remotevnetIdSplit[4]
+
+ if ($htVNets.($peering.properties.remoteVirtualNetwork.id)) {
+ $remotevnetState = 'existent'
+ $remoteLocation = $htVNets.($peering.properties.remoteVirtualNetwork.id).location
+ $remotePeeringsCount = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.virtualNetworkPeerings.id.Count
+ $remoteDhcpoptionsDnsservers = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.dhcpoptions.dnsservers
+ $remoteSubnetsCount = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.subnets.id.Count
+ $remoteSubnetsWithNSGCount = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.subnets.properties.networkSecurityGroup.id.Count
+ $remoteSubnetsWithRouteTable = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.subnets.properties.routeTable.id.Count
+ $remoteSubnetsWithDelegations = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.subnets.properties.delegations.id.Count
+ $remotePrivateEndPoints = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.subnets.properties.privateEndpoints.id.Count
+ $remoteSubnetsWithPrivateEndPoints = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.subnets.where({ $_.properties.privateEndpoints.id.Count -gt 0 }).Count
+ $remoteConnectedDevices = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.subnets.properties.ipConfigurations.id.Count
+ $remoteSubnetsWithConnectedDevices = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.subnets.where({ $_.properties.ipConfigurations.id.Count -gt 0 }).Count
+ $remoteDdosProtection = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.enableDdosProtection
+ $remotePeering = $htVNets.($peering.properties.remoteVirtualNetwork.id).properties.virtualNetworkPeerings.where({ $_.properties.remoteVirtualNetwork.id -eq $vnet.id })
+ if ($remotePeering.count -eq 1) {
+ $remotePeeringName = $remotePeering.name
+ $remotePeeringState = $remotePeering.Properties.peeringState
+ $remotePeeringSyncLevel = $remotePeering.Properties.peeringSyncLevel
+ $remoteAllowVirtualNetworkAccess = $remotePeering.properties.allowVirtualNetworkAccess
+ $remoteAllowForwardedTraffic = $remotePeering.properties.allowForwardedTraffic
+ $remoteAllowGatewayTransit = $remotePeering.properties.allowGatewayTransit
+ $remoteUseRemoteGateways = $remotePeering.properties.useRemoteGateways
+ $remoteDoNotVerifyRemoteGateways = $remotePeering.properties.doNotVerifyRemoteGateways
+ $remotePeerCompleteVnets = $remotePeering.properties.peerCompleteVnets
+ $remoteRouteServiceVips = $remotePeering.properties.routeServiceVips
+ }
+ else {
+ $remotePeeringName = 'n/a'
+ $remotePeeringState = 'n/a'
+ $remotePeeringSyncLevel = 'n/a'
+ $remoteAllowVirtualNetworkAccess = 'n/a'
+ $remoteAllowForwardedTraffic = 'n/a'
+ $remoteAllowGatewayTransit = 'n/a'
+ $remoteUseRemoteGateways = 'n/a'
+ $remoteDoNotVerifyRemoteGateways = 'n/a'
+ $remotePeerCompleteVnets = 'n/a'
+ $remoteRouteServiceVips = 'n/a'
+ }
+
+ }
+ else {
+ if ($getMgParentName -eq 'Tenant Root') {
+ $remotevnetState = 'non-existent'
+ }
+ else {
+ $remotevnetState = 'n/a'
+ }
+ $remoteLocation = 'n/a'
+ $remotePeeringsCount = 'n/a'
+ $remoteDhcpoptionsDnsservers = 'n/a'
+ $remoteSubnetsCount = 'n/a'
+ $remoteSubnetsWithNSGCount = 'n/a'
+ $remoteSubnetsWithRouteTable = 'n/a'
+ $remoteSubnetsWithDelegations = 'n/a'
+ $remotePrivateEndPoints = 'n/a'
+ $remoteSubnetsWithPrivateEndPoints = 'n/a'
+ $remoteConnectedDevices = 'n/a'
+ $remoteSubnetsWithConnectedDevices = 'n/a'
+ $remoteDdosProtection = 'n/a'
+ $remotePeeringName = 'n/a'
+ $remotePeeringState = 'n/a'
+ $remotePeeringSyncLevel = 'n/a'
+ $remoteAllowVirtualNetworkAccess = 'n/a'
+ $remoteAllowForwardedTraffic = 'n/a'
+ $remoteAllowGatewayTransit = 'n/a'
+ $remoteUseRemoteGateways = 'n/a'
+ $remoteDoNotVerifyRemoteGateways = 'n/a'
+ $remotePeerCompleteVnets = 'n/a'
+ $remoteRouteServiceVips = 'n/a'
+ }
+
+ $null = $script:arrayVirtualNetworks.Add([PSCustomObject]@{
+ SubscriptionName = $subscriptionName
+ Subscription = ($vnet.id -split '/')[2]
+ MGPath = $MGPath
+ VNet = $vnet.name
+ VNetId = $vnet.id
+ VNetResourceGroup = $vnetResourceGroup
+ Location = $vnet.location
+ AddressSpaceAddressPrefixes = ($vnet.properties.addressSpace.addressPrefixes -join "$CsvDelimiterOpposite ")
+ DhcpoptionsDnsservers = ($vnet.properties.dhcpoptions.dnsservers -join "$CsvDelimiterOpposite ")
+ SubnetsCount = $vnet.properties.subnets.id.Count
+ SubnetsWithNSGCount = $vnet.properties.subnets.properties.networkSecurityGroup.id.Count
+ SubnetsWithRouteTableCount = $vnet.properties.subnets.properties.routeTable.id.Count
+ SubnetsWithDelegationsCount = $vnet.properties.subnets.properties.delegations.id.Count
+ PrivateEndpointsCount = $vnet.properties.subnets.properties.privateEndpoints.id.Count
+ SubnetsWithPrivateEndPointsCount = $subnetsWithPrivateEndPointsCount
+ ConnectedDevices = $vnet.properties.subnets.properties.ipConfigurations.id.Count
+ SubnetsWithConnectedDevicesCount = $subnetsWithConnectedDevicesCount
+ DdosProtection = $vnet.properties.enableDdosProtection
+
+ PeeringsCount = $vnet.properties.virtualNetworkPeerings.id.Count
+ PeeringXTenant = $peeringXTenant
+ PeeringName = $peering.name
+ PeeringState = $peering.properties.peeringState
+ PeeringSyncLevel = $peering.properties.peeringSyncLevel
+ AllowVirtualNetworkAccess = $peering.properties.allowVirtualNetworkAccess
+ AllowForwardedTraffic = $peering.properties.allowForwardedTraffic
+ AllowGatewayTransit = $peering.properties.allowGatewayTransit
+ UseRemoteGateways = $peering.properties.useRemoteGateways
+ DoNotVerifyRemoteGateways = $peering.properties.doNotVerifyRemoteGateways
+ PeerCompleteVnets = $peering.properties.peerCompleteVnets
+ RouteServiceVips = $peering.properties.routeServiceVips
+
+ RemotePeeringsCount = $remotePeeringsCount
+ RemotePeeringName = $remotePeeringName
+ RemotePeeringState = $remotePeeringState
+ RemotePeeringSyncLevel = $remotePeeringSyncLevel
+ RemoteAllowVirtualNetworkAccess = $RemoteAllowVirtualNetworkAccess
+ RemoteAllowForwardedTraffic = $RemoteAllowForwardedTraffic
+ RemoteAllowGatewayTransit = $RemoteAllowGatewayTransit
+ RemoteUseRemoteGateways = $RemoteUseRemoteGateways
+ RemoteDoNotVerifyRemoteGateways = $RemoteDoNotVerifyRemoteGateways
+ RemotePeerCompleteVnets = $RemotePeerCompleteVnets
+ RemoteRouteServiceVips = $RemoteRouteServiceVips
+
+ RemoteSubscriptionName = $remotesubscriptionName
+ RemoteSubscription = $remotesubscriptionId
+ RemoteMGPath = $remoteMGPath -join ', '
+ RemoteVNet = $remotevnetName
+ RemoteVNetId = $peering.properties.remoteVirtualNetwork.id
+ RemoteVNetState = $remotevnetState
+ RemoteVNetResourceGroup = $remotevnetResourceGroup
+ RemoteVNetLocation = $remoteLocation
+ RemoteAddressSpaceAddressPrefixes = ($peering.properties.remoteAddressSpace.addressPrefixes -join "$CsvDelimiterOpposite ")
+ RemoteVirtualNetworkAddressSpaceAddressPrefixes = ($peering.properties.remoteVirtualNetworkAddressSpace.addressPrefixes -join "$CsvDelimiterOpposite ")
+
+ RemoteDhcpoptionsDnsservers = ($remoteDhcpoptionsDnsservers -join "$CsvDelimiterOpposite ")
+ RemoteSubnetsCount = $remoteSubnetsCount
+ RemoteSubnetsWithNSGCount = $remoteSubnetsWithNSGCount
+ RemoteSubnetsWithRouteTable = $remoteSubnetsWithRouteTable
+ RemoteSubnetsWithDelegations = $remoteSubnetsWithDelegations
+ RemotePrivateEndPoints = $remotePrivateEndPoints
+ RemoteSubnetsWithPrivateEndPoints = $remoteSubnetsWithPrivateEndPoints
+ RemoteConnectedDevices = $remoteConnectedDevices
+ RemoteSubnetsWithConnectedDevices = $remoteSubnetsWithConnectedDevices
+ RemoteDdosProtection = $remoteDdosProtection
+ })
+ }
+
+ }
+ else {
+ $null = $script:arrayVirtualNetworks.Add([PSCustomObject]@{
+ SubscriptionName = $subscriptionName
+ Subscription = ($vnet.id -split '/')[2]
+ MGPath = $MGPath
+ VNet = $vnet.name
+ VNetId = $vnet.id
+ VNetResourceGroup = $vnetResourceGroup
+ Location = $vnet.location
+
+ AddressSpaceAddressPrefixes = ($vnet.properties.addressSpace.addressPrefixes -join "$CsvDelimiterOpposite ")
+ DhcpoptionsDnsservers = ($vnet.properties.dhcpoptions.dnsservers -join "$CsvDelimiterOpposite ")
+ SubnetsCount = $vnet.properties.subnets.id.Count
+ SubnetsWithNSGCount = $vnet.properties.subnets.properties.networkSecurityGroup.id.Count
+ SubnetsWithRouteTableCount = $vnet.properties.subnets.properties.routeTable.id.Count
+ SubnetsWithDelegationsCount = $vnet.properties.subnets.properties.delegations.id.Count
+ PrivateEndpointsCount = $vnet.properties.subnets.properties.privateEndpoints.id.Count
+ SubnetsWithPrivateEndPointsCount = $subnetsWithPrivateEndPointsCount
+ ConnectedDevices = $vnet.properties.subnets.properties.ipConfigurations.id.Count
+ SubnetsWithConnectedDevicesCount = $subnetsWithConnectedDevicesCount
+ DdosProtection = $vnet.properties.enableDdosProtection
+
+ PeeringsCount = $vnet.properties.virtualNetworkPeerings.id.Count
+ PeeringXTenant = 'n/a'
+ PeeringName = ''
+ PeeringState = ''
+ PeeringSyncLevel = ''
+ AllowVirtualNetworkAccess = ''
+ AllowForwardedTraffic = ''
+ AllowGatewayTransit = ''
+ UseRemoteGateways = ''
+ DoNotVerifyRemoteGateways = ''
+ PeerCompleteVnets = ''
+ RouteServiceVips = ''
+
+ RemotePeeringsCount = ''
+ RemotePeeringName = ''
+ RemotePeeringState = ''
+ RemotePeeringSyncLevel = ''
+ RemoteAllowVirtualNetworkAccess = ''
+ RemoteAllowForwardedTraffic = ''
+ RemoteAllowGatewayTransit = ''
+ RemoteUseRemoteGateways = ''
+ RemoteDoNotVerifyRemoteGateways = ''
+ RemotePeerCompleteVnets = ''
+ RemoteRouteServiceVips = ''
+
+ RemoteSubscriptionName = ''
+ RemoteSubscription = ''
+ RemoteMGPath = ''
+ RemoteVNet = ''
+ RemoteVNetId = ''
+ RemoteVNetState = ''
+ RemoteVNetResourceGroup = ''
+ RemoteVNetLocation = ''
+ RemoteAddressSpaceAddressPrefixes = ''
+ RemoteVirtualNetworkAddressSpaceAddressPrefixes = ''
+ RemoteDhcpoptionsDnsservers = ''
+ RemoteSubnetsCount = ''
+ RemoteSubnetsWithNSGCount = ''
+ RemoteSubnetsWithRouteTable = ''
+ RemoteSubnetsWithDelegations = ''
+ RemotePrivateEndPoints = ''
+ RemoteSubnetsWithPrivateEndPoints = ''
+ RemoteConnectedDevices = ''
+ RemoteSubnetsWithConnectedDevices = ''
+ RemoteDdosProtection = ''
+ })
+ }
+ #endregion peerings
+
+ #region subnets
+
+ if ($vnet.properties.subnets.Count -gt 0) {
+ foreach ($subnet in $vnet.properties.subnets) {
+
+ $script:htSubnets.($subnet.id) = @{
+ SubscriptionName = $subscriptionName
+ Subscription = ($vnet.id -split '/')[2]
+ MGPath = $MGPath
+ VNet = $vnet.name
+ VNetId = $vnet.id
+ Location = $vnet.location
+ ResourceGroup = $vnetResourceGroup
+ }
+
+ $arrayServiceEndPoints = @()
+ if ($subnet.properties.serviceEndpoints.service.Count -gt 0) {
+ $arrayServiceEndPoints = foreach ($serviceEndpoint in $subnet.properties.serviceEndpoints) {
+ "$($serviceEndpoint.service) ($(($serviceEndpoint.locations | Sort-Object) -join ', '))"
+ }
+ }
+
+ $delegation = ''
+ if ($subnet.properties.delegations.Count -gt 0) {
+ $delegation = "$($subnet.properties.delegations.properties.serviceName) ($(($subnet.properties.delegations.properties.actions | Sort-Object) -join ', '))"
+ }
+
+ #region IP address usage
+ #https://github.com/ElanShudnow/AzureCode/blob/242b923eada55fa795b930473a50dedf14bdc409/PowerShell/AzSubnetAvailability/AzSubnetAvailability.ps1
+ # Gets the mask from the IP configuration (I.e 10.0.0.0/24, turns to just "24")
+
+ if (-not [string]::IsNullOrWhiteSpace($subnet.properties.addressPrefix)) {
+ $AddressPrefix = $subnet.properties.addressPrefix
+ $subnetNet = $AddressPrefix -replace '/.*'
+ $subnetNetOutput = $subnetNet
+ }
+
+ #ignore IPv6
+ if (-not [string]::IsNullOrWhiteSpace($subnet.properties.addressPrefixes)) {
+ $arr = foreach ($entry in $subnet.properties.addressPrefixes) {
+ if ($entry -match '^(([01]?\d?\d|2[0-4]\d|25[0-5])\.){3}([01]?\d?\d|2[0-4]\d|25[0-5])\/(\d{1}|[0-2]{1}\d{1}|3[0-2])$') {
+ $AddressPrefix = $entry
+ $AddressPrefix -replace '/.*'
+ $subnetNet = $AddressPrefix -replace '/.*'
+ }
+ else {
+ "(ignoring IPv6 $entry)"
+ }
+ }
+ $subnetNetOutput = $arr
+ }
+
+ $Mask = $AddressPrefix.substring($AddressPrefix.Length - 2, 2)
+
+ #Amount of available IP Addresses minus the 3 IPs that Azure consumes, minus net and broadcast
+ #https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets
+ switch ($Mask) {
+ '30' { $AvailableAddresses = [Math]::Pow(2, 2) - 5 }
+ '29' { $AvailableAddresses = [Math]::Pow(2, 3) - 5 }
+ '28' { $AvailableAddresses = [Math]::Pow(2, 4) - 5 }
+ '27' { $AvailableAddresses = [Math]::Pow(2, 5) - 5 }
+ '26' { $AvailableAddresses = [Math]::Pow(2, 6) - 5 }
+ '25' { $AvailableAddresses = [Math]::Pow(2, 7) - 5 }
+ '24' { $AvailableAddresses = [Math]::Pow(2, 8) - 5 }
+ '23' { $AvailableAddresses = [Math]::Pow(2, 9) - 5 }
+ '22' { $AvailableAddresses = [Math]::Pow(2, 10) - 5 }
+ '21' { $AvailableAddresses = [Math]::Pow(2, 11) - 5 }
+ '20' { $AvailableAddresses = [Math]::Pow(2, 12) - 5 }
+ '19' { $AvailableAddresses = [Math]::Pow(2, 13) - 5 }
+ '18' { $AvailableAddresses = [Math]::Pow(2, 14) - 5 }
+ '17' { $AvailableAddresses = [Math]::Pow(2, 15) - 5 }
+ '16' { $AvailableAddresses = [Math]::Pow(2, 16) - 5 }
+ '15' { $AvailableAddresses = [Math]::Pow(2, 17) - 5 }
+ '14' { $AvailableAddresses = [Math]::Pow(2, 18) - 5 }
+ '13' { $AvailableAddresses = [Math]::Pow(2, 19) - 5 }
+ '12' { $AvailableAddresses = [Math]::Pow(2, 20) - 5 }
+ '11' { $AvailableAddresses = [Math]::Pow(2, 21) - 5 }
+ '10' { $AvailableAddresses = [Math]::Pow(2, 22) - 5 }
+ '9' { $AvailableAddresses = [Math]::Pow(2, 23) - 5 }
+ '8' { $AvailableAddresses = [Math]::Pow(2, 24) - 5 }
+ }
+
+ $IPsLeft = $AvailableAddresses - $subnet.properties.ipConfigurations.Count
+ $PercentIPsUsed = [math]::Round((($subnet.properties.ipConfigurations.Count / $AvailableAddresses) * 100), 1)
+ $subnetIPAddressUsageCritical = $false
+ if ($PercentIPsUsed -gt $NetworkSubnetIPAddressUsageCriticalPercentage) {
+ $subnetIPAddressUsageCritical = $true
+ }
+
+ #endregion IP address usage
+
+ $subnetPrefix = $AddressPrefix -replace '.*/'
+
+ $subnetmask = ([IPAddress]"$([system.convert]::ToInt64(('1'*$subnetPrefix).PadRight(32,'0'),2))").IPAddressToString
+ $IPBits = [int[]]$subnetNet.Split('.')
+ $MaskBits = [int[]]$subnetmask.Split('.')
+ $NetworkIDBits = 0..3 | ForEach-Object { $IPBits[$_] -band $MaskBits[$_] }
+ $Broadcast = (0..3 | ForEach-Object { $NetworkIDBits[$_] + ($MaskBits[$_] -bxor 255) }) -join '.'
+ $Range = "$subnetNet - $Broadcast"
+
+ $null = $script:arraySubnets.Add([PSCustomObject]@{
+ SubscriptionName = $subscriptionName
+ Subscription = ($vnet.id -split '/')[2]
+ MGPath = $MGPath
+ VNet = $vnet.name
+ VNetId = $vnet.id
+ VNetResourceGroup = $vnetResourceGroup
+ Location = $vnet.location
+ SubnetName = $subnet.name
+ SubnetId = $subnet.id
+ SubnetNet = $subnetNetOutput -join "$CsvDelimiterOpposite "
+ SubnetPrefix = $subnetPrefix
+ Subnetmask = $subnetmask
+ Range = $Range
+ ConnectedDevices = $subnet.properties.ipConfigurations.Count
+ AvailableIPAddresses = $IPsLeft
+ UsedIPAddressesPercent = "$PercentIPsUsed %"
+ SubnetIPAddressUsageCritical = $subnetIPAddressUsageCritical
+ PrivateEndpointNetworkPolicies = $subnet.properties.privateEndpointNetworkPolicies
+ PrivateLinkServiceNetworkPolicies = $subnet.properties.privateLinkServiceNetworkPolicies
+ ServiceEndpointsCount = $subnet.properties.serviceEndpoints.service.Count
+ ServiceEndpoints = $arrayServiceEndPoints -join ', '
+ Delegation = $delegation
+ NetworkSecurityGroup = $subnet.properties.networkSecurityGroup.id
+ RouteTable = $subnet.properties.routeTable
+ NatGateway = ''
+ PrivateEndpoints = $subnet.properties.privateEndpoints.Count
+ })
+ }
+ }
+ #endregion subnets
+ }
+
+ $end = Get-Date
+ Write-Host " Processing Network enrichment duration: $((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds"
+}
+function processPrivateEndpoints {
+ $start = Get-Date
+ Write-Host 'Processing Private Endpoints enrichment'
+
+ $script:arrayPrivateEndpointsEnriched = [System.Collections.ArrayList]@()
+
+ if ($arrayPrivateEndPointsFromResourceProperties.Count -gt 0) {
+ $privateEndPointsFromResourcePropertiesToProcess = ($arrayPrivateEndPointsFromResourceProperties.where({ $arrayPrivateEndPoints.id -notcontains $_.privateEndpointConnection.Properties.privateEndpoint.id }))
+ $privateEndPointsFromResourcePropertiesToProcessCount = $privateEndPointsFromResourcePropertiesToProcess.Count
+ Write-Host " Processing Private Endpoints enrichment for $privateEndPointsFromResourcePropertiesToProcessCount Private Endpoint(s) where the Private Endpoint was not returned from the PE API endpoint but from a resource property"
+ if ($privateEndPointsFromResourcePropertiesToProcessCount -gt 0) {
+ foreach ($entry in $privateEndPointsFromResourcePropertiesToProcess) {
+ $peResIdSplit = $entry.privateEndpointConnection.Properties.privateEndpoint.id -split '/'
+ $crossSubscriptionPE = 'n/a'
+ $peSubscriptionId = $peResIdSplit[2]
+ if ($peSubscriptionId -ne $entry.ResourceSubscriptionId) {
+ $crossSubscriptionPE = $true
+ }
+ else {
+ $crossSubscriptionPE = $false
+ }
+
+ $peMGPath = 'n/a'
+ $peXTenant = 'unknown'
+ if ($htSubscriptionsMgPath.($peSubscriptionId)) {
+ $peMGPath = $htSubscriptionsMgPath.($peSubscriptionId).pathDelimited
+ $peXTenant = $false
+ }
+ elseif ($htUnknownTenantsForSubscription.($peSubscriptionId)) {
+ $remoteTenantId = $htUnknownTenantsForSubscription.($peSubscriptionId).TenantId
+ $peMGPath = $remoteTenantId
+ if ($remoteTenantId -eq $azApiCallConf['checkcontext'].tenant.id) {
+ $peXTenant = $false
+ }
+ else {
+ $peXTenant = $true
+ }
+ }
+ else {
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($peSubscriptionId)?api-version=2020-01-01"
+ $remoteTenantId = AzAPICall -AzAPICallConfiguration $azApiCallConf -uri $uri -listenOn 'content' -currentTask "getTenantId for subscriptionId '$($peSubscriptionId)'"
+ $arrayRemoteMGPath = @()
+ foreach ($remoteId in $remoteTenantId) {
+ $objectGuid = [System.Guid]::empty
+ if ([System.Guid]::TryParse($remoteId, [System.Management.Automation.PSReference]$ObjectGuid)) {
+ if ($remoteId -in $MSTenantIds) {
+ $arrayRemoteMGPath += "$remoteId (MS)"
+ }
+ else {
+ $arrayRemoteMGPath += $remoteId
+ }
+ if ($remoteId -eq $azApiCallConf['checkcontext'].tenant.id) {
+ $peXTenant = $false
+ }
+ else {
+ $peXTenant = $true
+ }
+ }
+ $script:htUnknownTenantsForSubscription.($peSubscriptionId) = @{}
+ $script:htUnknownTenantsForSubscription.($peSubscriptionId).TenantId = $arrayRemoteMGPath -join ', '
+ $peMGPath = $arrayRemoteMGPath -join ', '
+ }
+ }
+
+ $null = $script:arrayPrivateEndpointsEnriched.Add([PSCustomObject]@{
+ PEName = $entry.privateEndpointConnection.name
+ PEId = $entry.privateEndpointConnection.Properties.privateEndpoint.id
+ PELocation = 'n/a'
+ PEResourceGroup = $peResIdSplit[4]
+ PESubscriptionName = 'n/a'
+ PESubscription = $peSubscriptionId
+ PEMGPath = $peMGPath
+ PEConnectionType = 'n/a'
+ PEConnectionState = $entry.privateEndpointConnection.Properties.privateLinkServiceConnectionState.status
+ CrossSubscriptionPE = $crossSubscriptionPE
+ CrossTenantPE = $peXTenant
+
+ Resource = $entry.ResourceName
+ ResourceType = $entry.ResourceType
+ ResourceId = $entry.ResourceId
+ TargetSubresource = 'n/a'
+ NICName = 'n/a'
+ FQDN = 'n/a'
+ ipAddresses = 'n/a'
+ ResourceResourceGroup = $entry.ResourceResourceGroup
+ ResourceSubscriptionName = $entry.ResourceSubscriptionName
+ ResourceSubscriptionId = $entry.ResourceSubscriptionId
+ ResourceMGPath = $entry.ResourceMGPath
+ ResourceCrossTenant = 'false'
+
+ Subnet = 'n/a'
+ SubnetId = 'n/a'
+ SubnetVNet = 'n/a'
+ SubnetVNetId = 'n/a'
+ SubnetVNetLocation = 'n/a'
+ SubnetVNetResourceGroup = 'n/a'
+ SubnetSubscriptionName = 'n/a'
+ SubnetSubscription = 'n/a'
+ SubnetMGPath = 'n/a'
+ })
+ }
+ }
+ }
+
+ Write-Host " Processing Private Endpoints enrichment for $($arrayPrivateEndPoints.Count) Private Endpoint(s) where the Private Endpoint was returned from the PE API endpoint"
+ $htVPrivateEndPoints = @{}
+ foreach ($pe in $arrayPrivateEndPoints) {
+ $htVPrivateEndPoints.($pe.id) = $pe
+ }
+
+ $htVPrivateEndPoints = @{}
+ foreach ($pe in $arrayPrivateEndPoints) {
+ $htVPrivateEndPoints.($pe.id) = $pe
+ }
+
+ foreach ($pe in $arrayPrivateEndPoints) {
+
+ $peIdSplit = ($pe.id -split '/')
+ $subscriptionId = $peIdSplit[2]
+ $resourceGroup = $peIdSplit[4]
+
+ $subscriptionName = 'n/a'
+ $MGPath = 'n/a'
+ if ($htSubscriptionsMgPath.($subscriptionId)) {
+ $subHelper = $htSubscriptionsMgPath.($subscriptionId)
+ $subscriptionName = $subHelper.displayName
+ $MGPath = $subHelper.ParentNameChainDelimited
+ }
+
+ $SubnetSubscriptionName = 'n/a'
+ $SubnetSubscription = 'n/a'
+ $SubnetMGPath = 'n/a'
+ $SubnetVNet = 'n/a'
+ $SubnetVNetId = 'n/a'
+ $SubnetVNetLocation = 'n/a'
+ $SubnetVNetResourceGroup = 'n/a'
+ if ($htSubnets.($pe.properties.subnet.id)) {
+ $hlper = $htSubnets.($pe.properties.subnet.id)
+ $SubnetSubscriptionName = $hlper.SubscriptionName
+ $SubnetSubscription = $hlper.Subscription
+ $SubnetMGPath = $hlper.MGPath
+ $SubnetVNet = $hlper.VNet
+ $SubnetVNetId = $hlper.VNetId
+ $SubnetVNetLocation = $hlper.Location
+ $SubnetVNetResourceGroup = $hlper.ResourceGroup
+ }
+
+ $resourceSplit = $false
+ if ($pe.properties.privateLinkServiceConnections.Count -gt 0) {
+ $resourceId = $pe.properties.privateLinkServiceConnections.properties.privateLinkServiceId
+ $targetSubresource = $pe.properties.privateLinkServiceConnections.properties.groupIds -join ', '
+ $resourceSplit = $pe.properties.privateLinkServiceConnections.properties.privateLinkServiceId -split '/'
+ $peConnectionType = 'direct'
+ $peConnectionState = $pe.properties.privateLinkServiceConnections.properties.privateLinkServiceConnectionState.status
+ }
+ if ($pe.properties.manualPrivateLinkServiceConnections.Count -gt 0) {
+ $resourceId = $pe.properties.manualPrivateLinkServiceConnections.properties.privateLinkServiceId
+ $targetSubresource = $pe.properties.manualPrivateLinkServiceConnections.properties.groupIds -join ', '
+ $resourceSplit = $pe.properties.manualPrivateLinkServiceConnections.properties.privateLinkServiceId -split '/'
+ $peConnectionType = 'manual'
+ $peConnectionState = $pe.properties.manualPrivateLinkServiceConnections.properties.privateLinkServiceConnectionState.status
+ }
+
+ $resourceSubscriptionId = 'n/a'
+ $resource = 'n/a'
+ $resourceType = 'n/a'
+ $resourceResourceGroup = 'n/a'
+ $resourceSubscriptionName = 'n/a'
+ $resourceMGPath = 'n/a'
+ $crossSubscriptionPE = 'n/a'
+ $resourceXTenant = 'unknown'
+
+ if ($resourceSplit) {
+ $ObjectGuid = [System.Guid]::empty
+ if ([System.Guid]::TryParse($resourceSplit[2], [System.Management.Automation.PSReference]$ObjectGuid)) {
+ $resourceSubscriptionId = $resourceSplit[2]
+ $resource = $resourceSplit[8]
+ $resourceType = "$($resourceSplit[6])/$($resourceSplit[7])"
+ $resourceResourceGroup = $resourceSplit[4]
+
+ if ($htSubscriptionsMgPath.($resourceSubscriptionId)) {
+ $subHelper = $htSubscriptionsMgPath.($resourceSubscriptionId)
+ $resourceSubscriptionName = $subHelper.displayName
+ $resourceMGPath = $subHelper.ParentNameChainDelimited
+ $resourceXTenant = $false
+ }
+ else {
+ if ($htUnknownTenantsForSubscription.($resourceSubscriptionId)) {
+ $remoteTenantId = $htUnknownTenantsForSubscription.($resourceSubscriptionId).TenantId
+ $resourceMGPath = $remoteTenantId
+ if ($remoteTenantId -eq $azApiCallConf['checkcontext'].tenant.id) {
+ $resourceXTenant = $false
+ }
+ else {
+ $resourceXTenant = $true
+ }
+ }
+ else {
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($resourceSubscriptionId)?api-version=2020-01-01"
+ $remoteTenantId = AzAPICall -AzAPICallConfiguration $azApiCallConf -uri $uri -listenOn 'content' -currentTask "getTenantId for subscriptionId '$($resourceSubscriptionId)'"
+ $arrayRemoteMGPath = @()
+ foreach ($remoteId in $remoteTenantId) {
+ $objectGuid = [System.Guid]::empty
+ if ([System.Guid]::TryParse($remoteId, [System.Management.Automation.PSReference]$ObjectGuid)) {
+ if ($remoteId -in $MSTenantIds) {
+ $arrayRemoteMGPath += "$remoteId (MS)"
+ }
+ else {
+ $arrayRemoteMGPath += $remoteId
+ }
+ if ($remoteId -eq $azApiCallConf['checkcontext'].tenant.id) {
+ $resourceXTenant = $false
+ }
+ else {
+ $resourceXTenant = $true
+ }
+ }
+ $script:htUnknownTenantsForSubscription.($resourceSubscriptionId) = @{}
+ $script:htUnknownTenantsForSubscription.($resourceSubscriptionId).TenantId = $arrayRemoteMGPath -join ', '
+ $resourceMGPath = $arrayRemoteMGPath -join ', '
+ }
+ }
+ }
+
+ if ($SubnetSubscription -eq $resourceSubscriptionId) {
+ $crossSubscriptionPE = $false
+ }
+ else {
+ $crossSubscriptionPE = $true
+ }
+
+ $crossTenantPE = $false
+ if ($resourceXTenant -eq $true) {
+ $crossTenantPE = $true
+ }
+
+ }
+ }
+
+ $null = $script:arrayPrivateEndpointsEnriched.Add([PSCustomObject]@{
+ PEName = $pe.name
+ PEId = $pe.id
+ PELocation = $pe.location
+ PEResourceGroup = $resourceGroup
+ PESubscriptionName = $subscriptionName
+ PESubscription = ($pe.id -split '/')[2]
+ PEMGPath = $MGPath
+ PEConnectionType = $peConnectionType
+ PEConnectionState = $peConnectionState
+ CrossSubscriptionPE = $crossSubscriptionPE
+ CrossTenantPE = $crossTenantPE
+
+ Resource = $resource
+ ResourceType = $resourceType
+ ResourceId = $resourceId
+ TargetSubresource = $targetSubresource -join ', '
+ NICName = $pe.properties.customNetworkInterfaceName
+ FQDN = $pe.properties.customDnsConfigs.fqdn -join ', '
+ ipAddresses = $pe.properties.customDnsConfigs.ipAddresses -join ', '
+ ResourceResourceGroup = $resourceResourceGroup
+ ResourceSubscriptionName = $resourceSubscriptionName
+ ResourceSubscriptionId = $resourceSubscriptionId
+ ResourceMGPath = $resourceMGPath
+ ResourceCrossTenant = $resourceXTenant
+
+ Subnet = $pe.properties.subnet.id -replace '.*/'
+ SubnetId = $pe.properties.subnet.id
+ SubnetVNet = $SubnetVNet
+ SubnetVNetId = $SubnetVNetId
+ SubnetVNetLocation = $SubnetVNetLocation
+ SubnetVNetResourceGroup = $SubnetVNetResourceGroup
+ SubnetSubscriptionName = $SubnetSubscriptionName
+ SubnetSubscription = $SubnetSubscription
+ SubnetMGPath = $SubnetMGPath
+ })
+ }
+
+
+ $end = Get-Date
+ Write-Host " Processing Private Endpoints enrichment duration: $((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds"
+}
+function processScopeInsightsMgOrSub($mgOrSub, $mgChild, $subscriptionId, $subscriptionsMgId) {
+ $script:scopescnter++
+ $htmlScopeInsights = $null
+ $htmlScopeInsights = [System.Text.StringBuilder]::new()
+ #region ScopeInsightsBaseCollection
+ if ($mgOrSub -eq 'mg') {
+ #$startScopeInsightsPreQueryMg = Get-Date
+ #BLUEPRINT
+ $blueprintReleatedQuery = $blueprintBaseQuery.where( { $_.MgId -eq $mgChild -and [String]::IsNullOrEmpty($_.SubscriptionId) -and [String]::IsNullOrEmpty($_.BlueprintAssignmentId) } )
+ $blueprintsScoped = $blueprintReleatedQuery
+ $blueprintsScopedCount = ($blueprintsScoped).count
+ #Resources
+ $mgAllChildSubscriptions = [System.Collections.ArrayList]@()
+ $mgAllChildSubscriptions = foreach ($entry in $htSubscriptionsMgPath.keys) {
+ if (($htSubscriptionsMgPath.($entry).ParentNameChain) -contains $mgchild) {
+ $entry
+ }
+ }
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ $resourcesAllChildSubscriptions = [System.Collections.ArrayList]@()
+ foreach ($mgAllChildSubscription in $mgAllChildSubscriptions) {
+ foreach ($resource in ($resourcesAllGroupedBySubcriptionId.where( { $_.name -eq $mgAllChildSubscription } )).group | Sort-Object -Property type, location) {
+ $null = $resourcesAllChildSubscriptions.Add($resource)
+ }
+
+ }
+ $resourcesAllChildSubscriptionsArray = [System.Collections.ArrayList]@()
+ $grp = $resourcesAllChildSubscriptions | Group-Object -Property type, location
+ foreach ($resLoc in $grp) {
+ $cnt = 0
+ $ResoureTypeAndLocation = $resLoc.Name.split(',')
+ $resLoc.Group.count_ | ForEach-Object { $cnt += $_ }
+ $null = $resourcesAllChildSubscriptionsArray.Add([PSCustomObject]@{
+ ResourceType = $ResoureTypeAndLocation[0]
+ Location = $ResoureTypeAndLocation[1]
+ ResourceCount = $cnt
+ })
+ }
+ $resourcesAllChildSubscriptions.count_ | ForEach-Object { $resourcesAllChildSubscriptionTotal += $_ }
+ $resourcesAllChildSubscriptionResourceTypeCount = (($resourcesAllChildSubscriptions | Sort-Object -Property type -Unique) | Measure-Object).count
+ $resourcesAllChildSubscriptionLocationCount = (($resourcesAllChildSubscriptions | Sort-Object -Property location -Unique) | Measure-Object).count
+ }
+ #childrenMgInfo
+ $mgAllChildMgs = [System.Collections.ArrayList]@()
+ $mgAllChildMgs = foreach ($entry in $htManagementGroupsMgPath.keys) {
+ if (($htManagementGroupsMgPath.($entry).path) -contains $mgchild) {
+ $entry
+ }
+ }
+
+ $arrayPolicyAssignmentsEnrichedForThisManagementGroup = ($arrayPolicyAssignmentsEnrichedGroupedByManagementGroup.where( { $_.name -eq $mgChild } )).group
+ $arrayPolicyAssignmentsEnrichedForThisManagementGroupGroupedByPolicyVariant = $arrayPolicyAssignmentsEnrichedForThisManagementGroup | Group-Object -Property PolicyVariant
+ $arrayPolicyAssignmentsEnrichedForThisManagementGroupVariantPolicy = ($arrayPolicyAssignmentsEnrichedForThisManagementGroupGroupedByPolicyVariant.where( { $_.name -eq 'Policy' } )).group
+ $arrayPolicyAssignmentsEnrichedForThisManagementGroupVariantPolicySet = ($arrayPolicyAssignmentsEnrichedForThisManagementGroupGroupedByPolicyVariant.where( { $_.name -eq 'PolicySet' } )).group
+
+ if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) {
+ if ([string]::IsNullOrEmpty(($htMgASCSecureScore).($mgChild).SecureScore) -or [string]::IsNullOrWhiteSpace(($htMgASCSecureScore).($mgChild).SecureScore)) {
+ $managementGroupASCPoints = 'n/a'
+ }
+ else {
+ $managementGroupASCPoints = ($htMgASCSecureScore).($mgChild).SecureScore
+ }
+ }
+ else {
+ $managementGroupASCPoints = "excluded (-NoMDfCSecureScore $($azAPICallConf['htParameters'].NoMDfCSecureScore))"
+ }
+
+ $cssClass = 'mgDetailsTable'
+
+ #$endScopeInsightsPreQueryMg = Get-Date
+ #Write-Host " ScopeInsights MG PreQuery processing duration: $((NEW-TIMESPAN -Start $startScopeInsightsPreQueryMg -End $endScopeInsightsPreQueryMg).TotalSeconds) seconds"
+ }
+ if ($mgOrSub -eq 'sub') {
+ #$startScopeInsightsPreQuerySub = Get-Date
+ #BLUEPRINT
+ $blueprintReleatedQuery = $blueprintBaseQuery.where( { $_.SubscriptionId -eq $subscriptionId -and -not [String]::IsNullOrEmpty($_.BlueprintName) } )
+ $blueprintsAssigned = $blueprintReleatedQuery.where( { -not [String]::IsNullOrEmpty($_.BlueprintAssignmentId) } )
+ $blueprintsAssignedCount = ($blueprintsAssigned).count
+ $blueprintsScoped = $blueprintReleatedQuery.where( { $_.BlueprintScoped -eq "/subscriptions/$subscriptionId" -and [String]::IsNullOrEmpty($_.BlueprintAssignmentId) } )
+ $blueprintsScopedCount = ($blueprintsScoped).count
+ #SubscriptionDetails
+ $subPath = $htSubscriptionsMgPath.($subscriptionId).pathDelimited
+ $subscriptionDetailsReleatedQuery = $htSubDetails.($subscriptionId).details
+ $subscriptionState = ($subscriptionDetailsReleatedQuery).SubscriptionState
+ $subscriptionQuotaId = ($subscriptionDetailsReleatedQuery).SubscriptionQuotaId
+ $subscriptionResourceGroupsCount = ($resourceGroupsAll.where( { $_.subscriptionId -eq $subscriptionId } )).count_
+ if (-not $subscriptionResourceGroupsCount) {
+ $subscriptionResourceGroupsCount = 0
+ }
+ $subscriptionASCPoints = ($subscriptionDetailsReleatedQuery).SubscriptionASCSecureScore
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #Resources
+ $resourcesSubscription = [System.Collections.ArrayList]@()
+ foreach ($resource in ($resourcesAllGroupedBySubcriptionId.where( { $_.name -eq $subscriptionId } )).group | Sort-Object -Property type, location) {
+ $null = $resourcesSubscription.Add($resource)
+ }
+
+ $resourcesSubscriptionTotal = 0
+ $resourcesSubscription.count_ | ForEach-Object { $resourcesSubscriptionTotal += $_ }
+ $resourcesSubscriptionResourceTypeCount = (($resourcesSubscription | Sort-Object -Property type -Unique)).count
+ $resourcesSubscriptionLocationCount = (($resourcesSubscription | Sort-Object -Property location -Unique)).count
+ }
+
+ $arrayPolicyAssignmentsEnrichedForThisSubscription = ($arrayPolicyAssignmentsEnrichedGroupedBySubscription.where( { $_.name -eq $subscriptionId } )).group
+ $arrayPolicyAssignmentsEnrichedForThisSubscriptionGroupedByPolicyVariant = $arrayPolicyAssignmentsEnrichedForThisSubscription | Group-Object -Property PolicyVariant
+ $arrayPolicyAssignmentsEnrichedForThisSubscriptionVariantPolicy = ($arrayPolicyAssignmentsEnrichedForThisSubscriptionGroupedByPolicyVariant.where( { $_.name -eq 'Policy' } )).group
+ $arrayPolicyAssignmentsEnrichedForThisSubscriptionVariantPolicySet = ($arrayPolicyAssignmentsEnrichedForThisSubscriptionGroupedByPolicyVariant.where( { $_.name -eq 'PolicySet' } )).group
+
+ $arrayDefenderPlansSubscription = $defenderPlansGroupedBySub.where( { $_.Name -like "*$($subscriptionId)*" } )
+
+ $arrayUserAssignedIdentities4ResourcesSubscription = $arrayUserAssignedIdentities4Resources.where( { $_.resourceSubscriptionId -eq $subscriptionId -or $_.miSubscriptionId -eq $subscriptionId } )
+ $arrayUserAssignedIdentities4ResourcesSubscriptionCount = $arrayUserAssignedIdentities4ResourcesSubscription.Count
+
+ if ($subFeaturesGroupedBySubscription) {
+ $subscriptionFeatures = $subFeaturesGroupedBySubscription.where({ $_.name -eq $subscriptionId })
+ }
+
+ $cssClass = 'subDetailsTable'
+
+ #$endScopeInsightsPreQuerySub = Get-Date
+ #Write-Host " ScopeInsights SUB PreQuery processing duration: $((NEW-TIMESPAN -Start $startScopeInsightsPreQuerySub -End $endScopeInsightsPreQuerySub).TotalSeconds) seconds"
+ }
+ #endregion ScopeInsightsBaseCollection
+
+ if ($mgOrSub -eq 'sub') {
+
+ if ($htDefenderEmailContacts.($subscriptionDetailsReleatedQuery.subscriptionId)) {
+ $hlpDefenderEmailContacts = $htDefenderEmailContacts.($subscriptionDetailsReleatedQuery.subscriptionId)
+ $MDfCEmailNotificationsState = $hlpDefenderEmailContacts.alertNotificationsState
+ $MDfCEmailNotificationsSeverity = $hlpDefenderEmailContacts.alertNotificationsminimalSeverity
+ $MDfCEmailNotificationsRoles = $hlpDefenderEmailContacts.roles
+ $MDfCEmailNotificationsEmails = $hlpDefenderEmailContacts.emails
+ }
+ else {
+ $MDfCEmailNotificationsState = ''
+ $MDfCEmailNotificationsSeverity = ''
+ $MDfCEmailNotificationsRoles = ''
+ $MDfCEmailNotificationsEmails = ''
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@"
+Subscription Name: $($subscriptionDetailsReleatedQuery.subscription -replace '<', '<' -replace '>', '>')
+Subscription Id: $($subscriptionDetailsReleatedQuery.subscriptionId)
+Subscription Path: $subPath
+State: $subscriptionState
+QuotaId: $subscriptionQuotaId
+ Microsoft Defender for Cloud Secure Score: $subscriptionASCPoints Video , Blog , docs
+ Microsoft Defender for Cloud 'Email notifications' state: $MDfCEmailNotificationsState
+ Microsoft Defender for Cloud 'Email notifications' severity: $MDfCEmailNotificationsSeverity
+ Microsoft Defender for Cloud 'Email notifications' roles: $MDfCEmailNotificationsRoles
+ Microsoft Defender for Cloud 'Email notifications' emails: $MDfCEmailNotificationsEmails
+
+"@)
+
+ #region ScopeInsightsDefenderPlans
+ if ($arrayDefenderPlansSubscription) {
+
+ $defenderPlanSubscriptionDeprecatedContainerRegistry = $false
+ $defenderPlanSubscriptionDeprecatedKubernetesService = $false
+
+ $containerRegistryStandardCount = ($arrayDefenderPlansSubscription.Group.where( { $_.defenderPlan -eq 'ContainerRegistry' -and $_.defenderPlanTier -eq 'Standard' } )).Count
+ $kubernetesServiceStandardCount = ($arrayDefenderPlansSubscription.Group.where( { $_.defenderPlan -eq 'KubernetesService' -and $_.defenderPlanTier -eq 'Standard' } )).Count
+ if ($containerRegistryStandardCount -gt 0) {
+ $defenderPlanSubscriptionDeprecatedContainerRegistry = $true
+ }
+ if ($kubernetesServiceStandardCount -gt 0) {
+ $defenderPlanSubscriptionDeprecatedKubernetesService = $true
+ }
+
+ $defenderCapabilitiesSubscription = ($arrayDefenderPlansSubscription.group.defenderPlan | Sort-Object -Unique)
+ $tfCount = 1
+ $htmlTableId = "ScopeInsights_DefenderPlans_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ Microsoft Defender for Cloud plans docs
+
+"@)
+
+ if ($defenderPlanSubscriptionDeprecatedContainerRegistry) {
+ [void]$htmlScopeInsights.AppendLine(@'
+
Using deprecated plan 'Container registries'
docs
+'@)
+ }
+ if ($defenderPlanSubscriptionDeprecatedKubernetesService) {
+ [void]$htmlScopeInsights.AppendLine(@'
+
Using deprecated plan 'Kubernetes'
docs
+'@)
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@"
+
Download CSV
semicolon |
comma
+
+
+
+Plan
+Tier
+
+
+
+
+"@)
+
+ foreach ($plan in $arrayDefenderPlansSubscription.Group | Sort-Object -Property defenderPlan) {
+ if (($plan.defenderPlan -eq 'ContainerRegistry' -and $plan.defenderPlanTier -eq 'Standard') -or ($plan.defenderPlan -eq 'KubernetesService' -and $plan.defenderPlanTier -eq 'Standard')) {
+ $thisDefenderPlan = " $($plan.defenderPlan)"
+ }
+ else {
+ $thisDefenderPlan = $plan.defenderPlan
+ }
+ [void]$htmlScopeInsights.AppendLine(@"
+
+ $($thisDefenderPlan)
+ $($plan.defenderPlanTier)
+
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ $subscriptionSkippedMDfC = $arrayDefenderPlansSubscriptionsSkipped.where( { $_.subscriptionId -eq $subscriptionId } )
+ if ($subscriptionSkippedMDfC.Count -gt 0) {
+ if ($subscriptionSkippedMDfC.reason -eq 'SubScriptionNotRegistered') {
+ [void]$htmlScopeInsights.AppendLine(@"
+ Microsoft Defender for Cloud plans - Subscription skipped ($($subscriptionSkippedMDfC.reason)) (ResourceProvider: Microsoft.Security) docs
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ Microsoft Defender for Cloud plans - Subscription skipped ($($subscriptionSkippedMDfC.reason))
+"@)
+ }
+
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Microsoft Defender for Cloud plans docs
+'@)
+ }
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsDefenderPlans
+
+ #region ScopeInsightsDiganosticsSubscription
+ if (($htDiagnosticSettingsMgSub).sub.($subscriptionId)) {
+ $diagnosticsSubCount = (($htDiagnosticSettingsMgSub).sub.($subscriptionId).Values.Count)
+ $tfCount = $diagnosticsSubCount
+ $htmlTableId = "ScopeInsights_DiagnosticsSub_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $diagnosticsSubCount Subscription Diagnostic settings
+
+
Download CSV
semicolon |
comma
+
+
+
+Diagnostic setting
+Target
+Target Id
+"@)
+ foreach ($logCategory in $diagnosticSettingsSubCategories) {
+ [void]$htmlScopeInsights.AppendLine(@"
+$logCategory
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+
+'@)
+ $htmlScopeInsightsDiagnosticsSub = $null
+ $htmlScopeInsightsDiagnosticsSub = foreach ($entry in ($htDiagnosticSettingsMgSub).sub.($subscriptionId).keys | Sort-Object) {
+ foreach ($diagset in ($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.keys | Sort-Object) {
+ @"
+
+$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticSettingName)
+$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticTargetType)
+$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticTargetId)
+"@
+ foreach ($logCategory in $diagnosticSettingsSubCategories) {
+ if (($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticCategoriesHt.($logCategory)) {
+ @"
+$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticCategoriesHt.($logCategory))
+"@
+ }
+ else {
+ @'
+n/a
+'@
+ }
+ }
+ @'
+
+'@
+ }
+ }
+
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsSub)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Subscription Diagnostic settings docs
+'@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsDiganosticsSubscription
+
+ #Tags
+ #region ScopeInsightsTags
+ $tagsSubscriptionCount = ($htSubscriptionTags.$subscriptionId.Keys).count
+ if ($tagsSubscriptionCount -gt 0) {
+ $tfCount = $tagsSubscriptionCount
+ $htmlTableId = "ScopeInsights_Tags_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+
+ $tagsSubscriptionCount Subscription Tags | Limit: ($tagsSubscriptionCount/$LimitTagsSubscription)
+
+
Download CSV
semicolon |
comma
+
+
+
+Tag Name
+Tag Value
+
+
+
+"@)
+ $htmlScopeInsightsTags = $null
+ $htmlScopeInsightsTags = foreach ($tag in (($htSubscriptionTags).($subscriptionId)).keys | Sort-Object) {
+ @"
+
+$tag
+$($htSubscriptionTags.$subscriptionId[$tag])
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTags)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $tagsSubscriptionCount Subscription Tags
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsTags
+
+ #TagNameUsage
+ #region ScopeInsightsTagNameUsage
+ $arrayTagListSubscription = [System.Collections.ArrayList]@()
+ foreach ($tagScope in $htSubscriptionTagList.($subscriptionId).keys) {
+ foreach ($tagScopeTagName in $htSubscriptionTagList.($subscriptionId).$tagScope.keys) {
+ $null = $arrayTagListSubscription.Add([PSCustomObject]@{
+ Scope = $tagScope
+ TagName = ($tagScopeTagName)
+ TagCount = $htSubscriptionTagList.($subscriptionId).($tagScope).($tagScopeTagName)
+ })
+ }
+ }
+ $tagsUsageCount = ($arrayTagListSubscription).Count
+
+ if ($tagsUsageCount -gt 0) {
+ $tagNamesUniqueCount = ($arrayTagListSubscription | Sort-Object -Property TagName -Unique).Count
+ $tagNamesUsedInScopes = ($arrayTagListSubscription | Sort-Object -Property Scope -Unique).scope -join "$($CsvDelimiterOpposite) "
+ $tfCount = $tagsUsageCount
+ $htmlTableId = "ScopeInsights_TagNameUsage_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+
+ Tag Name Usage ($tagNamesUniqueCount unique Tag Names applied at $($tagNamesUsedInScopes)
+
+
Resource naming and tagging decision guide docs
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+TagName
+Count
+
+
+
+"@)
+ $htmlScopeInsightsTagsUsage = $null
+ $htmlScopeInsightsTagsUsage = foreach ($tagEntry in $arrayTagListSubscription | Sort-Object Scope, TagName -CaseSensitive) {
+ @"
+
+$($tagEntry.Scope)
+$($tagEntry.TagName)
+$($tagEntry.TagCount)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTagsUsage)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ Tag Name Usage ($tagsUsageCount Tags) docs
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsTagNameUsage
+
+ #Consumption
+ #$startScopeInsightsConsumptionSub = Get-Date
+ #region ScopeInsightsConsumptionSub
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+
+ if ($htAzureConsumptionSubscriptions.($subscriptionId).ConsumptionData) {
+ $consumptionData = $htAzureConsumptionSubscriptions.($subscriptionId).ConsumptionData
+
+ $arrayTotalCostSummarySub = @()
+ $arrayConsumptionData = [System.Collections.ArrayList]@()
+
+ $totalCost = 0
+
+ $currency = $htAzureConsumptionSubscriptions.($subscriptionId).Currency
+ $consumedServiceCount = ($consumptionData.ResourceType | Sort-Object -Unique | Measure-Object).Count
+ $resourceCount = ($consumptionData.ResourceId | Sort-Object -Unique | Measure-Object).Count
+ $subConsumptionDataGrouped = $consumptionData | Group-Object -Property ResourceType, ChargeType, MeterCategory
+
+ foreach ($consumptionline in $subConsumptionDataGrouped) {
+
+ $costConsumptionLine = ($consumptionline.group.PreTaxCost | Measure-Object -Sum).Sum
+ if ([math]::Round($costConsumptionLine, 2) -eq 0) {
+ $cost = $costConsumptionLine.ToString('0.0000')
+ }
+ else {
+ $cost = [math]::Round($costConsumptionLine, 2).ToString('0.00')
+ }
+
+ $null = $arrayConsumptionData.Add([PSCustomObject]@{
+ ResourceType = ($consumptionline.name).split(', ')[0]
+ ConsumedServiceChargeType = ($consumptionline.name).split(', ')[1]
+ ConsumedServiceCategory = ($consumptionline.name).split(', ')[2]
+ ConsumedServiceInstanceCount = $consumptionline.Count
+ ConsumedServiceCost = $cost #[decimal]$cost
+ ConsumedServiceCurrency = $currency
+ })
+
+ $totalCost = $htAzureConsumptionSubscriptions.($subscriptionId).TotalCost
+
+ }
+ if ([math]::Round($totalCost, 2) -eq 0) {
+ $totalCost = $totalCost
+ }
+ else {
+ $totalCost = [math]::Round($totalCost, 2).ToString('0.00')
+ }
+ $arrayTotalCostSummarySub += "$($totalCost) $($currency) generated by $($resourceCount) Resources ($($consumedServiceCount) ResourceTypes)"
+
+ $tfCount = ($arrayConsumptionData | Measure-Object).Count
+ $htmlTableId = "ScopeInsights_Consumption_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ Total cost $($arrayTotalCostSummarySub -join ', ') last $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)
+
+
Download CSV
semicolon |
comma
+
+
+
+ChargeType
+ResourceType
+Category
+ResourceCount
+Cost ($($AzureConsumptionPeriod)d)
+Currency
+
+
+
+"@)
+ $htmlScopeInsightsConsumptionSub = $null
+ $htmlScopeInsightsConsumptionSub = foreach ($consumptionLine in $arrayConsumptionData) {
+ @"
+
+$($consumptionLine.ConsumedServiceChargeType)
+$($consumptionLine.ResourceType)
+$($consumptionLine.ConsumedServiceCategory)
+$($consumptionLine.ConsumedServiceInstanceCount)
+$($consumptionLine.ConsumedServiceCost)
+$($currency)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsConsumptionSub)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Consumption data available
+'@)
+ }
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Consumption data available as switch parameter -DoAzureConsumption was not applied
+'@)
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsConsumptionSub
+ #$endScopeInsightsConsumptionSub = Get-Date
+ #Write-Host " **ScopeInsightsConsumptionSub data duration: $((NEW-TIMESPAN -Start $startScopeInsightsConsumptionSub -End $endScopeInsightsConsumptionSub).TotalSeconds) seconds"
+
+ #ResourceGroups
+ #region ScopeInsightsResourceGroups
+ if ($subscriptionResourceGroupsCount -gt 0) {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $subscriptionResourceGroupsCount Resource Groups | Limit: ($subscriptionResourceGroupsCount/$LimitResourceGroups)
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $subscriptionResourceGroupsCount Resource Groups
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsResourceGroups
+
+ #ResourceProvider
+ #region ScopeInsightsResourceProvidersDetailed
+ if ($azAPICallConf['htParameters'].NoResourceProvidersAtAll -eq $false) {
+ if ($azAPICallConf['htParameters'].NoResourceProvidersDetailed -eq $false) {
+ if (($htResourceProvidersAll).($subscriptionId)) {
+ $tfCount = ($htResourceProvidersAll).($subscriptionId).Providers.Count
+ $htmlTableId = "ScopeInsights_ResourceProvider_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ Resource Providers Detailed
+
+
Download CSV
semicolon |
comma
+
+
+
+Provider
+State
+
+
+
+"@)
+ $htmlScopeInsightsResourceProvidersDetailed = $null
+ $htmlScopeInsightsResourceProvidersDetailed = foreach ($provider in ($htResourceProvidersAll).($subscriptionId).Providers) {
+ @"
+
+$($provider.namespace)
+$($provider.registrationState)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResourceProvidersDetailed)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $(($htResourceProvidersAll.Keys).count) Resource Providers
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+ }
+ #endregion ScopeInsightsResourceProvidersDetailed
+
+ #region ScopeInsightsSubscriptionFeatures
+ if ($subscriptionFeatures) {
+ $subscriptionFeaturesCount = $subscriptionFeatures.Group.Count
+
+ $tfCount = $subscriptionFeaturesCount
+ $htmlTableId = "ScopeInsights_SubscriptionFeatures_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+
+ [void]$htmlScopeInsights.AppendLine(@"
+
+ $tfCount enabled Subscription Features
+
+
Set up preview features in Azure subscription docs
+
+
+
+Feature
+
+
+
+"@)
+
+ foreach ($feature in $subscriptionFeatures.Group | Sort-Object -Property feature) {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $($feature.feature)
+"@)
+ }
+
+
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ 0 enabled Subscription Features docs
+'@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsSubscriptionFeatures
+
+ #ResourceLocks
+ #region ScopeInsightsResourceLocks
+ if ($htResourceLocks.($subscriptionId)) {
+ $tfCount = 6
+ $htmlTableId = "ScopeInsights_ResourceLocks_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+
+ $subscriptionLocksCannotDeleteCount = $htResourceLocks.($subscriptionId).SubscriptionLocksCannotDeleteCount
+ $subscriptionLocksReadOnlyCount = $htResourceLocks.($subscriptionId).SubscriptionLocksReadOnlyCount
+ $resourceGroupsLocksCannotDeleteCount = $htResourceLocks.($subscriptionId).ResourceGroupsLocksCannotDeleteCount
+ $resourceGroupsLocksReadOnlyCount = $htResourceLocks.($subscriptionId).ResourceGroupsLocksReadOnlyCount
+ $resourcesLocksCannotDeleteCount = $htResourceLocks.($subscriptionId).ResourcesLocksCannotDeleteCount
+ $resourcesLocksReadOnlyCount = $htResourceLocks.($subscriptionId).ResourcesLocksReadOnlyCount
+
+ [void]$htmlScopeInsights.AppendLine(@"
+
+ Resource Locks
+
+
Considerations before applying locks docs
+
+
+
+Lock scope
+Lock type
+presence
+
+
+
+Subscription CannotDelete $($subscriptionLocksCannotDeleteCount)
+Subscription ReadOnly $($subscriptionLocksReadOnlyCount)
+ResourceGroup CannotDelete $($resourceGroupsLocksCannotDeleteCount)
+ResourceGroup ReadOnly $($resourceGroupsLocksReadOnlyCount)
+Resource CannotDelete $($resourcesLocksCannotDeleteCount)
+Resource ReadOnly $($resourcesLocksReadOnlyCount)
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ 0 Resource Locks docs
+'@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsResourceLocks
+
+ }
+
+ #MgChildInfo
+ #region ScopeInsightsManagementGroups
+ if ($mgOrSub -eq 'mg') {
+
+ [void]$htmlScopeInsights.AppendLine(@"
+ $(($mgAllChildMgs).count -1) ManagementGroups below this scope
+$(($mgAllChildSubscriptions).count) Subscriptions below this scope
+ Microsoft Defender for Cloud Secure Score: $managementGroupASCPoints Video , Blog , docs
+
+"@)
+
+ #region ScopeInsightsDiagnosticsMg
+ if (($htDiagnosticSettingsMgSub).mg.($mgChild)) {
+ $diagnosticsMgCount = (($htDiagnosticSettingsMgSub).mg.($mgChild).Values.Count)
+ $tfCount = $diagnosticsMgCount
+ $htmlTableId = "ScopeInsights_DiagnosticsMg_$($mgChild -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $diagnosticsMgCount Management Group Diagnostic settings
+
+
Download CSV
semicolon |
comma
+
+
+
+Diagnostic setting
+Target
+Target Id
+"@)
+ foreach ($logCategory in $diagnosticSettingsMgCategories) {
+ [void]$htmlScopeInsights.AppendLine(@"
+$logCategory
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+
+'@)
+ $htmlScopeInsightsDiagnosticsMg = $null
+ $htmlScopeInsightsDiagnosticsMg = foreach ($entry in ($htDiagnosticSettingsMgSub).mg.($mgChild).keys | Sort-Object) {
+ foreach ($diagset in ($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.keys | Sort-Object) {
+ @"
+
+$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticSettingName)
+$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticTargetType)
+$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticTargetId)
+"@
+ foreach ($logCategory in $diagnosticSettingsMgCategories) {
+ if (($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticCategoriesHt.($logCategory)) {
+ @"
+$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticCategoriesHt.($logCategory))
+"@
+ }
+ else {
+ @'
+n/a
+'@
+ }
+ }
+ @'
+
+'@
+ }
+ }
+
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsMg)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Management Group Diagnostic settings docs
+'@)
+ }
+ #endregion ScopeInsightsDiagnosticsMg
+
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+
+ #$startScopeInsightsConsumptionMg = Get-Date
+ #region ScopeInsightsConsumptionMg
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ if ($allConsumptionDataCount -gt 0) {
+
+ $consumptionData = $htManagementGroupsCost.($mgchild).consumptionDataSubscriptions
+ if (($consumptionData | Measure-Object).Count -gt 0) {
+ $arrayTotalCostSummaryMg = @()
+ $arrayConsumptionData = [System.Collections.ArrayList]@()
+ $consumptionDataGroupedByCurrency = $consumptionData | Group-Object -Property Currency
+ foreach ($currency in $consumptionDataGroupedByCurrency) {
+ $totalCost = 0
+ $tenantSummaryConsumptionDataGrouped = $currency.group | Group-Object -Property ResourceType, ChargeType, MeterCategory
+ $subsCount = ($tenantSummaryConsumptionDataGrouped.group.subscriptionId | Sort-Object -Unique).Count
+ $consumedServiceCount = ($tenantSummaryConsumptionDataGrouped.group.ResourceType | Sort-Object -Unique).Count
+ $resourceCount = ($tenantSummaryConsumptionDataGrouped.group.ResourceId | Sort-Object -Unique).Count
+ foreach ($consumptionline in $tenantSummaryConsumptionDataGrouped) {
+
+ $costConsumptionLine = ($consumptionline.group.PreTaxCost | Measure-Object -Sum).Sum
+ if ([math]::Round($costConsumptionLine, 2) -eq 0) {
+ $cost = $costConsumptionLine.ToString('0.0000')
+ }
+ else {
+ $cost = [math]::Round($costConsumptionLine, 2).ToString('0.00')
+ }
+
+ $null = $arrayConsumptionData.Add([PSCustomObject]@{
+ ResourceType = ($consumptionline.name).split(', ')[0]
+ ConsumedServiceChargeType = ($consumptionline.name).split(', ')[1]
+ ConsumedServiceCategory = ($consumptionline.name).split(', ')[2]
+ ConsumedServiceInstanceCount = $consumptionline.Count
+ ConsumedServiceCost = $cost #[decimal]$cost
+ ConsumedServiceSubscriptions = ($consumptionline.group.SubscriptionId | Sort-Object -Unique).Count
+ ConsumedServiceCurrency = $currency.Name
+ })
+
+ $totalCost = $totalCost + $costConsumptionLine
+ }
+ if ([math]::Round($totalCost, 2) -eq 0) {
+ $totalCost = $totalCost.ToString('0.0000')
+ }
+ else {
+ $totalCost = [math]::Round($totalCost, 2).ToString('0.00')
+ }
+ $arrayTotalCostSummaryMg += "$($totalCost) $($currency.Name) generated by $($resourceCount) Resources ($($consumedServiceCount) ResourceTypes) in $($subsCount) Subscriptions"
+ }
+
+ $tfCount = ($arrayConsumptionData).Count
+ $htmlTableId = "ScopeInsights_Consumption_$($mgChild -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ Total cost $($arrayTotalCostSummaryMg -join "$CsvDelimiterOpposite ") last $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)
+
+
Download CSV
+
semicolon |
+
comma
+
+
+
+ChargeType
+ResourceType
+Category
+ResourceCount
+Cost ($($AzureConsumptionPeriod)d)
+Currency
+Subscriptions
+
+
+
+"@)
+ $htmlScopeInsightsConsumptionMg = $null
+ $htmlScopeInsightsConsumptionMg = foreach ($consumptionLine in $arrayConsumptionData) {
+ @"
+
+$($consumptionLine.ConsumedServiceChargeType)
+$($consumptionLine.ResourceType)
+$($consumptionLine.ConsumedServiceCategory)
+$($consumptionLine.ConsumedServiceInstanceCount)
+$($consumptionLine.ConsumedServiceCost)
+$($consumptionLine.ConsumedServiceCurrency)
+$($consumptionLine.ConsumedServiceSubscriptions)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsConsumptionMg)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Consumption data available for Subscriptions under this ManagementGroup
+'@)
+ }
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Consumption data available
+'@)
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Consumption data available as switch parameter -DoAzureConsumption was not applied
+'@)
+ }
+ #endregion ScopeInsightsConsumptionMg
+ #$endScopeInsightsConsumptionMg = Get-Date
+ #Write-Host " ++ScopeInsightsConsumptionMg duration: ($((NEW-TIMESPAN -Start $startScopeInsightsConsumptionMg -End $endScopeInsightsConsumptionMg).TotalSeconds) seconds)"
+
+
+ }
+ #endregion ScopeInsightsManagementGroups
+
+ #ScopeInsightsResources
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #resources
+ #region ScopeInsightsResources
+ if ($mgOrSub -eq 'mg') {
+ if ($resourcesAllChildSubscriptionLocationCount -gt 0) {
+ $tfCount = ($resourcesAllChildSubscriptionsArray).count
+ $htmlTableId = "ScopeInsights_Resources_$($mgChild -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $resourcesAllChildSubscriptionResourceTypeCount ResourceTypes ($resourcesAllChildSubscriptionTotal Resources) in $resourcesAllChildSubscriptionLocationCount Locations (all Subscriptions below this scope)
+
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Location
+Count
+
+
+
+"@)
+ $htmlScopeInsightsResources = $null
+ $htmlScopeInsightsResources = foreach ($resourceAllChildSubscriptionResourceTypePerLocation in $resourcesAllChildSubscriptionsArray | Sort-Object @{Expression = { $_.ResourceType } }, @{Expression = { $_.location } }) {
+ @"
+
+$($resourceAllChildSubscriptionResourceTypePerLocation.ResourceType)
+$($resourceAllChildSubscriptionResourceTypePerLocation.location)
+$($resourceAllChildSubscriptionResourceTypePerLocation.ResourceCount)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResources)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $resourcesAllChildSubscriptionResourceTypeCount ResourceTypes (all Subscriptions below this scope)
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+
+ if ($mgOrSub -eq 'sub') {
+ if ($resourcesSubscriptionResourceTypeCount -gt 0) {
+ $tfCount = ($resourcesSubscription).Count
+ $htmlTableId = "ScopeInsights_Resources_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $resourcesSubscriptionResourceTypeCount ResourceTypes ($resourcesSubscriptionTotal Resources) in $resourcesSubscriptionLocationCount Locations
+
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Location
+Count
+
+
+
+"@)
+ $htmlScopeInsightsResources = $null
+ $htmlScopeInsightsResources = foreach ($resourceSubscriptionResourceTypePerLocation in $resourcesSubscription | Sort-Object @{Expression = { $_.type } }, @{Expression = { $_.location } }, @{Expression = { $_.count_ } }) {
+ @"
+
+$($resourceSubscriptionResourceTypePerLocation.type)
+$($resourceSubscriptionResourceTypePerLocation.location)
+$($resourceSubscriptionResourceTypePerLocation.count_)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResources)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $resourcesSubscriptionResourceTypeCount ResourceTypes
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+ #endregion ScopeInsightsResources
+ }
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #region ScopeInsightsCAFResourceNamingALL
+ if ($mgOrSub -eq 'sub') {
+ $resourcesIdsAllCAFNamingRelevantThisSubscription = $resourcesIdsAllCAFNamingRelevantGroupedBySubscription.where({ $_.Name -eq $subscriptionId })
+ if ($resourcesIdsAllCAFNamingRelevantThisSubscription) {
+ $resourcesIdsAllCAFNamingRelevantThisSubscriptionGroupedByType = $resourcesIdsAllCAFNamingRelevantThisSubscription.Group | Group-Object -Property type
+ $resourcesIdsAllCAFNamingRelevantThisSubscriptionGroupedByTypeCount = ($resourcesIdsAllCAFNamingRelevantThisSubscriptionGroupedByType | Measure-Object).Count
+
+ $tfCount = $resourcesIdsAllCAFNamingRelevantThisSubscriptionGroupedByTypeCount
+ $htmlTableId = "ScopeInsights_CAFResourceNamingALL_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ CAF Naming Recommendation Compliance
+
+
CAF - Recommended abbreviations for Azure resource types docs
+
Resource details can be found in the CSV output *_ResourcesAll.csv
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Recommendation
+ResourceFriendlyName
+passed
+failed
+passed percentage
+
+
+
+"@)
+ $htmlScopeInsightsCAFResourceNamingALL = $null
+ $htmlScopeInsightsCAFResourceNamingALL = foreach ($entry in $resourcesIdsAllCAFNamingRelevantThisSubscriptionGroupedByType) {
+
+ $resourceTypeGroupedByCAFResourceNamingResult = $entry.Group | Group-Object -Property cafResourceNamingResult, cafResourceNaming
+ if ($entry.Group.cafResourceNaming.Count -gt 1) {
+ $namingConvention = ($entry.Group.cafResourceNaming)[0]
+ $namingConventionFriendlyName = ($entry.Group.cafResourceNamingFriendlyName)[0]
+ }
+ else {
+ $namingConvention = $entry.Group.cafResourceNaming
+ $namingConventionFriendlyName = $entry.Group.cafResourceNamingFriendlyName
+ }
+
+ $passed = 0
+ $failed = 0
+ foreach ($result in $resourceTypeGroupedByCAFResourceNamingResult) {
+ $resultNameSplitted = $result.Name -split ', '
+ if ($resultNameSplitted[0] -eq 'passed') {
+ $passed = $result.Count
+ }
+
+ if ($resultNameSplitted[0] -eq 'failed') {
+ $failed = $result.Count
+ }
+ }
+
+ if ($passed -gt 0) {
+ $percentage = [math]::Round(($passed / ($passed + $failed) * 100), 2)
+ }
+ else {
+ $percentage = 0
+ }
+
+ @"
+
+$($entry.Name)
+$($namingConvention)
+$($namingConventionFriendlyName)
+$($passed)
+$($failed)
+$($percentage)%
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsCAFResourceNamingALL)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No CAF Naming Recommendation Compliance data available
+'@)
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+ #endregion ScopeInsightsCAFResourceNamingALL
+ }
+
+ #region ScopeInsightsOrphanedResources
+ if ($mgOrSub -eq 'sub') {
+ if ($arrayOrphanedResourcesGroupedBySubscription) {
+ $orphanedResourcesThisSubscription = $arrayOrphanedResourcesGroupedBySubscription.where({ $_.Name -eq $subscriptionId })
+ if ($orphanedResourcesThisSubscription) {
+ $orphanedResourcesThisSubscriptionCount = $orphanedResourcesThisSubscription.Group.count
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ $orphanedIncludingCost = $true
+ $hintTableTH = " ($($AzureConsumptionPeriod) days)"
+
+ $orphanedResourcesThisSubscriptionGroupedByType = $orphanedResourcesThisSubscription.Group | Group-Object -Property type, currency
+ $orphanedResourcesThisSubscriptionGroupedByTypeCount = ($orphanedResourcesThisSubscriptionGroupedByType | Measure-Object).Count
+ }
+ else {
+ $orphanedIncludingCost = $false
+ $hintTableTH = ''
+
+ $orphanedResourcesThisSubscriptionGroupedByType = $orphanedResourcesThisSubscription.Group | Group-Object -Property type
+ $orphanedResourcesThisSubscriptionGroupedByTypeCount = ($orphanedResourcesThisSubscriptionGroupedByType | Measure-Object).Count
+ }
+
+ $tfCount = $orphanedResourcesThisSubscriptionGroupedByTypeCount
+ $htmlTableId = "ScopeInsights_OrphanedResources_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $orphanedResourcesThisSubscriptionCount Cost optimization & cleanup ($orphanedResourcesThisSubscriptionGroupedByTypeCount ResourceTypes)
+
+
'Azure Orphan Resources' ARG queries and workbooks GitHub
+
Resource details can be found in the CSV output *_ResourcesCostOptimizationAndCleanup.csv
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Resource count
+Intent
+Cost$($hintTableTH)
+Currency
+
+
+
+"@)
+ $htmlScopeInsightsOrphanedResources = $null
+ $htmlScopeInsightsOrphanedResources = foreach ($resourceType in $orphanedResourcesThisSubscriptionGroupedByType | Sort-Object -Property Name) {
+
+ if ($orphanedIncludingCost) {
+ if (($resourceType.Group[0].Intent) -like 'cost savings*') {
+ $orphCost = ($resourceType.Group.Cost | Measure-Object -Sum).Sum
+ if ($orphCost -eq 0) {
+ $orphCost = ''
+ }
+ $orphCurrency = $resourceType.Group[0].Currency
+ }
+ else {
+ $orphCost = ''
+ $orphCurrency = ''
+ }
+ }
+ else {
+ if (($resourceType.Group.Intent | Get-Unique) -like 'cost savings*') {
+ $orphCost = "use parameter -DoAzureConsumption to show potential savings "
+ $orphCurrency = ''
+ }
+ else {
+ $orphCost = ''
+ $orphCurrency = ''
+ }
+ }
+
+ @"
+
+$(($resourceType.Name -split ',')[0])
+$($resourceType.Group.Count)
+$($resourceType.Group[0].Intent)
+$($orphCost)
+$($orphCurrency)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsOrphanedResources)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No cost optimization & cleanup
+'@)
+ }
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No cost optimization & cleanup
+'@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+ #endregion ScopeInsightsOrphanedResources
+
+ #ScopeInsightsDiagnosticsCapable
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #resourcesDiagnosticsCapable
+ #region ScopeInsightsDiagnosticsCapable
+ if ($mgOrSub -eq 'mg') {
+ $resourceTypesUnique = ($resourcesAllChildSubscriptions | Select-Object type -Unique).type
+ $resourceTypesSummarizedArray = [System.Collections.ArrayList]@()
+ foreach ($resourceTypeUnique in $resourceTypesUnique) {
+ $resourcesTypeCountTotal = 0
+ ($resourcesAllChildSubscriptions.where( { $_.type -eq $resourceTypeUnique } )).count_ | ForEach-Object { $resourcesTypeCountTotal += $_ }
+ $dataFromResourceTypesDiagnosticsArray = $resourceTypesDiagnosticsArray.where( { $_.ResourceType -eq $resourceTypeUnique } )
+ if ($dataFromResourceTypesDiagnosticsArray.Metrics -eq $true -or $dataFromResourceTypesDiagnosticsArray.Logs -eq $true) {
+ $resourceDiagnosticscapable = $true
+ }
+ else {
+ $resourceDiagnosticscapable = $false
+ }
+ $null = $resourceTypesSummarizedArray.Add([PSCustomObject]@{
+ ResourceType = $resourceTypeUnique
+ ResourceCount = $resourcesTypeCountTotal
+ DiagnosticsCapable = $resourceDiagnosticscapable
+ Metrics = $dataFromResourceTypesDiagnosticsArray.Metrics
+ Logs = $dataFromResourceTypesDiagnosticsArray.Logs
+ LogCategories = ($dataFromResourceTypesDiagnosticsArray.LogCategories -join "$CsvDelimiterOpposite ")
+ })
+ }
+ $subscriptionResourceTypesDiagnosticsCapableMetricsCount = ($resourceTypesSummarizedArray.where( { $_.Metrics -eq $true } )).count
+ $subscriptionResourceTypesDiagnosticsCapableLogsCount = ($resourceTypesSummarizedArray.where( { $_.Logs -eq $true } )).count
+ $subscriptionResourceTypesDiagnosticsCapableMetricsLogsCount = ($resourceTypesSummarizedArray.where( { $_.Metrics -eq $true -or $_.Logs -eq $true } )).count
+
+ if ($resourcesAllChildSubscriptionResourceTypeCount -gt 0) {
+ $tfCount = $resourcesAllChildSubscriptionResourceTypeCount
+ $htmlTableId = "ScopeInsights_resourcesDiagnosticsCapable_$($mgchild -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $subscriptionResourceTypesDiagnosticsCapableMetricsLogsCount/$resourcesAllChildSubscriptionResourceTypeCount ResourceTypes (1st party) Diagnostics capable ($subscriptionResourceTypesDiagnosticsCapableMetricsCount Metrics, $subscriptionResourceTypesDiagnosticsCapableLogsCount Logs) (all Subscriptions below this scope)
+
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Resource Count
+Diagnostics capable
+Metrics
+Logs
+LogCategories
+
+
+
+"@)
+ $htmlScopeInsightsDiagnosticsCapable = $null
+ $htmlScopeInsightsDiagnosticsCapable = foreach ($resourceSubscriptionResourceType in $resourceTypesSummarizedArray | Sort-Object @{Expression = { $_.ResourceType } }) {
+ @"
+
+$($resourceSubscriptionResourceType.ResourceType)
+$($resourceSubscriptionResourceType.ResourceCount)
+$($resourceSubscriptionResourceType.DiagnosticsCapable)
+$($resourceSubscriptionResourceType.Metrics)
+$($resourceSubscriptionResourceType.Logs)
+$($resourceSubscriptionResourceType.LogCategories)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsCapable)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $resourcesAllChildSubscriptionResourceTypeCount ResourceTypes (1st party) Diagnostics capable (all Subscriptions below this scope)
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+
+ if ($mgOrSub -eq 'sub') {
+ $resourceTypesUnique = ($resourcesSubscription | Select-Object type -Unique).type
+ $resourceTypesSummarizedArray = [System.Collections.ArrayList]@()
+ foreach ($resourceTypeUnique in $resourceTypesUnique) {
+ $resourcesTypeCountTotal = 0
+ ($resourcesSubscription.where( { $_.type -eq $resourceTypeUnique } )).count_ | ForEach-Object { $resourcesTypeCountTotal += $_ }
+ $dataFromResourceTypesDiagnosticsArray = $resourceTypesDiagnosticsArray.where( { $_.ResourceType -eq $resourceTypeUnique } )
+ if ($dataFromResourceTypesDiagnosticsArray.Metrics -eq $true -or $dataFromResourceTypesDiagnosticsArray.Logs -eq $true) {
+ $resourceDiagnosticscapable = $true
+ }
+ else {
+ $resourceDiagnosticscapable = $false
+ }
+ $null = $resourceTypesSummarizedArray.Add([PSCustomObject]@{
+ ResourceType = $resourceTypeUnique
+ ResourceCount = $resourcesTypeCountTotal
+ DiagnosticsCapable = $resourceDiagnosticscapable
+ Metrics = $dataFromResourceTypesDiagnosticsArray.Metrics
+ Logs = $dataFromResourceTypesDiagnosticsArray.Logs
+ LogCategories = ($dataFromResourceTypesDiagnosticsArray.LogCategories -join "$CsvDelimiterOpposite ")
+ })
+ }
+
+ $subscriptionResourceTypesDiagnosticsCapableMetricsCount = ($resourceTypesSummarizedArray.where( { $_.Metrics -eq $true } )).count
+ $subscriptionResourceTypesDiagnosticsCapableLogsCount = ($resourceTypesSummarizedArray.where( { $_.Logs -eq $true } )).count
+ $subscriptionResourceTypesDiagnosticsCapableMetricsLogsCount = ($resourceTypesSummarizedArray.where( { $_.Metrics -eq $true -or $_.Logs -eq $true } )).count
+
+ if ($resourcesSubscriptionResourceTypeCount -gt 0) {
+ $tfCount = $resourcesSubscriptionResourceTypeCount
+ $htmlTableId = "ScopeInsights_resourcesDiagnosticsCapable_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $subscriptionResourceTypesDiagnosticsCapableMetricsLogsCount/$resourcesSubscriptionResourceTypeCount ResourceTypes (1st party) Diagnostics capable ($subscriptionResourceTypesDiagnosticsCapableMetricsCount Metrics, $subscriptionResourceTypesDiagnosticsCapableLogsCount Logs)
+
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Resource Count
+Diagnostics capable
+Metrics
+Logs
+LogCategories
+
+
+
+"@)
+ $htmlScopeInsightsDiagnosticsCapable = $null
+ $htmlScopeInsightsDiagnosticsCapable = foreach ($resourceSubscriptionResourceType in $resourceTypesSummarizedArray | Sort-Object @{Expression = { $_.ResourceType } }) {
+ @"
+
+$($resourceSubscriptionResourceType.ResourceType)
+$($resourceSubscriptionResourceType.ResourceCount)
+$($resourceSubscriptionResourceType.DiagnosticsCapable)
+$($resourceSubscriptionResourceType.Metrics)
+$($resourceSubscriptionResourceType.Logs)
+$($resourceSubscriptionResourceType.LogCategories)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsCapable)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $resourcesSubscriptionResourceTypeCount ResourceTypes (1st party) Diagnostics capable
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+ #endregion ScopeInsightsDiagnosticsCapable
+ }
+
+ #ScopeInsightsUserAssignedIdentities4Resources
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ if ($mgOrSub -eq 'sub') {
+ #region ScopeInsightsUserAssignedIdentities4Resources
+ if ($arrayUserAssignedIdentities4ResourcesSubscriptionCount -gt 0) {
+ $tfCount = $arrayUserAssignedIdentities4ResourcesSubscriptionCount
+ $htmlTableId = "ScopeInsights_UserAssignedIdentities4Resources_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+
+ UserAssigned Managed Identities assigned to Resources / vice versa
+
+
Managed identity 'user-assigned' vs 'system-assigned' docs
+
Download CSV
semicolon |
comma
+
+
+
+MI Name
+MI MgPath
+MI Subscription Name
+MI Subscription Id
+MI ResourceGroup
+MI ResourceId
+MI AAD SP objectId
+MI AAD SP applicationId
+MI count Res assignments
+MI used cross subscription
+Res Name
+Res Type
+Res MgPath
+Res Subscription Name
+Res Subscription Id
+Res ResourceGroup
+Res Id
+Res count assigned MIs
+
+
+
+"@)
+ $htmlScopeInsightsUserAssignedIdentities4Resource = $null
+ $htmlScopeInsightsUserAssignedIdentities4Resource = foreach ($miResEntry in $arrayUserAssignedIdentities4ResourcesSubscription | Sort-Object -Property miResourceId, resourceId) {
+ @"
+
+ $($miResEntry.miResourceName)
+ $($miResEntry.miMgPath)
+ $($miResEntry.miSubscriptionName)
+ $($miResEntry.miSubscriptionId)
+ $($miResEntry.miResourceGroupName)
+ $($miResEntry.miResourceId)
+ $($miResEntry.miPrincipalId)
+ $($miResEntry.miClientId)
+ $($htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount)
+ $($miResEntry.miCrossSubscription)
+ $($miResEntry.resourceName)
+ $($miResEntry.resourceType)
+ $($miResEntry.resourceMgPath)
+ $($miResEntry.resourceSubscriptionName)
+ $($miResEntry.resourceSubscriptionId)
+ $($miResEntry.resourceResourceGroupName)
+ $($miResEntry.resourceId)
+ $($htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsUserAssignedIdentities4Resource)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No UserAssigned Managed Identities assigned to Resources / vice versa - at all
+'@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsUserAssignedIdentities4Resources
+ }
+ }
+
+ #ScopeInsightsPSRule
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ if ($azAPICallConf['htParameters'].DoPSRule -eq $true) {
+ #region ScopeInsightsPSRule
+
+ if ($mgOrSub -eq 'mg') {
+
+ $allPSRuleResultsUnderThisMg = [system.collections.ArrayList]@()
+ foreach ($mg in $grpPSRuleManagementGroups) {
+ if ($htManagementGroupsMgPath.($mg.name -replace '.*/').path -contains $mgchild) {
+ $allPSRuleResultsUnderThisMg.AddRange($mg.Group)
+ }
+ }
+
+ $grpThisManagementGroup = $allPSRuleResultsUnderThisMg | Group-Object -Property resourceType, pillar, category, severity, rule, result
+
+ if ($grpThisManagementGroup) {
+ $grpThisManagementGroupCount = $grpThisManagementGroup.Count
+ $tfCount = $grpThisManagementGroupCount
+ $htmlTableId = "ScopeInsights_PSRule_$($mgchild -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+
+ $grpThisManagementGroupCount 'PSRule for Azure' results
+
+
Learn about PSRule for Azure
+
Download CSV
semicolon |
comma
+
+
+
+Resource Type
+Resource Count
+Subscription Count
+Pillar
+Category
+Severity
+Rule
+Recommendation
+lnk
+State
+
+
+
+"@)
+ $htmlScopeInsightsPSRuleMG = $null
+ $htmlScopeInsightsPSRuleMG = foreach ($result in $grpThisManagementGroup) {
+ $resultNameSplit = $result.Name.split(', ')
+ @"
+
+ $($resultNameSplit[0])
+ $($result.Group.Count)
+ $(($result.Group.subscriptionId | Sort-Object -Unique).Count)
+ $($resultNameSplit[1])
+ $($resultNameSplit[2])
+ $($resultNameSplit[3])
+ $(($result.Group[0].rule))
+ $(($result.Group[0].recommendation))
+
+ $($resultNameSplit[5])
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPSRuleMG)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No PSRule for Azure results
+'@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+
+ if ($mgOrSub -eq 'sub') {
+ $grpThisSubscription = $grpPSRuleSubscriptions.where({ $_.Name -eq $subscriptionId })
+ $grpThisSubscriptionGrouped = $grpThisSubscription.Group | Group-Object -Property resourceType, pillar, category, severity, rule, result
+
+ if ($grpThisSubscriptionGrouped) {
+ $grpThisSubscriptionGroupedCount = $grpThisSubscriptionGrouped.Count
+ $tfCount = $grpThisSubscriptionGroupedCount
+ $htmlTableId = "ScopeInsights_PSRule_$($subscriptionId -replace '-','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+
+ $grpThisSubscriptionGroupedCount PSRule for Azure results
+
+
Learn about PSRule for Azure
+
Download CSV
semicolon |
comma
+
+
+
+Resource Type
+Resource Count
+Pillar
+Category
+Severity
+Rule
+Recommendation
+lnk
+State
+
+
+
+"@)
+ $htmlScopeInsightsPSRuleSub = $null
+ $htmlScopeInsightsPSRuleSub = foreach ($result in $grpThisSubscriptionGrouped) {
+ $resultNameSplit = $result.Name.split(', ')
+ @"
+
+ $($resultNameSplit[0])
+ $($result.Group.Count)
+ $($resultNameSplit[1])
+ $($resultNameSplit[2])
+ $($resultNameSplit[3])
+ $(($result.Group[0].rule))
+ $(($result.Group[0].recommendation))
+
+ $($resultNameSplit[5])
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPSRuleSub)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No PSRule results
+'@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+ #endregion ScopeInsightsPSRule
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ PSRule for Azure - integration paused - PSRule for Azure
+'@)
+ }
+ }
+
+ #PolicyAssignments
+ #region ScopeInsightsPolicyAssignments
+ if ($mgOrSub -eq 'mg') {
+ $SIDivContentClass = 'contentSIMG'
+ $htmlTableIdentifier = $mgChild
+
+ $policiesAssigned = [System.Collections.ArrayList]@()
+ $policiesCount = 0
+ $policiesCountBuiltin = 0
+ $policiesCountCustom = 0
+ $policiesAssignedAtScope = 0
+ $policiesInherited = 0
+ foreach ($policyAssignment in $arrayPolicyAssignmentsEnrichedForThisManagementGroupVariantPolicy) {
+ if ([String]::IsNullOrEmpty($policyAssignment.subscriptionId)) {
+ $null = $policiesAssigned.Add($policyAssignment)
+ $policiesCount++
+ if ($policyAssignment.PolicyType -eq 'BuiltIn') {
+ $policiesCountBuiltin++
+ }
+ if ($policyAssignment.PolicyType -eq 'Custom') {
+ $policiesCountCustom++
+ }
+ if ($policyAssignment.Inheritance -like 'this*') {
+ $policiesAssignedAtScope++
+ }
+ if ($policyAssignment.Inheritance -notlike 'this*') {
+ $policiesInherited++
+ }
+ }
+ }
+ }
+ if ($mgOrSub -eq 'sub') {
+ $SIDivContentClass = 'contentSISub'
+ $htmlTableIdentifier = $subscriptionId
+
+ $policiesAssigned = [System.Collections.ArrayList]@()
+ $policiesCount = 0
+ $policiesCountBuiltin = 0
+ $policiesCountCustom = 0
+ $policiesAssignedAtScope = 0
+ $policiesInherited = 0
+ foreach ($policyAssignment in $arrayPolicyAssignmentsEnrichedForThisSubscriptionVariantPolicy) {
+ $null = $policiesAssigned.Add($policyAssignment)
+ $policiesCount++
+ if ($policyAssignment.PolicyType -eq 'BuiltIn') {
+ $policiesCountBuiltin++
+ }
+ if ($policyAssignment.PolicyType -eq 'Custom') {
+ $policiesCountCustom++
+ }
+ if ($policyAssignment.Inheritance -like 'this*') {
+ $policiesAssignedAtScope++
+ }
+ if ($policyAssignment.Inheritance -notlike 'this*') {
+ $policiesInherited++
+ }
+ }
+ }
+
+ if (($policiesAssigned).count -gt 0) {
+ $tfCount = ($policiesAssigned).count
+ $htmlTableId = "ScopeInsights_PolicyAssignments_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ $noteOrNot = ''
+ [void]$htmlScopeInsights.AppendLine(@"
+ $policiesCount Policy assignments ($policiesAssignedAtScope at scope, $policiesInherited inherited) (Builtin: $policiesCountBuiltin | Custom: $policiesCountCustom)
+
+
Download CSV
semicolon |
comma
+
*Depending on the number of rows and your computer´s performance the table may respond with delay, download the csv for better filtering experience
+
+
+
+Inheritance
+ScopeExcluded
+Exemption applies
+Policy DisplayName
+PolicyId
+Type
+Category
+ALZ
+Effect
+Parameters
+Enforcement
+NonCompliance Message
+"@)
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+
+ [void]$htmlScopeInsights.AppendLine(@'
+Policies NonCmplnt
+Policies Compliant
+Resources NonCmplnt
+Resources Compliant
+Resources Conflicting
+'@)
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@"
+Role/Assignment $noteOrNot
+Managed Identity
+Assignment DisplayName
+AssignmentId
+AssignedBy
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlScopeInsightsPolicyAssignments = $null
+ $htmlScopeInsightsPolicyAssignments = foreach ($policyAssignment in $policiesAssigned | Sort-Object @{Expression = { $_.Level } }, @{Expression = { $_.MgName } }, @{Expression = { $_.MgId } }, @{Expression = { $_.SubscriptionName } }, @{Expression = { $_.SubscriptionId } }, @{Expression = { $_.PolicyAssignmentId } }) {
+
+ if ($policyAssignment.PolicyType -eq 'Custom') {
+ $policyName = ($policyAssignment.PolicyName -replace '<', '<' -replace '>', '>')
+ }
+ else {
+ $policyName = $policyAssignment.PolicyName
+ }
+ @"
+
+$($policyAssignment.Inheritance)
+$($policyAssignment.ExcludedScope)
+$($policyAssignment.ExemptionScope)
+$($policyName)
+$($policyAssignment.PolicyId)
+$($policyAssignment.PolicyType)
+$($policyAssignment.PolicyCategory -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyIsALZ)
+$($policyAssignment.Effect)
+$($policyAssignment.PolicyAssignmentParameters)
+$($policyAssignment.PolicyAssignmentEnforcementMode)
+$($policyAssignment.PolicyAssignmentNonComplianceMessages)
+"@
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ @"
+$($policyAssignment.NonCompliantPolicies)
+$($policyAssignment.CompliantPolicies)
+$($policyAssignment.NonCompliantResources)
+$($policyAssignment.CompliantResources)
+$($policyAssignment.ConflictingResources)
+"@
+ }
+
+ @"
+$($policyAssignment.RelatedRoleAssignments)
+$($policyAssignment.PolicyAssignmentMI)
+$($policyAssignment.PolicyAssignmentDisplayName -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyAssignmentId -replace '<', '<' -replace '>', '>')
+$($policyAssignment.AssignedBy)
+$($policyAssignment.CreatedOn)
+$($policyAssignment.CreatedBy)
+$($policyAssignment.UpdatedOn)
+$($policyAssignment.UpdatedBy)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPolicyAssignments)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $(($policiesAssigned).count) Policy assignments
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsPolicyAssignments
+
+ #PolicySetAssignments
+ #region ScopeInsightsPolicySetAssignments
+ if ($mgOrSub -eq 'mg') {
+ $SIDivContentClass = 'contentSIMG'
+ $htmlTableIdentifier = $mgChild
+
+ $policySetsAssigned = [System.Collections.ArrayList]@()
+ $policySetsCount = 0
+ $policySetsCountBuiltin = 0
+ $policySetsCountCustom = 0
+ $policySetsAssignedAtScope = 0
+ $policySetsInherited = 0
+ foreach ($policySetAssignment in $arrayPolicyAssignmentsEnrichedForThisManagementGroupVariantPolicySet) {
+ if ([String]::IsNullOrEmpty($policySetAssignment.subscriptionId)) {
+ $null = $policySetsAssigned.Add($policySetAssignment)
+ $policySetsCount++
+ if ($policySetAssignment.PolicyType -eq 'BuiltIn') {
+ $policySetsCountBuiltin++
+ }
+ if ($policySetAssignment.PolicyType -eq 'Custom') {
+ $policySetsCountCustom++
+ }
+ if ($policySetAssignment.Inheritance -like 'this*') {
+ $policySetsAssignedAtScope++
+ }
+ if ($policySetAssignment.Inheritance -notlike 'this*') {
+ $policySetsInherited++
+ }
+ }
+ }
+ }
+ if ($mgOrSub -eq 'sub') {
+ $SIDivContentClass = 'contentSISub'
+ $htmlTableIdentifier = $subscriptionId
+
+ $policySetsAssigned = [System.Collections.ArrayList]@()
+ $policySetsCount = 0
+ $policySetsCountBuiltin = 0
+ $policySetsCountCustom = 0
+ $policySetsAssignedAtScope = 0
+ $policySetsInherited = 0
+ foreach ($policySetAssignment in $arrayPolicyAssignmentsEnrichedForThisSubscriptionVariantPolicySet) {
+ $null = $policySetsAssigned.Add($policySetAssignment)
+ $policySetsCount++
+ if ($policySetAssignment.PolicyType -eq 'BuiltIn') {
+ $policySetsCountBuiltin++
+ }
+ if ($policySetAssignment.PolicyType -eq 'Custom') {
+ $policySetsCountCustom++
+ }
+ if ($policySetAssignment.Inheritance -like 'this*') {
+ $policySetsAssignedAtScope++
+ }
+ if ($policySetAssignment.Inheritance -notlike 'this*') {
+ $policySetsInherited++
+ }
+ }
+ }
+
+ if (($policySetsAssigned).count -gt 0) {
+ $tfCount = ($policiesAssigned).count
+ $htmlTableId = "ScopeInsights_PolicySetAssignments_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ $noteOrNot = ''
+ [void]$htmlScopeInsights.AppendLine(@"
+ $policySetsCount PolicySet assignments ($policySetsAssignedAtScope at scope, $policySetsInherited inherited) (Builtin: $policySetsCountBuiltin | Custom: $policySetsCountCustom)
+
+
Download CSV
semicolon |
comma
+
+
+
+Inheritance
+ScopeExcluded
+PolicySet DisplayName
+PolicySetId
+Type
+Category
+ALZ
+Parameters
+Enforcement
+NonCompliance Message
+"@)
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+
+ [void]$htmlScopeInsights.AppendLine(@'
+Policies NonCmplnt
+Policies Compliant
+Resources NonCmplnt
+Resources Compliant
+Resources Conflicting
+'@)
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@"
+Role/Assignment $noteOrNot
+Managed Identity
+Assignment DisplayName
+AssignmentId
+AssignedBy
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlScopeInsightsPolicySetAssignments = $null
+ $htmlScopeInsightsPolicySetAssignments = foreach ($policyAssignment in $policySetsAssigned | Sort-Object -Property Level, PolicyAssignmentId) {
+ if ($policyAssignment.PolicyType -eq 'Custom') {
+ $policyName = ($policyAssignment.PolicyName -replace '<', '<' -replace '>', '>')
+ }
+ else {
+ $policyName = $policyAssignment.PolicyName
+ }
+ @"
+
+$($policyAssignment.Inheritance)
+$($policyAssignment.ExcludedScope)
+$($policyName)
+$($policyAssignment.PolicyId)
+$($policyAssignment.PolicyType)
+$($policyAssignment.PolicyCategory -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyIsALZ)
+$($policyAssignment.PolicyAssignmentParameters)
+$($policyAssignment.PolicyAssignmentEnforcementMode)
+$($policyAssignment.PolicyAssignmentNonComplianceMessages)
+"@
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ @"
+$($policyAssignment.NonCompliantPolicies)
+$($policyAssignment.CompliantPolicies)
+$($policyAssignment.NonCompliantResources)
+$($policyAssignment.CompliantResources)
+$($policyAssignment.ConflictingResources)
+"@
+ }
+ @"
+$($policyAssignment.RelatedRoleAssignments)
+$($policyAssignment.PolicyAssignmentMI)
+$($policyAssignment.PolicyAssignmentDisplayName -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyAssignmentId -replace '<', '<' -replace '>', '>')
+$($policyAssignment.AssignedBy)
+$($policyAssignment.CreatedOn)
+$($policyAssignment.CreatedBy)
+$($policyAssignment.UpdatedOn)
+$($policyAssignment.UpdatedBy)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPolicySetAssignments)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $(($policySetsAssigned).count) PolicySet assignments
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsPolicySetAssignments
+
+ #PolicyAssignmentsLimit (Policy+PolicySet)
+ #region ScopeInsightsPolicyAssignmentsLimit
+ if ($mgOrSub -eq 'mg') {
+ $limit = $LimitPOLICYPolicyAssignmentsManagementGroup
+ }
+ if ($mgOrSub -eq 'sub') {
+ $limit = $LimitPOLICYPolicyAssignmentsSubscription
+ }
+
+ if ($policiesAssignedAtScope -eq 0 -and $policySetsAssignedAtScope -eq 0) {
+ $faimage = " "
+
+ [void]$htmlScopeInsights.AppendLine(@"
+ $faImage Policy Assignment Limit: 0/$limit
+"@)
+ }
+ else {
+ if ($mgOrSub -eq 'mg') {
+ $scopePolicyAssignmentsLimit = $policyPolicyBaseQueryScopeInsights.where( { [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.MgId -eq $mgChild } )
+ }
+ if ($mgOrSub -eq 'sub') {
+ $scopePolicyAssignmentsLimit = $policyPolicyBaseQueryScopeInsights.where( { $_.SubscriptionId -eq $subscriptionId } )
+ }
+
+ if ($scopePolicyAssignmentsLimit.PolicyAndPolicySetAssignmentAtScopeCount -gt (($limit) * $LimitCriticalPercentage / 100)) {
+ $faImage = " "
+ }
+ else {
+ $faimage = " "
+ }
+ [void]$htmlScopeInsights.AppendLine(@"
+ $faImage Policy Assignment Limit: $($scopePolicyAssignmentsLimit.PolicyAndPolicySetAssignmentAtScopeCount)/$($limit)
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsPolicyAssignmentsLimit
+
+ #ScopedPolicies
+ #region ScopeInsightsScopedPolicies
+ if ($mgOrSub -eq 'mg') {
+ $SIDivContentClass = 'contentSIMG'
+ $htmlTableIdentifier = $mgChild
+ $scopePolicies = $customPoliciesDetailed.where( { $_.PolicyDefinitionId -like "*/providers/Microsoft.Management/managementGroups/$mgChild/*" } )
+ $scopePoliciesCount = ($scopePolicies).count
+ }
+ if ($mgOrSub -eq 'sub') {
+ $SIDivContentClass = 'contentSISub'
+ $htmlTableIdentifier = $subscriptionId
+ $scopePolicies = $customPoliciesDetailed.where( { $_.PolicyDefinitionId -like "*/subscriptions/$subscriptionId/*" } )
+ $scopePoliciesCount = ($scopePolicies).count
+ }
+
+ if ($scopePoliciesCount -gt 0) {
+ $tfCount = $scopePoliciesCount
+ $htmlTableId = "ScopeInsights_ScopedPolicies_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ if ($mgOrSub -eq 'mg') {
+ $LimitPOLICYPolicyScoped = $LimitPOLICYPolicyDefinitionsScopedManagementGroup
+ if ($scopePoliciesCount -gt (($LimitPOLICYPolicyScoped * $LimitCriticalPercentage) / 100)) {
+ $faIcon = " "
+ }
+ else {
+ $faIcon = " "
+ }
+ }
+ if ($mgOrSub -eq 'sub') {
+ $LimitPOLICYPolicyScoped = $LimitPOLICYPolicyDefinitionsScopedSubscription
+ if ($scopePoliciesCount -gt (($LimitPOLICYPolicyScoped * $LimitCriticalPercentage) / 100)) {
+ $faIcon = " "
+ }
+ else {
+ $faIcon = " "
+ }
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@"
+$faIcon $scopePoliciesCount Custom Policy definitions scoped | Limit: ($scopePoliciesCount/$LimitPOLICYPolicyScoped)
+
+
Download CSV
semicolon |
comma
+
+
+
+Policy DisplayName
+PolicyId
+Category
+ALZ
+Policy effect
+Role definitions
+Unique assignments
+Used in PolicySets
+
+
+
+"@)
+ $htmlScopeInsightsScopedPolicies = $null
+ $htmlScopeInsightsScopedPolicies = foreach ($custompolicy in $scopePolicies | Sort-Object @{Expression = { $_.PolicyDisplayName } }, @{Expression = { $_.PolicyDefinitionId } }) {
+ if ($custompolicy.UsedInPolicySetsCount -gt 0) {
+ $customPolicyUsedInPolicySets = "$($customPolicy.UsedInPolicySetsCount) ($($customPolicy.UsedInPolicySets))"
+ }
+ else {
+ $customPolicyUsedInPolicySets = $($customPolicy.UsedInPolicySetsCount)
+ }
+ @"
+
+$($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')
+$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')
+$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')
+$($customPolicy.ALZ)
+$($customPolicy.PolicyEffect)
+$($customPolicy.RoleDefinitions)
+$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')
+$($customPolicyUsedInPolicySets)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsScopedPolicies)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $scopePoliciesCount Custom Policy definitions scoped
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsScopedPolicies
+
+ #ScopedPolicySets
+ #region ScopeInsightsScopedPolicySets
+ if ($mgOrSub -eq 'mg') {
+ $SIDivContentClass = 'contentSIMG'
+ $htmlTableIdentifier = $mgChild
+ $scopePolicySets = $customPolicySetsDetailed.where( { $_.PolicySetDefinitionId -like "*/providers/Microsoft.Management/managementGroups/$mgChild/*" } )
+ $scopePolicySetsCount = ($scopePolicySets).count
+ }
+ if ($mgOrSub -eq 'sub') {
+ $SIDivContentClass = 'contentSISub'
+ $htmlTableIdentifier = $subscriptionId
+ $scopePolicySets = $customPolicySetsDetailed.where( { $_.PolicySetDefinitionId -like "*/subscriptions/$subscriptionId/*" } )
+ $scopePolicySetsCount = ($scopePolicySets).count
+ }
+
+ if ($scopePolicySetsCount -gt 0) {
+ $tfCount = $scopePolicySetsCount
+ $htmlTableId = "ScopeInsights_ScopedPolicySets_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ if ($mgOrSub -eq 'mg') {
+ $LimitPOLICYPolicySetScoped = $LimitPOLICYPolicySetDefinitionsScopedManagementGroup
+ if ($scopePolicySetsCount -gt (($LimitPOLICYPolicySetScoped * $LimitCriticalPercentage) / 100)) {
+ $faIcon = " "
+ }
+ else {
+ $faIcon = " "
+ }
+ }
+ if ($mgOrSub -eq 'sub') {
+ $LimitPOLICYPolicySetScoped = $LimitPOLICYPolicySetDefinitionsScopedSubscription
+ if ($scopePolicySetsCount -gt (($LimitPOLICYPolicySetScoped * $LimitCriticalPercentage) / 100)) {
+ $faIcon = " "
+ }
+ else {
+ $faIcon = " "
+ }
+ }
+ [void]$htmlScopeInsights.AppendLine(@"
+$faIcon $scopePolicySetsCount Custom PolicySet definitions scoped | Limit: ($scopePolicySetsCount/$LimitPOLICYPolicySetScoped)
+
+
Download CSV
semicolon |
comma
+
+
+
+PolicySet DisplayName
+PolicySetId
+Category
+ALZ
+Unique assignments
+Policies Used
+
+
+
+"@)
+ $htmlScopeInsightsScopedPolicySets = $null
+ $htmlScopeInsightsScopedPolicySets = foreach ($custompolicySet in $scopePolicySets | Sort-Object @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) {
+ @"
+
+$($custompolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')
+$($custompolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')
+$($custompolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')
+$($custompolicySet.ALZ)
+$($custompolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')
+$($custompolicySet.PoliciesUsed)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsScopedPolicySets)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $scopePolicySetsCount Custom PolicySet definitions scoped
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsScopedPolicySets
+
+ #BlueprintAssignments
+ #region ScopeInsightsBlueprintAssignments
+ if ($mgOrSub -eq 'sub') {
+ if ($blueprintsAssignedCount -gt 0) {
+
+ if ($mgOrSub -eq 'mg') {
+ $htmlTableIdentifier = $mgChild
+ }
+ if ($mgOrSub -eq 'sub') {
+ $htmlTableIdentifier = $subscriptionId
+ }
+ $htmlTableId = "ScopeInsights_BlueprintAssignment_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $blueprintsAssignedCount Blueprints assigned
+
+
Download CSV
semicolon |
comma
+
+
+
+Blueprint Name
+Blueprint DisplayName
+Blueprint Description
+BlueprintId
+Blueprint Version
+Blueprint AssignmentId
+
+
+
+"@)
+ $htmlScopeInsightsBlueprintAssignments = $null
+ $htmlScopeInsightsBlueprintAssignments = foreach ($blueprintAssigned in $blueprintsAssigned) {
+ @"
+
+$($blueprintAssigned.BlueprintName -replace '<', '<' -replace '>', '>')
+$($blueprintAssigned.BlueprintDisplayName -replace '<', '<' -replace '>', '>')
+$($blueprintAssigned.BlueprintDescription -replace '<', '<' -replace '>', '>')
+$($blueprintAssigned.BlueprintId -replace '<', '<' -replace '>', '>')
+$($blueprintAssigned.BlueprintAssignmentVersion -replace '<', '<' -replace '>', '>')
+$($blueprintAssigned.BlueprintAssignmentId -replace '<', '<' -replace '>', '>')
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsBlueprintAssignments)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $blueprintsAssignedCount Blueprints assigned
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ }
+ #endregion ScopeInsightsBlueprintAssignments
+
+ #BlueprintsScoped
+ #region ScopeInsightsBlueprintsScoped
+ if ($blueprintsScopedCount -gt 0) {
+ $tfCount = $blueprintsScopedCount
+ if ($mgOrSub -eq 'mg') {
+ $SIDivContentClass = 'contentSIMG'
+ $htmlTableIdentifier = $mgChild
+ }
+ if ($mgOrSub -eq 'sub') {
+ $SIDivContentClass = 'contentSISub'
+ $htmlTableIdentifier = $subscriptionId
+ }
+ $htmlTableId = "ScopeInsights_BlueprintScoped_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $blueprintsScopedCount Blueprints scoped
+
+
Download CSV
semicolon |
comma
+
+
+
+Blueprint Name
+Blueprint DisplayName
+Blueprint Description
+BlueprintId
+
+
+
+"@)
+ $htmlScopeInsightsBlueprintsScoped = $null
+ $htmlScopeInsightsBlueprintsScoped = foreach ($blueprintScoped in $blueprintsScoped) {
+ @"
+
+$($blueprintScoped.BlueprintName -replace '<', '<' -replace '>', '>')
+$($blueprintScoped.BlueprintDisplayName -replace '<', '<' -replace '>', '>')
+$($blueprintScoped.BlueprintDescription -replace '<', '<' -replace '>', '>')
+$($blueprintScoped.BlueprintId -replace '<', '<' -replace '>', '>')
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsBlueprintsScoped)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $blueprintsScopedCount Blueprints scoped
+"@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsBlueprintsScoped
+
+ if ($mgOrSub -eq 'sub') {
+ #region ScopeInsightsClassicAdministrators
+ if ($htClassicAdministrators.($subscriptionId).ClassicAdministrators.Count -gt 0) {
+ $tfCount = $htClassicAdministrators.($subscriptionId).ClassicAdministrators.Count
+ $htmlTableId = "ScopeInsights_ClassicAdministrators_$($subscriptionId -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ [void]$htmlScopeInsights.AppendLine(@"
+ $tfCount Classic Administrators
+
+
Download CSV
semicolon |
comma
+
+
+
+Role
+Identity
+
+
+
+"@)
+ $htmlScopeInsightsClassicAdministrators = $null
+ $htmlScopeInsightsClassicAdministrators = foreach ($classicAdministrator in $htClassicAdministrators.($subscriptionId).ClassicAdministrators | Sort-Object -Property Role, Identity) {
+ @"
+
+$($classicAdministrator.Role)
+$($classicAdministrator.Identity)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsClassicAdministrators)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@'
+ No Classic Administrators
+'@)
+ }
+ [void]$htmlScopeInsights.AppendLine(@'
+
+
+'@)
+ #endregion ScopeInsightsClassicAdministrators
+ }
+
+ #RoleAssignments
+ #region ScopeInsightsRoleAssignments
+ if ($mgOrSub -eq 'mg') {
+ $SIDivContentClass = 'contentSIMG'
+ $htmlTableIdentifier = $mgChild
+ $LimitRoleAssignmentsScope = $LimitRBACRoleAssignmentsManagementGroup
+
+ $rolesAssigned = [System.Collections.ArrayList]@()
+ $rolesAssignedCount = 0
+ $rolesAssignedInheritedCount = 0
+ $rolesAssignedUser = 0
+ $rolesAssignedGroup = 0
+ $rolesAssignedServicePrincipal = 0
+ $rolesAssignedUnknown = 0
+ $roleAssignmentsRelatedToPolicyCount = 0
+ $roleSecurityFindingCustomRoleOwner = 0
+ $roleSecurityFindingOwnerAssignmentSP = 0
+ $rbacForThisManagementGroup = ($rbacAllGroupedByManagementGroup.where( { $_.name -eq $mgChild } )).group
+ foreach ($roleAssignment in $rbacForThisManagementGroup) {
+ if ([String]::IsNullOrEmpty($roleAssignment.subscriptionId)) {
+ $null = $rolesAssigned.Add($roleAssignment)
+ $rolesAssignedCount++
+ if ($roleAssignment.Scope -notlike 'this*') {
+ $rolesAssignedInheritedCount++
+ }
+ if ($roleAssignment.ObjectType -like 'User*') {
+ $rolesAssignedUser++
+ }
+ if ($roleAssignment.ObjectType -eq 'Group') {
+ $rolesAssignedGroup++
+ }
+ if ($roleAssignment.ObjectType -like 'SP*') {
+ $rolesAssignedServicePrincipal++
+ }
+ if ($roleAssignment.ObjectType -eq 'Unknown') {
+ $rolesAssignedUnknown++
+ }
+ if ($roleAssignment.RbacRelatedPolicyAssignment -ne 'none') {
+ $roleAssignmentsRelatedToPolicyCount++
+ }
+ if ($roleAssignment.RoleSecurityCustomRoleOwner -eq 1) {
+ $roleSecurityFindingCustomRoleOwner++
+ }
+ if ($roleAssignment.RoleSecurityOwnerAssignmentSP -eq 1) {
+ $roleSecurityFindingOwnerAssignmentSP++
+ }
+ }
+ }
+ }
+ if ($mgOrSub -eq 'sub') {
+ $SIDivContentClass = 'contentSISub'
+ $htmlTableIdentifier = $subscriptionId
+ $LimitRoleAssignmentsScope = $htSubscriptionsRoleAssignmentLimit.($subscriptionId)
+
+ $rolesAssigned = [System.Collections.ArrayList]@()
+ $rolesAssignedCount = 0
+ $rolesAssignedInheritedCount = 0
+ $rolesAssignedUser = 0
+ $rolesAssignedGroup = 0
+ $rolesAssignedServicePrincipal = 0
+ $rolesAssignedUnknown = 0
+ $roleAssignmentsRelatedToPolicyCount = 0
+ $roleSecurityFindingCustomRoleOwner = 0
+ $roleSecurityFindingOwnerAssignmentSP = 0
+ $rbacForThisSubscription = ($rbacAllGroupedBySubscription.where( { $_.name -eq $subscriptionId } )).group
+ $rolesAssigned = foreach ($roleAssignment in $rbacForThisSubscription) {
+
+ $roleAssignment
+ $rolesAssignedCount++
+ if ($roleAssignment.Scope -notlike 'this*') {
+ $rolesAssignedInheritedCount++
+ }
+ if ($roleAssignment.ObjectType -like 'User*') {
+ $rolesAssignedUser++
+ }
+ if ($roleAssignment.ObjectType -eq 'Group') {
+ $rolesAssignedGroup++
+ }
+ if ($roleAssignment.ObjectType -like 'SP*') {
+ $rolesAssignedServicePrincipal++
+ }
+ if ($roleAssignment.ObjectType -eq 'Unknown') {
+ $rolesAssignedUnknown++
+ }
+ if ($roleAssignment.RbacRelatedPolicyAssignment -ne 'none') {
+ $roleAssignmentsRelatedToPolicyCount++
+ }
+ if ($roleAssignment.RoleSecurityCustomRoleOwner -eq 1) {
+ $roleSecurityFindingCustomRoleOwner++
+ }
+ if ($roleAssignment.RoleSecurityOwnerAssignmentSP -eq 1) {
+ $roleSecurityFindingOwnerAssignmentSP++
+ }
+ }
+ }
+
+ $rolesAssignedAtScopeCount = $rolesAssignedCount - $rolesAssignedInheritedCount
+
+ if (($rolesAssigned).count -gt 0) {
+ $tfCount = ($rolesAssigned).count
+ $htmlTableId = "ScopeInsights_RoleAssignments_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')"
+ $randomFunctionName = "func_$htmlTableId"
+ $noteOrNot = ''
+ [void]$htmlScopeInsights.AppendLine(@"
+ $rolesAssignedCount Role assignments ($rolesAssignedInheritedCount inherited) (User: $rolesAssignedUser | Group: $rolesAssignedGroup | ServicePrincipal: $rolesAssignedServicePrincipal | Orphaned: $rolesAssignedUnknown) ($($roleSecurityFindingCustomRoleOwnerImg)CustomRoleOwner: $roleSecurityFindingCustomRoleOwner, $($RoleSecurityFindingOwnerAssignmentSPImg)OwnerAssignmentSP: $roleSecurityFindingOwnerAssignmentSP) (Policy related: $roleAssignmentsRelatedToPolicyCount) | Limit: ($rolesAssignedAtScopeCount/$LimitRoleAssignmentsScope)
+
+
Download CSV
semicolon |
comma
+
*Depending on the number of rows and your computer´s performance the table may respond with delay, download the csv for better filtering experience
+
+
+
+Scope
+Role
+RoleId
+Role Type
+Data
+Can do Role assignment
+Identity Displayname
+Identity SignInName
+Identity ObjectId
+Identity Type
+Applicability
+Applies through membership
+Group Details
+Role AssignmentId
+Related Policy Assignment $noteOrNot
+CreatedOn
+CreatedBy
+
+
+
+"@)
+ $htmlScopeInsightsRoleAssignments = $null
+ $htmlScopeInsightsRoleAssignments = foreach ($roleAssignment in ($rolesAssigned | Sort-Object -Property Level, MgName, MgId, SubscriptionName, SubscriptionId, Scope, Role, RoleId, ObjectId, RoleAssignmentId)) {
+ @"
+
+$($roleAssignment.Scope)
+$($roleAssignment.Role)
+$($roleAssignment.RoleId)
+$($roleAssignment.RoleType)
+$($roleAssignment.RoleDataRelated)
+$($roleAssignment.RoleCanDoRoleAssignments)
+$($roleAssignment.ObjectDisplayName)
+$($roleAssignment.ObjectSignInName)
+$($roleAssignment.ObjectId)
+$($roleAssignment.ObjectType)
+$($roleAssignment.AssignmentType)
+$($roleAssignment.AssignmentInheritFrom)
+$($roleAssignment.GroupMembersCount)
+$($roleAssignment.RoleAssignmentId)
+$($roleAssignment.rbacRelatedPolicyAssignment)
+$($roleAssignment.CreatedOn)
+$($roleAssignment.CreatedBy)
+
+"@
+ }
+ [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsRoleAssignments)
+ [void]$htmlScopeInsights.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlScopeInsights.AppendLine(@"
+ $(($rbacAll).count) Role assignments
+
+"@)
+ }
+
+ [void]$htmlScopeInsights.AppendLine(@'
+
+'@)
+ #endregion ScopeInsightsRoleAssignments
+
+
+ if (-not $NoScopeInsights) {
+ $script:html += $htmlScopeInsights
+ }
+
+ if (-not $NoSingleSubscriptionOutput) {
+ if ($mgOrSub -eq 'sub') {
+ $htmlThisSubSingleOutput = $htmlSubscriptionOnlyStart
+ $htmlThisSubSingleOutput += $htmlScopeInsights
+ $htmlThisSubSingleOutput += $htmlSubscriptionOnlyEnd
+ $htmlThisSubSingleOutput | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($HTMLPath)$($DirectorySeparatorChar)$($fileName)_$($subscriptionId).html" -Encoding utf8 -Force
+ $htmlThisSubSingleOutput = $null
+ }
+ }
+
+ if (-not $NoScopeInsights) {
+ if ($scopescnter % 50 -eq 0) {
+ $script:scopescnter = 0
+ Write-Host ' append file duration: '(Measure-Command { $script:html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force }).TotalSeconds 'seconds'
+ $script:html = $null
+ }
+ }
+
+ if ($scopescnter % 50 -eq 0) {
+ showMemoryUsage
+ }
+
+}
+function processStorageAccountAnalysis {
+ $start = Get-Date
+ Write-Host 'Processing Storage Account Analysis'
+ $storageAccountsCount = $storageAccounts.count
+ if ($storageAccountsCount -gt 0) {
+ Write-Host " Executing Storage Account Analysis for $storageAccountsCount Storage Accounts"
+ createBearerToken -AzAPICallConfiguration $azapicallconf -targetEndPoint 'Storage'
+
+ $htSACost = @{}
+ if ($DoAzureConsumption -eq $true) {
+ $saConsumptionByResourceId = $allConsumptionData.where({ $_.resourceType -eq 'microsoft.storage/storageaccounts' }) | Group-Object -Property resourceid
+
+ foreach ($sa in $saConsumptionByResourceId) {
+ $htSACost.($sa.Name) = @{}
+ $htSACost.($sa.Name).meterCategoryAll = ($sa.Group.MeterCategory | Sort-Object) -join ', '
+ $htSACost.($sa.Name).costAll = ($sa.Group.PreTaxCost | Measure-Object -Sum).Sum #[decimal]($sa.Group.PreTaxCost | Measure-Object -Sum).Sum
+ $htSACost.($sa.Name).currencyAll = ($sa.Group.Currency | Sort-Object -Unique) -join ', '
+ foreach ($costentry in $sa.Group) {
+ $htSACost.($sa.Name)."cost_$($costentry.MeterCategory)" = $costentry.PreTaxCost
+ $htSACost.($sa.Name)."currency_$($costentry.MeterCategory)" = $costentry.Currency
+ }
+ }
+ }
+
+ $storageAccounts | ForEach-Object -Parallel {
+ $storageAccount = $_
+ $azAPICallConf = $using:azAPICallConf
+ $arrayStorageAccountAnalysisResults = $using:arrayStorageAccountAnalysisResults
+ $htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI
+ $htSubscriptionsMgPath = $using:htSubscriptionsMgPath
+ $htSubscriptionTags = $using:htSubscriptionTags
+ $CSVDelimiterOpposite = $using:CSVDelimiterOpposite
+ $htSACost = $using:htSACost
+ $StorageAccountAccessAnalysisSubscriptionTags = $using:StorageAccountAccessAnalysisSubscriptionTags
+ $StorageAccountAccessAnalysisStorageAccountTags = $using:StorageAccountAccessAnalysisStorageAccountTags
+ $listContainersSuccess = 'n/a'
+ $containersCount = 'n/a'
+ $arrayContainers = @()
+ $arrayContainersAnonymousContainer = @()
+ $arrayContainersAnonymousBlob = @()
+ $staticWebsitesState = 'n/a'
+ $webSiteResponds = 'n/a'
+
+ $subscriptionId = ($storageAccount.SA.id -split '/')[2]
+ $resourceGroupName = ($storageAccount.SA.id -split '/')[4]
+ $subDetails = $htAllSubscriptionsFromAPI.($subscriptionId).subDetails
+
+ Write-Host "Processing Storage Account '$($storageAccount.SA.name)' - Subscription: '$($subDetails.displayName)' ($subscriptionId) [$($subDetails.subscriptionPolicies.quotaId)]"
+
+ if ($storageAccount.SA.Properties.primaryEndpoints.blob) {
+
+ $urlServiceProps = "$($storageAccount.SA.Properties.primaryEndpoints.blob)?restype=service&comp=properties"
+ $saProperties = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $urlServiceProps -method 'GET' -listenOn 'Content' -currentTask "$($storageAccount.SA.name) get restype=service&comp=properties" -saResourceGroupName $resourceGroupName -unhandledErrorAction Continue
+ if ($saProperties) {
+ if ($saProperties -eq 'AuthorizationFailure' -or $saProperties -eq 'AuthorizationPermissionDenied' -or $saProperties -eq 'ResourceUnavailable' -or $saProperties -eq 'AuthorizationPermissionMismatch' ) {
+ if ($saProperties -eq 'ResourceUnavailable') {
+ $staticWebsitesState = $saProperties
+ }
+ }
+ else {
+ try {
+ # ? https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/issues/218#issuecomment-1854516882
+ if($saProperties.gettype().Name -eq 'Byte[]') {
+ $byteArray = [byte[]]$saProperties
+ $saProperties = [System.Text.Encoding]::UTF8.GetString($byteArray)
+ }
+
+ # $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
+ # $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
+ $xmlSaProperties = [xml]($saProperties -replace '^.*?<', '<') # Universal fix for all PS versions
+
+ if ($xmlSaProperties.StorageServiceProperties.StaticWebsite) {
+ if ($xmlSaProperties.StorageServiceProperties.StaticWebsite.Enabled -eq $true) {
+ $staticWebsitesState = $true
+ }
+ else {
+ $staticWebsitesState = $false
+ }
+ }
+ }
+ catch {
+ Write-Host "XMLSAPropertiesFailed: Subscription: $($subDetails.displayName) ($subscriptionId) - Storage Account: $($storageAccount.SA.name)"
+ Write-Host $($saProperties.ForEach({ [char]$_ }) -join '') -ForegroundColor Cyan
+ }
+ }
+ }
+
+ $urlCompList = "$($storageAccount.SA.Properties.primaryEndpoints.blob)?comp=list"
+ $listContainers = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $urlCompList -method 'GET' -listenOn 'Content' -currentTask "$($storageAccount.SA.name) get comp=list" -unhandledErrorAction Continue
+ if ($listContainers) {
+ if ($listContainers -eq 'AuthorizationFailure' -or $listContainers -eq 'AuthorizationPermissionDenied' -or $listContainers -eq 'ResourceUnavailable' -or $listContainers -eq 'AuthorizationPermissionMismatch') {
+ if ($listContainers -eq 'ResourceUnavailable') {
+ $listContainersSuccess = $listContainers
+ }
+ else {
+ $listContainersSuccess = $false
+ }
+ }
+ else {
+ $listContainersSuccess = $true
+ }
+
+ if ($listContainersSuccess -eq $true) {
+ # ? https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/issues/218#issuecomment-1854516882
+ if($listContainers.gettype().Name -eq 'Byte[]') {
+ $byteArray = [byte[]]$listContainers
+ $listContainers = [System.Text.Encoding]::UTF8.GetString($byteArray)
+ }
+
+ # $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
+ # $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
+ $xmlListContainers = [xml]($listContainers -replace '^.*?<', '<') # Universal fix for all PS versions
+
+ $containersCount = $xmlListContainers.EnumerationResults.Containers.Container.Count
+
+ foreach ($container in $xmlListContainers.EnumerationResults.Containers.Container) {
+ $arrayContainers += $container.Name
+ if ($container.Name -eq '$web' -and $staticWebsitesState) {
+ if ($storageAccount.SA.properties.primaryEndpoints.web) {
+ try {
+ $testStaticWebsiteResponse = Invoke-WebRequest -Uri $storageAccount.SA.properties.primaryEndpoints.web -Method 'HEAD'
+ $webSiteResponds = $true
+ }
+ catch {
+ $webSiteResponds = $false
+ }
+ }
+ }
+
+ if ($container.Properties.PublicAccess) {
+ if ($container.Properties.PublicAccess -eq 'blob') {
+ $arrayContainersAnonymousBlob += $container.Name
+ }
+ if ($container.Properties.PublicAccess -eq 'container') {
+ $arrayContainersAnonymousContainer += $container.Name
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $allowSharedKeyAccess = $storageAccount.SA.properties.allowSharedKeyAccess
+ if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.allowSharedKeyAccess)) {
+ $allowSharedKeyAccess = 'likely True'
+ }
+ $requireInfrastructureEncryption = $storageAccount.SA.properties.encryption.requireInfrastructureEncryption
+ if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.encryption.requireInfrastructureEncryption)) {
+ $requireInfrastructureEncryption = 'likely False'
+ }
+
+ $arrayResourceAccessRules = [System.Collections.ArrayList]@()
+ if ($storageAccount.SA.properties.networkAcls.resourceAccessRules) {
+ if ($storageAccount.SA.properties.networkAcls.resourceAccessRules.count -gt 0) {
+ foreach ($resourceAccessRule in $storageAccount.SA.properties.networkAcls.resourceAccessRules) {
+
+ $resourceAccessRuleResourceIdSplitted = $resourceAccessRule.resourceId -split '/'
+ $resourceType = "$($resourceAccessRuleResourceIdSplitted[6])/$($resourceAccessRuleResourceIdSplitted[7])"
+
+ [regex]$regex = '\*+'
+ #$resourceAccessRule.resourceId
+ switch ($regex.matches($resourceAccessRule.resourceId).count) {
+ { $_ -eq 1 } {
+ $null = $arrayResourceAccessRules.Add([PSCustomObject]@{
+ resourcetype = $resourceType
+ range = 'resourceGroup'
+ sort = 3
+ })
+ }
+ { $_ -eq 2 } {
+ $null = $arrayResourceAccessRules.Add([PSCustomObject]@{
+ resourcetype = $resourceType
+ range = 'subscription'
+ sort = 2
+ })
+ }
+ { $_ -eq 3 } {
+ $null = $arrayResourceAccessRules.Add([PSCustomObject]@{
+ resourcetype = $resourceType
+ range = 'tenant'
+ sort = 1
+ })
+ }
+ default {
+ $null = $arrayResourceAccessRules.Add([PSCustomObject]@{
+ resourcetype = $resourceType
+ range = 'resource'
+ resource = $resourceAccessRule.resourceId
+ sort = 0
+ })
+ }
+ }
+ }
+ }
+ }
+ $resourceAccessRulesCount = $arrayResourceAccessRules.count
+ if ($resourceAccessRulesCount -eq 0) {
+ $resourceAccessRules = ''
+ }
+ else {
+ $ht = @{}
+ foreach ($accessRulePerRange in $arrayResourceAccessRules | Group-Object -Property range | Sort-Object -Property Name -Descending) {
+
+ if ($accessRulePerRange.Name -eq 'resource') {
+ $arrayResources = @()
+ foreach ($resource in $accessRulePerRange.Group.resource | Sort-Object) {
+ $arrayResources += $resource
+ }
+ $ht.($accessRulePerRange.Name) = [array]($arrayResources)
+ }
+ else {
+ $arrayResourceTypes = @()
+ foreach ($resourceType in $accessRulePerRange.Group.resourceType | Sort-Object) {
+ $arrayResourceTypes += $resourceType
+ }
+ $ht.($accessRulePerRange.Name) = [array]($arrayResourceTypes)
+ }
+ }
+ $resourceAccessRules = $ht | ConvertTo-Json
+ }
+
+ if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.publicNetworkAccess)) {
+ $publicNetworkAccess = 'likely Enabled'
+ }
+ else {
+ $publicNetworkAccess = $storageAccount.SA.properties.publicNetworkAccess
+ }
+
+ if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.allowedCopyScope)) {
+ $allowedCopyScope = 'From any Storage Account'
+ }
+ else {
+ $allowedCopyScope = $storageAccount.SA.properties.allowedCopyScope
+ }
+
+ if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.allowCrossTenantReplication)) {
+ if ($allowedCopyScope -ne 'From any Storage Account') {
+ $allowCrossTenantReplication = "likely False (allowedCopyScope=$allowedCopyScope)"
+ }
+ else {
+ $allowCrossTenantReplication = 'likely True'
+ }
+ }
+ else {
+ $allowCrossTenantReplication = $storageAccount.SA.properties.allowCrossTenantReplication
+ }
+
+ if ($storageAccount.SA.properties.dnsEndpointType) {
+ $dnsEndpointType = $storageAccount.SA.properties.dnsEndpointType
+ }
+ else {
+ $dnsEndpointType = 'standard'
+ }
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ if ($htSACost.($storageAccount.SA.id)) {
+ $hlpCost = $htSACost.($storageAccount.SA.id)
+ $saCost = $hlpCost.costAll
+ $saCostCurrency = $hlpCost.currencyAll
+ $saCostMeterCategories = $hlpCost.meterCategoryAll
+ }
+ else {
+ $saCost = 'n/a'
+ $saCostCurrency = 'n/a'
+ $saCostMeterCategories = 'n/a'
+ }
+ }
+ else {
+ $saCost = ''
+ $saCostCurrency = ''
+ $saCostMeterCategories = ''
+ }
+
+ $temp = [System.Collections.ArrayList]@()
+ $null = $temp.Add([PSCustomObject]@{
+ storageAccount = $storageAccount.SA.name
+ kind = $storageAccount.SA.kind
+ skuName = $storageAccount.SA.sku.name
+ skuTier = $storageAccount.SA.sku.tier
+ location = $storageAccount.SA.location
+ creationTime = $storageAccount.SA.properties.creationTime
+ allowBlobPublicAccess = $storageAccount.SA.properties.allowBlobPublicAccess
+ publicNetworkAccess = $publicNetworkAccess
+ SubscriptionId = $subscriptionId
+ SubscriptionName = $subDetails.displayName
+ subscriptionQuotaId = $subDetails.subscriptionPolicies.quotaId
+ subscriptionMGPath = $htSubscriptionsMgPath.($subscriptionId).path -join '/'
+ resourceGroup = $resourceGroupName
+ networkAclsdefaultAction = $storageAccount.SA.properties.networkAcls.defaultAction
+ staticWebsitesState = $staticWebsitesState
+ staticWebsitesResponse = $webSiteResponds
+ containersCanBeListed = $listContainersSuccess
+ containersCount = $containersCount
+ containers = $arrayContainers -join "$CSVDelimiterOpposite "
+ containersAnonymousContainerCount = $arrayContainersAnonymousContainer.Count
+ containersAnonymousContainer = $arrayContainersAnonymousContainer -join "$CSVDelimiterOpposite "
+ containersAnonymousBlobCount = $arrayContainersAnonymousBlob.Count
+ containersAnonymousBlob = $arrayContainersAnonymousBlob -join "$CSVDelimiterOpposite "
+ ipRulesCount = $storageAccount.SA.properties.networkAcls.ipRules.Count
+ ipRulesIPAddressList = ($storageAccount.SA.properties.networkAcls.ipRules.value | Sort-Object) -join "$CSVDelimiterOpposite "
+ virtualNetworkRulesCount = $storageAccount.SA.properties.networkAcls.virtualNetworkRules.Count
+ virtualNetworkRulesList = ($storageAccount.SA.properties.networkAcls.virtualNetworkRules.Id | Sort-Object) -join "$CSVDelimiterOpposite "
+ resourceAccessRulesCount = $resourceAccessRulesCount
+ resourceAccessRules = $resourceAccessRules
+ bypass = ($storageAccount.SA.properties.networkAcls.bypass | Sort-Object) -join "$CSVDelimiterOpposite "
+ supportsHttpsTrafficOnly = $storageAccount.SA.properties.supportsHttpsTrafficOnly
+ minimumTlsVersion = $storageAccount.SA.properties.minimumTlsVersion
+ allowSharedKeyAccess = $allowSharedKeyAccess
+ requireInfrastructureEncryption = $requireInfrastructureEncryption
+ allowedCopyScope = $allowedCopyScope
+ allowCrossTenantReplication = $allowCrossTenantReplication
+ dnsEndpointType = $dnsEndpointType
+ usedCapacity = $storageAccount.SAUsedCapacity
+ cost = $saCost
+ metercategory = $saCostMeterCategories
+ curreny = $saCostCurrency
+ })
+
+ if ($StorageAccountAccessAnalysisSubscriptionTags[0] -ne 'undefined' -and $StorageAccountAccessAnalysisSubscriptionTags.Count -gt 0) {
+ foreach ($subTag4StorageAccountAccessAnalysis in $StorageAccountAccessAnalysisSubscriptionTags) {
+ if ($htSubscriptionTags.($subscriptionId).$subTag4StorageAccountAccessAnalysis) {
+ $temp | Add-Member -NotePropertyName "SubTag_$subTag4StorageAccountAccessAnalysis" -NotePropertyValue $($htSubscriptionTags.($subscriptionId).$subTag4StorageAccountAccessAnalysis)
+ }
+ else {
+ $temp | Add-Member -NotePropertyName "SubTag_$subTag4StorageAccountAccessAnalysis" -NotePropertyValue 'n/a'
+ }
+ }
+ }
+
+ if ($StorageAccountAccessAnalysisStorageAccountTags[0] -ne 'undefined' -and $StorageAccountAccessAnalysisStorageAccountTags.Count -gt 0) {
+ if ($storageAccount.SA.tags) {
+ $htAllSATags = @{}
+ foreach ($saTagName in ($storageAccount.SA.tags | Get-Member).where({ $_.MemberType -eq 'NoteProperty' }).Name) {
+ $htAllSATags.$saTagName = $storageAccount.SA.tags.$saTagName
+ }
+ }
+ foreach ($saTag4StorageAccountAccessAnalysis in $StorageAccountAccessAnalysisStorageAccountTags) {
+ if ($htAllSATags.$saTag4StorageAccountAccessAnalysis) {
+ $temp | Add-Member -NotePropertyName "SATag_$saTag4StorageAccountAccessAnalysis" -NotePropertyValue $($htAllSATags.$saTag4StorageAccountAccessAnalysis)
+ }
+ else {
+ $temp | Add-Member -NotePropertyName "SATag_$saTag4StorageAccountAccessAnalysis" -NotePropertyValue 'n/a'
+ }
+ }
+ }
+
+ $null = $script:arrayStorageAccountAnalysisResults.AddRange($temp)
+
+ } -ThrottleLimit $ThrottleLimit
+ }
+ else {
+ Write-Host ' No Storage Accounts present'
+ }
+
+ $end = Get-Date
+ Write-Host " Processing Storage Account Analysis duration: $((New-TimeSpan -Start $start -End $end).TotalMinutes) minutes ($((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds)"
+}
+function processTenantSummary() {
+ Write-Host ' Building TenantSummary'
+ showMemoryUsage
+ if ($getMgParentName -eq 'Tenant Root') {
+ $scopeNamingSummary = 'Tenant wide'
+ }
+ else {
+ $scopeNamingSummary = "ManagementGroup '$ManagementGroupId' and descendants wide"
+ }
+
+ #region tenantSummaryPre
+ $startRoleAssignmentsAllPre = Get-Date
+ $roleAssignmentsallCount = ($rbacBaseQuery).count
+ Write-Host " processing (pre) TenantSummary RoleAssignments (all $roleAssignmentsallCount)"
+
+ #region RelatedPolicyAssignments
+ $startRelatedPolicyAssignmentsAll = Get-Date
+ $htRoleAssignmentRelatedPolicyAssignments = @{}
+ $htOrphanedSPMI = @{}
+ foreach ($roleAssignmentIdUnique in $roleAssignmentsUniqueById) {
+
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId) = @{}
+
+ if ($htManagedIdentityForPolicyAssignment.($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId)) {
+ $hlpPolicyAssignmentId = ($htManagedIdentityForPolicyAssignment.($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId).policyAssignmentId).ToLower()
+ if (-not $htCacheAssignmentsPolicy.($hlpPolicyAssignmentId)) {
+ if ($ManagementGroupId -eq $azAPICallConf['checkContext'].Tenant.Id) {
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) {
+ if (-not ($htCacheAssignmentsPolicyOnResourceGroupsAndResources).($hlpPolicyAssignmentId)) {
+ Write-Host " !Relict detected: SP MI: $($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId) - PolicyAssignmentId: $hlpPolicyAssignmentId"
+ if (-not $htOrphanedSPMI.($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId)) {
+ $htOrphanedSPMI.($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId) = @{}
+ }
+ }
+ }
+ else {
+ Write-Host " !Relict detected: SP MI: $($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId) - PolicyAssignmentId: $hlpPolicyAssignmentId"
+ if (-not $htOrphanedSPMI.($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId)) {
+ $htOrphanedSPMI.($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId) = @{}
+ }
+ }
+ }
+ }
+ else {
+ $temp0000000000 = $htCacheAssignmentsPolicy.($hlpPolicyAssignmentId)
+ $policyAssignmentId = ($temp0000000000.Assignment.id).Tolower()
+ $policyDefinitionId = ($temp0000000000.Assignment.properties.policyDefinitionId).Tolower()
+
+
+ #builtin
+ if ($policyDefinitionId -like '/providers/Microsoft.Authorization/policy*') {
+ #policy
+ if ($policyDefinitionId -like '/providers/Microsoft.Authorization/policyDefinitions/*') {
+ $LinkOrNotLinkToAzAdvertizer = ($htCacheDefinitionsPolicy).($policyDefinitionId).LinkToAzAdvertizer
+ }
+ #policySet
+ if ($policyDefinitionId -like '/providers/Microsoft.Authorization/policySetDefinitions/*') {
+ $LinkOrNotLinkToAzAdvertizer = ($htCacheDefinitionsPolicySet).($policyDefinitionId).LinkToAzAdvertizer
+ }
+ }
+ else {
+ #policy
+ if ($policyDefinitionId -like '*/providers/Microsoft.Authorization/policyDefinitions/*') {
+ $policyDisplayName = ($htCacheDefinitionsPolicy).($policyDefinitionId).DisplayName
+
+ }
+ #policySet
+ if ($policyDefinitionId -like '*/providers/Microsoft.Authorization/policySetDefinitions/*') {
+ $policyDisplayName = ($htCacheDefinitionsPolicySet).($policyDefinitionId).DisplayName
+
+ }
+
+ $LinkOrNotLinkToAzAdvertizer = "$($policyDisplayName -replace '<', '<' -replace '>', '>') "
+ }
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).relatedPolicyAssignment = "$($policyAssignmentId) ($LinkOrNotLinkToAzAdvertizer)"
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).relatedPolicyAssignmentClear = "$($policyAssignmentId) ($policyDisplayName)"
+ }
+ }
+ else {
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).relatedPolicyAssignment = 'none'
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).relatedPolicyAssignmentClear = 'none'
+ }
+
+ if ($roleAssignmentIdUnique.RoleIsCustom -eq 'FALSE') {
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).roleType = 'Builtin'
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).roleWithWithoutLinkToAzAdvertizer = ($htCacheDefinitionsRole).($roleAssignmentIdUnique.RoleDefinitionId).LinkToAzAdvertizer
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).roleClear = $roleAssignmentIdUnique.RoleDefinitionName
+ }
+ else {
+
+ if ($roleAssigned.RoleSecurityCustomRoleOwner -eq 1) {
+ $roletype = " Custom "
+ }
+ else {
+ $roleType = 'Custom'
+ }
+
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).roleType = $roleType
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).roleWithWithoutLinkToAzAdvertizer = $roleAssignmentIdUnique.RoleDefinitionName
+ $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).roleClear = $roleAssignmentIdUnique.RoleDefinitionName
+ }
+ }
+ $endRelatedPolicyAssignmentsAll = Get-Date
+ Write-Host " RelatedPolicyAssignmentsAll duration: $((New-TimeSpan -Start $startRelatedPolicyAssignmentsAll -End $endRelatedPolicyAssignmentsAll).TotalMinutes) minutes ($((New-TimeSpan -Start $startRelatedPolicyAssignmentsAll -End $endRelatedPolicyAssignmentsAll).TotalSeconds) seconds)"
+ #endregion RelatedPolicyAssignments
+
+ #region createRBACAll
+ $cnter = 0
+ $script:rbacAll = [System.Collections.ArrayList]@()
+ $startCreateRBACAll = Get-Date
+ foreach ($rbac in $rbacBaseQuery) {
+ $cnter++
+ if ($cnter % 1000 -eq 0) {
+ $etappeRoleAssignmentsAll = Get-Date
+ Write-Host " $cnter of $roleAssignmentsallCount RoleAssignments processed; $((New-TimeSpan -Start $startRoleAssignmentsAllPre -End $etappeRoleAssignmentsAll).TotalSeconds) seconds"
+ }
+ $scope = $null
+
+ if ($rbac.RoleAssignmentPIM -eq 'true') {
+ $pim = $true
+ $pimAssignmentType = $rbac.RoleAssignmentPIMAssignmentType
+ $pimSlotStart = [string]$($rbac.RoleAssignmentPIMSlotStart)
+ $pimSlotEnd = [string]$($rbac.RoleAssignmentPIMSlotEnd)
+ }
+ else {
+ $pim = $false
+ $pimAssignmentType = ''
+ $pimSlotStart = ''
+ $pimSlotEnd = ''
+ }
+
+ if ($rbac.RoleAssignmentId -like '/providers/Microsoft.Management/managementGroups/*') {
+ $scopeTenOrMgOrSubOrRGOrRes = 'Mg'
+ if (-not [String]::IsNullOrEmpty($rbac.SubscriptionId)) {
+ $scope = "inherited $($rbac.RoleAssignmentScopeName)"
+ }
+ else {
+ if (($rbac.RoleAssignmentScopeName) -eq $rbac.MgId) {
+ $scope = 'thisScope MG'
+ }
+ else {
+ $scope = "inherited $($rbac.RoleAssignmentScopeName)"
+ }
+ }
+ }
+
+ if ($rbac.RoleAssignmentId -like '/subscriptions/*') {
+ $scope = 'thisScope Sub'
+ $scopeTenOrMgOrSubOrRGOrRes = 'Sub'
+ }
+
+ if ($rbac.RoleAssignmentId -like '/subscriptions/*/resourcegroups/*') {
+ $scope = 'thisScope Sub RG'
+ $scopeTenOrMgOrSubOrRGOrRes = 'RG'
+ }
+
+ if ($rbac.RoleAssignmentId -like '/subscriptions/*/resourcegroups/*/providers/*/providers/*') {
+ $scope = 'thisScope Sub RG Res'
+ $scopeTenOrMgOrSubOrRGOrRes = 'Res'
+ }
+
+ if ($rbac.RoleAssignmentId -like '/providers/Microsoft.Authorization/roleAssignments/*') {
+ $scope = 'inherited Tenant'
+ $scopeTenOrMgOrSubOrRGOrRes = 'Ten'
+ }
+
+ $objectTypeUserType = ''
+ if ($rbac.RoleAssignmentIdentityObjectType -eq 'User') {
+ if ($htUserTypesGuest.($rbac.RoleAssignmentIdentityObjectId)) {
+ $objectTypeUserType = 'Guest'
+ }
+ else {
+ $objectTypeUserType = 'Member'
+ }
+ }
+
+ if (-not [string]::IsNullOrEmpty($rbac.RoleDataActions) -or -not [string]::IsNullOrEmpty($rbac.RoleNotDataActions)) {
+ $roleManageData = 'true'
+ }
+ else {
+ $roleManageData = 'false'
+ }
+
+ $hlpRoleAssignmentRelatedPolicyAssignments = $htRoleAssignmentRelatedPolicyAssignments.($rbac.RoleAssignmentId)
+
+ if (-not $NoAADGroupsResolveMembers) {
+ if ($rbac.RoleAssignmentIdentityObjectType -eq 'Group') {
+
+ $grpHlpr = $htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId)
+ $null = $script:rbacAll.Add([PSCustomObject]@{
+ Level = $rbac.Level
+ RoleAssignmentId = $rbac.RoleAssignmentId
+ RoleAssignmentPIMRelated = $pim
+ RoleAssignmentPIMAssignmentType = $pimAssignmentType
+ RoleAssignmentPIMAssignmentSlotStart = $pimSlotStart
+ RoleAssignmentPIMAssignmentSlotEnd = $pimSlotEnd
+ CreatedBy = $rbac.RoleAssignmentCreatedBy
+ CreatedOn = $rbac.RoleAssignmentCreatedOn
+ UpdatedBy = $rbac.RoleAssignmentUpdatedBy
+ UpdatedOn = $rbac.RoleAssignmentUpdatedOn
+ MgId = $rbac.MgId
+ MgName = $rbac.MgName
+ MgParentId = $rbac.MgParentId
+ MgParentName = $rbac.MgParentName
+ SubscriptionId = $rbac.SubscriptionId
+ SubscriptionName = $rbac.Subscription
+ Scope = $scope
+ ScopeTenOrMgOrSubOrRGOrRes = $scopeTenOrMgOrSubOrRGOrRes
+ RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName
+ RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG
+ RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes
+ Role = $hlpRoleAssignmentRelatedPolicyAssignments.roleWithWithoutLinkToAzAdvertizer
+ RoleClear = $hlpRoleAssignmentRelatedPolicyAssignments.roleClear
+ RoleId = $rbac.RoleDefinitionId
+ RoleType = $hlpRoleAssignmentRelatedPolicyAssignments.roleType
+ RoleDataRelated = $roleManageData
+ AssignmentType = 'direct'
+ AssignmentInheritFrom = ''
+ GroupMembersCount = "$($grpHlpr.MembersAllCount) (Usr: $($grpHlpr.MembersUsersCount)$($CsvDelimiterOpposite) Grp: $($grpHlpr.MembersGroupsCount)$($CsvDelimiterOpposite) SP: $($grpHlpr.MembersServicePrincipalsCount))"
+ ObjectDisplayName = $rbac.RoleAssignmentIdentityDisplayname
+ ObjectSignInName = $rbac.RoleAssignmentIdentitySignInName
+ ObjectId = $rbac.RoleAssignmentIdentityObjectId
+ ObjectType = $rbac.RoleAssignmentIdentityObjectType
+ RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment
+ RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear
+ RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner
+ RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP
+ RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments
+ })
+
+
+ if ($grpHlpr.MembersAllCount -gt 0) {
+
+ if ($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount -le $AADGroupMembersLimit) {
+
+ foreach ($groupmember in $htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAll) {
+ if ($groupmember.'@odata.type' -eq '#microsoft.graph.user') {
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $true) {
+ $grpMemberDisplayName = 'scrubbed'
+ $grpMemberSignInName = 'scrubbed'
+ }
+ else {
+ $grpMemberDisplayName = $groupmember.displayName
+ $grpMemberSignInName = $groupmember.userPrincipalName
+ }
+ $grpMemberId = $groupmember.Id
+ $grpMemberType = 'User'
+ $grpMemberUserType = ''
+
+ if ($htUserTypesGuest.($grpMemberId)) {
+ $grpMemberUserType = 'Guest'
+ }
+ else {
+ $grpMemberUserType = 'Member'
+ }
+
+ $identityTypeFull = "$grpMemberType $grpMemberUserType"
+ }
+ if ($groupmember.'@odata.type' -eq '#microsoft.graph.group') {
+ $grpMemberDisplayName = $groupmember.displayName
+ $grpMemberSignInName = 'n/a'
+ $grpMemberId = $groupmember.Id
+ $grpMemberType = 'Group'
+ $grpMemberUserType = ''
+ $identityTypeFull = "$grpMemberType"
+ }
+ if ($groupmember.'@odata.type' -eq '#microsoft.graph.servicePrincipal') {
+ $grpMemberDisplayName = $groupmember.appDisplayName
+ $grpMemberSignInName = 'n/a'
+ $grpMemberId = $groupmember.Id
+ $grpMemberType = 'ServicePrincipal'
+ $grpMemberUserType = ''
+ $identityType = $htServicePrincipals.($grpMemberId).spTypeConcatinated
+ $identityTypeFull = "$identityType"
+ }
+
+ $null = $script:rbacAll.Add([PSCustomObject]@{
+ Level = $rbac.Level
+ RoleAssignmentId = $rbac.RoleAssignmentId
+ RoleAssignmentPIMRelated = $pim
+ RoleAssignmentPIMAssignmentType = $pimAssignmentType
+ RoleAssignmentPIMAssignmentSlotStart = $pimSlotStart
+ RoleAssignmentPIMAssignmentSlotEnd = $pimSlotEnd
+ CreatedBy = $rbac.RoleAssignmentCreatedBy
+ CreatedOn = $rbac.RoleAssignmentCreatedOn
+ UpdatedBy = $rbac.RoleAssignmentUpdatedBy
+ UpdatedOn = $rbac.RoleAssignmentUpdatedOn
+ MgId = $rbac.MgId
+ MgName = $rbac.MgName
+ MgParentId = $rbac.MgParentId
+ MgParentName = $rbac.MgParentName
+ SubscriptionId = $rbac.SubscriptionId
+ SubscriptionName = $rbac.Subscription
+ Scope = $scope
+ ScopeTenOrMgOrSubOrRGOrRes = $scopeTenOrMgOrSubOrRGOrRes
+ RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName
+ RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG
+ RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes
+ Role = $hlpRoleAssignmentRelatedPolicyAssignments.roleWithWithoutLinkToAzAdvertizer
+ RoleClear = $hlpRoleAssignmentRelatedPolicyAssignments.roleClear
+ RoleId = $rbac.RoleDefinitionId
+ RoleType = $hlpRoleAssignmentRelatedPolicyAssignments.roleType
+ RoleDataRelated = $roleManageData
+ AssignmentType = 'indirect'
+ AssignmentInheritFrom = "$($rbac.RoleAssignmentIdentityDisplayname) ($($rbac.RoleAssignmentIdentityObjectId))"
+ GroupMembersCount = "$($grpHlpr.MembersAllCount) (Usr: $($grpHlpr.MembersUsersCount)$($CsvDelimiterOpposite) Grp: $($grpHlpr.MembersGroupsCount)$($CsvDelimiterOpposite) SP: $($grpHlpr.MembersServicePrincipalsCount))"
+ ObjectDisplayName = $grpMemberDisplayName
+ ObjectSignInName = $grpMemberSignInName
+ ObjectId = $grpMemberId
+ ObjectType = $identityTypeFull
+ RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment
+ RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear
+ RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner
+ RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP
+ RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments
+ })
+ }
+ }
+ else {
+ $null = $script:rbacAll.Add([PSCustomObject]@{
+ Level = $rbac.Level
+ RoleAssignmentId = $rbac.RoleAssignmentId
+ RoleAssignmentPIMRelated = $pim
+ RoleAssignmentPIMAssignmentType = $pimAssignmentType
+ RoleAssignmentPIMAssignmentSlotStart = $pimSlotStart
+ RoleAssignmentPIMAssignmentSlotEnd = $pimSlotEnd
+ CreatedBy = $rbac.RoleAssignmentCreatedBy
+ CreatedOn = $rbac.RoleAssignmentCreatedOn
+ UpdatedBy = $rbac.RoleAssignmentUpdatedBy
+ UpdatedOn = $rbac.RoleAssignmentUpdatedOn
+ MgId = $rbac.MgId
+ MgName = $rbac.MgName
+ MgParentId = $rbac.MgParentId
+ MgParentName = $rbac.MgParentName
+ SubscriptionId = $rbac.SubscriptionId
+ SubscriptionName = $rbac.Subscription
+ Scope = $scope
+ ScopeTenOrMgOrSubOrRGOrRes = $scopeTenOrMgOrSubOrRGOrRes
+ RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName
+ RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG
+ RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes
+ Role = $hlpRoleAssignmentRelatedPolicyAssignments.roleWithWithoutLinkToAzAdvertizer
+ RoleClear = $hlpRoleAssignmentRelatedPolicyAssignments.roleClear
+ RoleId = $rbac.RoleDefinitionId
+ RoleType = $hlpRoleAssignmentRelatedPolicyAssignments.roleType
+ RoleDataRelated = $roleManageData
+ AssignmentType = 'indirect'
+ AssignmentInheritFrom = "$($rbac.RoleAssignmentIdentityDisplayname) ($($rbac.RoleAssignmentIdentityObjectId))"
+ GroupMembersCount = "$($grpHlpr.MembersAllCount) (Usr: $($grpHlpr.MembersUsersCount)$($CsvDelimiterOpposite) Grp: $($grpHlpr.MembersGroupsCount)$($CsvDelimiterOpposite) SP: $($grpHlpr.MembersServicePrincipalsCount))"
+ ObjectDisplayName = "Azure Governance Visualizer:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))"
+ ObjectSignInName = "Azure Governance Visualizer:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))"
+ ObjectId = "Azure Governance Visualizer:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))"
+ ObjectType = 'unresolved'
+ RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment
+ RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear
+ RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner
+ RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP
+ RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments
+ })
+ }
+ }
+
+ }
+ else {
+
+ if ($rbac.RoleAssignmentIdentityObjectType -eq 'ServicePrincipal') {
+ $identityType = $htServicePrincipals.($rbac.RoleAssignmentIdentityObjectId).spTypeConcatinated
+ $identityTypeFull = $identityType
+ }
+ elseif ($rbac.RoleAssignmentIdentityObjectType -eq 'Unknown') {
+ $identityTypeFull = 'Unknown'
+ }
+ else {
+ #user
+ $identityType = $rbac.RoleAssignmentIdentityObjectType
+ $identityTypeFull = "$identityType $objectTypeUserType"
+ }
+
+ $null = $script:rbacAll.Add([PSCustomObject]@{
+ Level = $rbac.Level
+ RoleAssignmentId = $rbac.RoleAssignmentId
+ RoleAssignmentPIMRelated = $pim
+ RoleAssignmentPIMAssignmentType = $pimAssignmentType
+ RoleAssignmentPIMAssignmentSlotStart = $pimSlotStart
+ RoleAssignmentPIMAssignmentSlotEnd = $pimSlotEnd
+ CreatedBy = $rbac.RoleAssignmentCreatedBy
+ CreatedOn = $rbac.RoleAssignmentCreatedOn
+ UpdatedBy = $rbac.RoleAssignmentUpdatedBy
+ UpdatedOn = $rbac.RoleAssignmentUpdatedOn
+ MgId = $rbac.MgId
+ MgName = $rbac.MgName
+ MgParentId = $rbac.MgParentId
+ MgParentName = $rbac.MgParentName
+ SubscriptionId = $rbac.SubscriptionId
+ SubscriptionName = $rbac.Subscription
+ Scope = $scope
+ ScopeTenOrMgOrSubOrRGOrRes = $scopeTenOrMgOrSubOrRGOrRes
+ RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName
+ RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG
+ RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes
+ Role = $hlpRoleAssignmentRelatedPolicyAssignments.roleWithWithoutLinkToAzAdvertizer
+ RoleClear = $hlpRoleAssignmentRelatedPolicyAssignments.roleClear
+ RoleId = $rbac.RoleDefinitionId
+ RoleType = $hlpRoleAssignmentRelatedPolicyAssignments.roleType
+ RoleDataRelated = $roleManageData
+ AssignmentType = 'direct'
+ AssignmentInheritFrom = ''
+ GroupMembersCount = ''
+ ObjectDisplayName = $rbac.RoleAssignmentIdentityDisplayname
+ ObjectSignInName = $rbac.RoleAssignmentIdentitySignInName
+ ObjectId = $rbac.RoleAssignmentIdentityObjectId
+ ObjectType = $identityTypeFull
+ RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment
+ RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear
+ RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner
+ RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP
+ RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments
+ })
+ }
+ }
+ else {
+
+ if ($rbac.RoleAssignmentIdentityObjectType -eq 'ServicePrincipal') {
+ $identityType = $htServicePrincipals.($rbac.RoleAssignmentIdentityObjectId).spTypeConcatinated
+ $identityTypeFull = $identityType
+ }
+ elseif ($rbac.RoleAssignmentIdentityObjectType -eq 'Unknown') {
+ $identityTypeFull = 'Unknown'
+ }
+ elseif ($rbac.RoleAssignmentIdentityObjectType -eq 'Group') {
+ $identityTypeFull = 'Group'
+ }
+ else {
+ #user
+ $identityType = $rbac.RoleAssignmentIdentityObjectType
+ $identityTypeFull = "$identityType $objectTypeUserType"
+ }
+
+ #noaadgroupmemberresolve
+ $null = $script:rbacAll.Add([PSCustomObject]@{
+ Level = $rbac.Level
+ RoleAssignmentId = $rbac.RoleAssignmentId
+ RoleAssignmentPIMRelated = $pim
+ RoleAssignmentPIMAssignmentType = $pimAssignmentType
+ RoleAssignmentPIMAssignmentSlotStart = $pimSlotStart
+ RoleAssignmentPIMAssignmentSlotEnd = $pimSlotEnd
+ CreatedBy = $rbac.RoleAssignmentCreatedBy
+ CreatedOn = $rbac.RoleAssignmentCreatedOn
+ UpdatedBy = $rbac.RoleAssignmentUpdatedBy
+ UpdatedOn = $rbac.RoleAssignmentUpdatedOn
+ MgId = $rbac.MgId
+ MgName = $rbac.MgName
+ MgParentId = $rbac.MgParentId
+ MgParentName = $rbac.MgParentName
+ SubscriptionId = $rbac.SubscriptionId
+ SubscriptionName = $rbac.Subscription
+ Scope = $scope
+ ScopeTenOrMgOrSubOrRGOrRes = $scopeTenOrMgOrSubOrRGOrRes
+ RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName
+ RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG
+ RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes
+ Role = $hlpRoleAssignmentRelatedPolicyAssignments.roleWithWithoutLinkToAzAdvertizer
+ RoleClear = $hlpRoleAssignmentRelatedPolicyAssignments.roleClear
+ RoleId = $rbac.RoleDefinitionId
+ RoleType = $hlpRoleAssignmentRelatedPolicyAssignments.roleType
+ RoleDataRelated = $roleManageData
+ AssignmentType = 'direct'
+ AssignmentInheritFrom = ''
+ GroupMembersCount = ''
+ ObjectDisplayName = $rbac.RoleAssignmentIdentityDisplayname
+ ObjectSignInName = $rbac.RoleAssignmentIdentitySignInName
+ ObjectId = $rbac.RoleAssignmentIdentityObjectId
+ ObjectType = $identityTypeFull
+ RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment
+ RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear
+ RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner
+ RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP
+ RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments
+ })
+ }
+ }
+ #endregion createRBACAll
+
+ #region PIMEligible
+ if (-not $NoPIMEligibility) {
+ $startPIMEnrichment = Get-Date
+ Write-Host ' Processing PIMEnrichment'
+ $PIMEligibleEnriched = [System.Collections.ArrayList]@()
+ #$tfCountCnt = 0
+ foreach ($PIMEligible in $arrayPIMEligible) {
+ #$tfCountCnt++
+ if ($PIMEligible.RoleType -eq 'BuiltInRole') {
+ $roleName = "$($PIMEligible.RoleName) "
+ }
+ else {
+ $roleName = $PIMEligible.RoleName
+ }
+ $null = $PIMEligibleEnriched.Add([PSCustomObject]@{
+ Scope = $PIMEligible.ScopeType
+ ScopeId = $PIMEligible.ScopeId
+ ScopeName = $PIMEligible.ScopeDisplayName
+ ManagementGroupId = $PIMEligible.ManagementGroupId
+ ManagementGroupDisplayName = $PIMEligible.ManagementGroupDisplayName
+ SubscriptionId = $PIMEligible.SubscriptionId
+ SubscriptionDisplayName = $PIMEligible.SubscriptionDisplayName
+ MgPath = $PIMEligible.MgPath -join '/'
+ MgLevel = $PIMEligible.MgLevel
+ Role = $roleName
+ RoleClear = $PIMEligible.RoleName
+ RoleId = $PIMEligible.RoleId
+ RoleIdGuid = $PIMEligible.RoleIdGuid
+ RoleType = $PIMEligible.RoleType
+ IdentityObjectId = $PIMEligible.IdentityObjectId
+ IdentityDisplayName = $PIMEligible.IdentityDisplayName
+ IdentitySignInName = $PIMEligible.IdentityPrincipalName
+ IdentityType = $PIMEligible.IdentityType
+ IdentityApplicability = 'direct'
+ AppliesThrough = ''
+ PIMEligibilityId = $PIMEligible.PIMId
+ PIMEligibility = $PIMEligible.PIMInheritance
+ PIMEligibilityInheritedFrom = $PIMEligible.PIMInheritedFrom
+ PIMEligibilityInheritedFromClear = $PIMEligible.PIMInheritedFromClear
+ PIMEligibilityStartDateTime = [string]$PIMEligible.PIMStartDateTime
+ PIMEligibilityEndDateTime = [string]$PIMEligible.PIMEndDateTime
+ })
+
+ if (-not $NoAADGroupsResolveMembers) {
+ if ($PIMEligible.IdentityType -eq 'Group') {
+ if ($htAADGroupsDetails.($PIMEligible.IdentityObjectId)) {
+ foreach ($groupMemberUser in $htAADGroupsDetails.($PIMEligible.IdentityObjectId).MembersUsers) {
+ #$tfCountCnt++
+ $null = $PIMEligibleEnriched.Add([PSCustomObject]@{
+ Scope = $PIMEligible.ScopeType
+ ScopeId = $PIMEligible.ScopeId
+ ScopeName = $PIMEligible.ScopeDisplayName
+ ManagementGroupId = $PIMEligible.ManagementGroupId
+ ManagementGroupDisplayName = $PIMEligible.ManagementGroupDisplayName
+ SubscriptionId = $PIMEligible.SubscriptionId
+ SubscriptionDisplayName = $PIMEligible.SubscriptionDisplayName
+ MgPath = $PIMEligible.MgPath -join '/'
+ MgLevel = $PIMEligible.MgLevel
+ Role = $roleName
+ RoleClear = $PIMEligible.RoleName
+ RoleId = $PIMEligible.RoleId
+ RoleIdGuid = $PIMEligible.RoleIdGuid
+ RoleType = $PIMEligible.RoleType
+ IdentityObjectId = $groupMemberUser.id
+ IdentityDisplayName = $groupMemberUser.displayName
+ IdentitySignInName = $groupMemberUser.userPrincipalName
+ IdentityType = "User $($groupMemberUser.userType)"
+ IdentityApplicability = 'nested'
+ AppliesThrough = "$($PIMEligible.IdentityDisplayName) ($($PIMEligible.IdentityObjectId))"
+ PIMEligibilityId = $PIMEligible.PIMId
+ PIMEligibility = $PIMEligible.PIMInheritance
+ PIMEligibilityInheritedFrom = $PIMEligible.PIMInheritedFrom
+ PIMEligibilityInheritedFromClear = $PIMEligible.PIMInheritedFromClear
+ PIMEligibilityStartDateTime = [string]$PIMEligible.PIMStartDateTime
+ PIMEligibilityEndDateTime = [string]$PIMEligible.PIMEndDateTime
+ })
+ }
+ }
+ else {
+ Write-Host "!! Unexpected: Group $($PIMEligible.IdentityDisplayName) ($($PIMEligible.IdentityObjectId)) not found in `$htAADGroupsDetails - please report back!"
+ }
+ }
+ }
+ }
+ $endPIMEnrichment = Get-Date
+ Write-Host " PIMEnrichment duration: $((New-TimeSpan -Start $startPIMEnrichment -End $endPIMEnrichment).TotalMinutes) minutes ($((New-TimeSpan -Start $startPIMEnrichment -End $endPIMEnrichment).TotalSeconds) seconds)"
+
+ if (-not $NoPIMEligibilityIntegrationRoleAssignmentsAll) {
+ $startPIMEnrichmentToRBACAll = Get-Date
+ Write-Host ' Processing PIMEnrichment to RBACAll'
+ foreach ($PIMEligibleRoleAssignment in $PIMEligibleEnriched) {
+ if ($PIMEligibleRoleAssignment.PIMEligibility -eq 'Inherited') {
+ $scope = "inherited $($PIMEligibleRoleAssignment.PIMEligibilityInheritedFromClear)"
+ }
+ else {
+ $scope = "thisScope $($PIMEligibleRoleAssignment.Scope)"
+ }
+
+ if (-not [string]::IsNullOrEmpty($htCacheDefinitionsRole.($PIMEligibleRoleAssignment.RoleId).RoleDataActions) -or -not [string]::IsNullOrEmpty($htCacheDefinitionsRole.($PIMEligibleRoleAssignment.RoleId).RoleNotDataActions)) {
+ $roleManageData = 'true'
+ }
+ else {
+ $roleManageData = 'false'
+ }
+
+ $roleCanDoRoleAssignments = $false
+ if ($htCacheDefinitionsRole.($PIMEligibleRoleAssignment.RoleId).RoleCanDoRoleAssignments) {
+ $roleCanDoRoleAssignments = 'true'
+ }
+
+ $null = $script:rbacAll.Add([PSCustomObject]@{
+ Level = $PIMEligibleRoleAssignment.MgLevel
+ RoleAssignmentId = ''
+ RoleAssignmentPIMRelated = $true
+ RoleAssignmentPIMAssignmentType = 'Eligible'
+ RoleAssignmentPIMAssignmentSlotStart = $PIMEligibleRoleAssignment.PIMEligibilityStartDateTime
+ RoleAssignmentPIMAssignmentSlotEnd = $PIMEligibleRoleAssignment.PIMEligibilityEndDateTime
+ CreatedBy = ''
+ CreatedOn = ''
+ UpdatedBy = $rbac.RoleAssignmentUpdatedBy
+ UpdatedOn = $rbac.RoleAssignmentUpdatedOn
+ MgId = $PIMEligibleRoleAssignment.ManagementGroupId
+ MgName = $PIMEligibleRoleAssignment.ManagementGroupDisplayName
+ MgParentId = '' #check
+ MgParentName = '' #check
+ SubscriptionId = $PIMEligibleRoleAssignment.SubscriptionId
+ SubscriptionName = $PIMEligibleRoleAssignment.SubscriptionDisplayName
+ Scope = $scope
+ ScopeTenOrMgOrSubOrRGOrRes = $PIMEligibleRoleAssignment.Scope
+ RoleAssignmentScopeName = $PIMEligibleRoleAssignment.Scope
+ RoleAssignmentScopeRG = ''
+ RoleAssignmentScopeRes = ''
+ Role = $PIMEligibleRoleAssignment.Role
+ RoleClear = $PIMEligibleRoleAssignment.RoleClear
+ RoleId = $PIMEligibleRoleAssignment.RoleIdGuid
+ RoleType = $PIMEligibleRoleAssignment.RoleType
+ RoleDataRelated = $roleManageData #check
+ AssignmentType = $PIMEligibleRoleAssignment.IdentityApplicability
+ AssignmentInheritFrom = $PIMEligibleRoleAssignment.AppliesThrough
+ GroupMembersCount = ''
+ ObjectDisplayName = $PIMEligibleRoleAssignment.IdentityDisplayName
+ ObjectSignInName = $PIMEligibleRoleAssignment.IdentitySignInName
+ ObjectId = $PIMEligibleRoleAssignment.IdentityObjectId
+ ObjectType = $PIMEligibleRoleAssignment.IdentityType
+ RbacRelatedPolicyAssignment = ''
+ RbacRelatedPolicyAssignmentClear = ''
+ RoleSecurityCustomRoleOwner = '' #check $rbac.RoleSecurityCustomRoleOwner
+ RoleSecurityOwnerAssignmentSP = '' #check $rbac.RoleSecurityOwnerAssignmentSP
+ RoleCanDoRoleAssignments = $roleCanDoRoleAssignments
+ })
+ }
+ $endPIMEnrichmentToRBACAll = Get-Date
+ Write-Host " PIMEnrichment to RBACAll duration: $((New-TimeSpan -Start $startPIMEnrichmentToRBACAll -End $endPIMEnrichmentToRBACAll).TotalMinutes) minutes ($((New-TimeSpan -Start $startPIMEnrichmentToRBACAll -End $endPIMEnrichmentToRBACAll).TotalSeconds) seconds)"
+ }
+ }
+ #endregion PIMEligible
+
+ Write-Host ' Processing unresoved Identities (createdBy)'
+ $startUnResolvedIdentitiesCreatedBy = Get-Date
+ #prep prepUnresoledIdentities
+ #region identitiesThatCreatedRoleAssignmentsButDontHaveARoleAssignmentThemselve
+ $script:htIdentitiesWithRoleAssignmentsUnique = @{}
+ $identitiesWithRoleAssignmentsUnique = $rbacAll.where( { $_.ObjectType -ne 'Unknown' } ) | Sort-Object -Property ObjectId -Unique | Select-Object ObjectType, ObjectDisplayName, ObjectSignInName, ObjectId
+ foreach ($identityWithRoleAssignment in $identitiesWithRoleAssignmentsUnique | Sort-Object -Property objectType) {
+
+ if (-not $htIdentitiesWithRoleAssignmentsUnique.($identityWithRoleAssignment.ObjectId)) {
+ $script:htIdentitiesWithRoleAssignmentsUnique.($identityWithRoleAssignment.ObjectId) = @{}
+
+ $arr = @()
+ $ht = [ordered]@{}
+ $identityWithRoleAssignment.psobject.properties | ForEach-Object {
+ if ($_.Value) {
+ $value = $_.Value
+ }
+ else {
+ $value = 'n/a'
+ }
+ $arr += "$($_.Name): $value"
+ $ht.($_.Name) = $value
+ }
+
+ $script:htIdentitiesWithRoleAssignmentsUnique.($identityWithRoleAssignment.ObjectId).details = $arr -join "$CsvDelimiterOpposite "
+ $script:htIdentitiesWithRoleAssignmentsUnique.($identityWithRoleAssignment.ObjectId).detailsJson = $ht
+ }
+ }
+ #endregion identitiesThatCreatedRoleAssignmentsButDontHaveARoleAssignmentThemselve
+
+ #enrich rbacAll with createdBy and UpdatedBy identity information
+ #region enrichrbacAll
+ $htNonResolvedIdentities = @{}
+ foreach ($rbac in $rbacAll) {
+ $createdBy = $rbac.createdBy
+ if (-not [string]::IsNullOrEmpty($createdBy)) {
+ if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) {
+ $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details
+ $rbac.CreatedBy = $createdBy
+ }
+ else {
+ if (-not $htNonResolvedIdentities.($rbac.createdBy)) {
+ $htNonResolvedIdentities.($rbac.createdBy) = @{}
+ }
+ }
+ }
+
+ $updatedBy = $rbac.updatedBy
+ if (-not [string]::IsNullOrEmpty($updatedBy)) {
+ if ($htIdentitiesWithRoleAssignmentsUnique.($updatedBy)) {
+ $updatedBy = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).details
+ $rbac.UpdatedBy = $updatedBy
+ }
+ else {
+ if (-not $htNonResolvedIdentities.($rbac.updatedBy)) {
+ $htNonResolvedIdentities.($rbac.updatedBy) = @{}
+ }
+ }
+ }
+ }
+ #endregion enrichrbacAll
+
+ #region nonResolvedIdentities
+ $htNonResolvedIdentitiesCount = $htNonResolvedIdentities.Count
+ if ($htNonResolvedIdentitiesCount -gt 0) {
+ Write-Host " $htNonResolvedIdentitiesCount unresolved identities that created a RBAC Role assignment (createdBy)"
+ $arrayUnresolvedIdentities = @()
+ $arrayUnresolvedIdentities = foreach ($unresolvedIdentity in $htNonResolvedIdentities.keys) {
+ if (-not [string]::IsNullOrEmpty($unresolvedIdentity)) {
+ $unresolvedIdentity
+ }
+ }
+ $arrayUnresolvedIdentitiesCount = $arrayUnresolvedIdentities.Count
+ Write-Host " $arrayUnresolvedIdentitiesCount unresolved identities that have a value"
+ if ($arrayUnresolvedIdentitiesCount -gt 0) {
+
+ $counterBatch = [PSCustomObject] @{ Value = 0 }
+ $batchSize = 1000
+ $ObjectBatch = $arrayUnresolvedIdentities | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) }
+ $ObjectBatchCount = ($ObjectBatch | Measure-Object).Count
+ $batchCnt = 0
+
+ $script:htResolvedIdentities = @{}
+
+ foreach ($batch in $ObjectBatch) {
+ $batchCnt++
+
+ $nonResolvedIdentitiesToCheck = '"{0}"' -f ($batch.Group.where({ testGuid $_ }) -join '","')
+ Write-Host " IdentitiesToCheck: Batch #$batchCnt/$($ObjectBatchCount) ($(($batch.Group).Count))"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/v1.0/directoryObjects/getByIds"
+ $method = 'POST'
+ $body = @"
+ {
+ "ids":[$($nonResolvedIdentitiesToCheck)]
+ }
+"@
+
+ function resolveIdentitiesRBAC($currentTask) {
+ $resolvedIdentities = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask
+ $resolvedIdentitiesCount = $resolvedIdentities.Count
+ Write-Host " $resolvedIdentitiesCount identities resolved"
+ if ($resolvedIdentitiesCount -gt 0) {
+
+ foreach ($resolvedIdentity in $resolvedIdentities) {
+
+ if (-not $htResolvedIdentities.($resolvedIdentity.id)) {
+
+ $script:htResolvedIdentities.($resolvedIdentity.id) = @{}
+ if ($resolvedIdentity.'@odata.type' -eq '#microsoft.graph.servicePrincipal' -or $resolvedIdentity.'@odata.type' -eq '#microsoft.graph.user') {
+ if ($resolvedIdentity.'@odata.type' -eq '#microsoft.graph.servicePrincipal') {
+ if ($resolvedIdentity.servicePrincipalType -eq 'ManagedIdentity') {
+ $miType = 'unknown'
+ foreach ($altName in $resolvedIdentity.alternativeNames) {
+ if ($altName -like 'isExplicit=*') {
+ $splitAltName = $altName.split('=')
+ if ($splitAltName[1] -eq 'true') {
+ $miType = 'Usr'
+ }
+ if ($splitAltName[1] -eq 'false') {
+ $miType = 'Sys'
+ }
+ }
+ }
+ $sptype = "MI $miType"
+ $custObjectType = "ObjectType: SP $sptype, ObjectDisplayName: $($resolvedIdentity.displayName), ObjectSignInName: n/a, ObjectId: $($resolvedIdentity.id) (r)"
+ $ht = @{}
+ $ht.'ObjectType' = "SP $sptype"
+ $ht.'ObjectDisplayName' = $($resolvedIdentity.displayName)
+ $ht.'ObjectSignInName' = 'n/a'
+ $ht.'ObjectId' = $resolvedIdentity.id
+ }
+ else {
+ if ($resolvedIdentity.servicePrincipalType -eq 'Application') {
+ $sptype = 'App'
+ if ($resolvedIdentity.appOwnerOrganizationId -eq $azAPICallConf['checkContext'].Tenant.Id) {
+ $custObjectType = "ObjectType: SP $sptype INT, ObjectDisplayName: $($resolvedIdentity.displayName), ObjectSignInName: n/a, ObjectId: $($resolvedIdentity.id) (r)"
+ $ht = @{}
+ $ht.'ObjectType' = "SP $sptype INT"
+ $ht.'ObjectDisplayName' = $($resolvedIdentity.displayName)
+ $ht.'ObjectSignInName' = 'n/a'
+ $ht.'ObjectId' = $resolvedIdentity.id
+ }
+ else {
+ $custObjectType = "ObjectType: SP $sptype EXT, ObjectDisplayName: $($resolvedIdentity.displayName), ObjectSignInName: n/a, ObjectId: $($resolvedIdentity.id) (r)"
+ $ht = @{}
+ $ht.'ObjectType' = "SP $sptype EXT"
+ $ht.'ObjectDisplayName' = $($resolvedIdentity.displayName)
+ $ht.'ObjectSignInName' = 'n/a'
+ $ht.'ObjectId' = $resolvedIdentity.id
+ }
+ }
+ else {
+ Write-Host "* * * Unexpected IdentityType $($resolvedIdentity.servicePrincipalType)"
+ }
+ }
+ $script:htResolvedIdentities.($resolvedIdentity.id).custObjectType = $custObjectType
+ $script:htResolvedIdentities.($resolvedIdentity.id).obj = $resolvedIdentity
+ }
+
+ if ($resolvedIdentity.'@odata.type' -eq '#microsoft.graph.user') {
+ if ($htParamteters.DoNotShowRoleAssignmentsUserData) {
+ $hlpObjectDisplayName = 'scrubbed'
+ $hlpObjectSigninName = 'scrubbed'
+ }
+ else {
+ $hlpObjectDisplayName = $resolvedIdentity.displayName
+ $hlpObjectSigninName = $resolvedIdentity.userPrincipalName
+ }
+ $custObjectType = "ObjectType: User, ObjectDisplayName: $hlpObjectDisplayName, ObjectSignInName: $hlpObjectSigninName, ObjectId: $($resolvedIdentity.id) (r)"
+ $ht = @{}
+ $ht.'ObjectType' = 'User'
+ $ht.'ObjectDisplayName' = $hlpObjectDisplayName
+ $ht.'ObjectSignInName' = $hlpObjectSigninName
+ $ht.'ObjectId' = $resolvedIdentity.id
+
+ $script:htResolvedIdentities.($resolvedIdentity.id).custObjectType = $custObjectType
+ $script:htResolvedIdentities.($resolvedIdentity.id).obj = $resolvedIdentity
+ }
+ if (-not $htIdentitiesWithRoleAssignmentsUnique.($resolvedIdentity.id)) {
+ $script:htIdentitiesWithRoleAssignmentsUnique.($resolvedIdentity.id) = @{}
+ $script:htIdentitiesWithRoleAssignmentsUnique.($resolvedIdentity.id).details = $custObjectType
+ $script:htIdentitiesWithRoleAssignmentsUnique.($resolvedIdentity.id).detailsJson = $ht
+ }
+ }
+
+ if ($resolvedIdentity.'@odata.type' -ne '#microsoft.graph.user' -and $resolvedIdentity.'@odata.type' -ne '#microsoft.graph.servicePrincipal') {
+ Write-Host "!!! * * * IdentityType '$($resolvedIdentity.'@odata.type')' was not considered by Azure Governance Visualizer - if you see this line, please file an issue on GitHub - thank you." -ForegroundColor Yellow
+ }
+ }
+ }
+ }
+ }
+ resolveIdentitiesRBAC -currentTask ' resolveObjectbyId RoleAssignment'
+ }
+
+ foreach ($rbac in $rbacAll.where( { $_.CreatedBy -notlike 'ObjectType*' -or $_.UpdatedBy -notlike 'ObjectType*' })) {
+ if ($rbac.CreatedBy -notlike 'ObjectType*') {
+ if ($htResolvedIdentities.($rbac.CreatedBy)) {
+ $rbac.CreatedBy = $htResolvedIdentities.($rbac.CreatedBy).custObjectType
+ }
+ else {
+ if ($rbac.RoleAssignmentPIMAssignmentType -eq 'Eligible') {
+ $rbac.CreatedBy = ''
+ }
+ else {
+ if ([string]::IsNullOrEmpty($rbac.CreatedBy)) {
+ $rbac.CreatedBy = 'IsNullOrEmpty'
+ }
+ else {
+ $rbac.CreatedBy = "$($rbac.CreatedBy)"
+ }
+ }
+ }
+ }
+ if ($rbac.UpdatedBy -notlike 'ObjectType*') {
+ if ($htResolvedIdentities.($rbac.UpdatedBy)) {
+ $rbac.UpdatedBy = $htResolvedIdentities.($rbac.UpdatedBy).custObjectType
+ }
+ else {
+ if ($rbac.RoleAssignmentPIMAssignmentType -eq 'Eligible') {
+ $rbac.UpdatedBy = ''
+ }
+ else {
+ if ([string]::IsNullOrEmpty($rbac.UpdatedBy)) {
+ $rbac.UpdatedBy = 'IsNullOrEmpty'
+ }
+ else {
+ $rbac.UpdatedBy = "$($rbac.UpdatedBy)"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ $endUnResolvedIdentitiesCreatedBy = Get-Date
+ Write-Host " UnresolvedIdentities (createdBy) duration: $((New-TimeSpan -Start $startUnResolvedIdentitiesCreatedBy -End $endUnResolvedIdentitiesCreatedBy).TotalMinutes) minutes ($((New-TimeSpan -Start $startUnResolvedIdentitiesCreatedBy -End $endUnResolvedIdentitiesCreatedBy).TotalSeconds) seconds)"
+ #endregion nonResolvedIdentities
+
+ $startRBACAllGrouping = Get-Date
+ $script:rbacAllGroupedBySubscription = $rbacAll | Group-Object -Property SubscriptionId
+ $script:rbacAllGroupedByManagementGroup = $rbacAll | Group-Object -Property MgId
+ $endRBACAllGrouping = Get-Date
+ Write-Host " RBACAll Grouping duration: $((New-TimeSpan -Start $startRBACAllGrouping -End $endRBACAllGrouping).TotalMinutes) minutes ($((New-TimeSpan -Start $startRBACAllGrouping -End $endRBACAllGrouping).TotalSeconds) seconds)"
+ $endCreateRBACAll = Get-Date
+ Write-Host " CreateRBACAll duration: $((New-TimeSpan -Start $startCreateRBACAll -End $endCreateRBACAll).TotalMinutes) minutes ($((New-TimeSpan -Start $startCreateRBACAll -End $endCreateRBACAll).TotalSeconds) seconds)"
+ #endregion tenantSummaryPre
+
+ showMemoryUsage
+
+ #region tenantSummaryPolicy
+ $htmlTenantSummary = [System.Text.StringBuilder]::new()
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+
Anything which can help you learn Azure Policy GitHub
+'@)
+
+ #region SUMMARYcustompolicies
+ $startCustPolLoop = Get-Date
+ Write-Host ' processing TenantSummary Custom Policy definitions'
+
+ $script:customPoliciesDetailed = [System.Collections.ArrayList]@()
+ $script:tenantPoliciesDetailed = [System.Collections.ArrayList]@()
+ foreach ($tenantPolicy in (($htCacheDefinitionsPolicy).Values | Sort-Object @{Expression = { $_.DisplayName } }, @{Expression = { $_.PolicyDefinitionId } })) {
+
+ #uniqueAssignments
+ $policyUniqueAssignments = $null
+ if ($htPolicyWithAssignmentsBase.($tenantPolicy.PolicyDefinitionId)) {
+ $policyUniqueAssignments = $htPolicyWithAssignmentsBase.($tenantPolicy.PolicyDefinitionId).Assignments | Sort-Object
+ $policyUniqueAssignmentsCount = ($policyUniqueAssignments).count
+ }
+ else {
+ $policyUniqueAssignmentsCount = 0
+ }
+
+ $uniqueAssignments = $null
+ if ($policyUniqueAssignmentsCount -gt 0) {
+ $policyUniqueAssignmentsList = "($($policyUniqueAssignments -join "$CsvDelimiterOpposite "))"
+ $uniqueAssignments = "$policyUniqueAssignmentsCount $policyUniqueAssignmentsList"
+ }
+ else {
+ $uniqueAssignments = $policyUniqueAssignmentsCount
+ }
+
+ #PolicyUsedInPolicySet
+ $usedInPolicySet4JSON = $null
+ $usedInPolicySet = 0
+ $usedInPolicySet4CSV = ''
+ $usedInPolicySetCount = 0
+ if (($htPoliciesUsedInPolicySets).($tenantPolicy.PolicyDefinitionId)) {
+ $hlpPolicySetUsed = ($htPoliciesUsedInPolicySets).($tenantPolicy.PolicyDefinitionId)
+ $usedInPolicySet4JSON = $hlpPolicySetUsed.PolicySetIdOnly | Sort-Object
+ $usedInPolicySet = "$(($hlpPolicySetUsed.PolicySet | Sort-Object) -join "$CsvDelimiterOpposite ")"
+ $usedInPolicySet4CSV = "$(($hlpPolicySetUsed.PolicySet4CSV | Sort-Object) -join "$CsvDelimiterOpposite ")"
+ $usedInPolicySetCount = ($hlpPolicySetUsed.PolicySet).Count
+ }
+
+ #policyEffect
+ if ($tenantPolicy.effectDefaultValue -ne 'n/a') {
+ $effect = "Default: $($tenantPolicy.effectDefaultValue); Allowed: $($tenantPolicy.effectAllowedValue)"
+ }
+ elseif ($tenantPolicy.effectFixedValue -ne 'n/a') {
+ $effect = "Fixed: $($tenantPolicy.effectFixedValue)"
+ }
+ else {
+ $effect = 'n/a'
+ }
+
+ if (($tenantPolicy.RoleDefinitionIds) -ne 'n/a') {
+ $policyRoleDefinitionsArray = @()
+ $policyRoleDefinitionsArray = foreach ($roleDefinitionId in $tenantPolicy.RoleDefinitionIds | Sort-Object) {
+ if (($htCacheDefinitionsRole).($roleDefinitionId -replace '.*/').LinkToAzAdvertizer) {
+ ($htCacheDefinitionsRole).($roleDefinitionId -replace '.*/').LinkToAzAdvertizer
+ }
+ else {
+ ($htCacheDefinitionsRole).($roleDefinitionId -replace '.*/').Name -replace '<', '<' -replace '>', '>'
+ }
+ }
+ $policyRoleDefinitionsClearArray = @()
+ $policyRoleDefinitionsClearArray = foreach ($roleDefinitionId in $tenantPolicy.RoleDefinitionIds | Sort-Object) {
+ ($htCacheDefinitionsRole).($roleDefinitionId -replace '.*/').Name
+ }
+ $policyRoleDefinitions = $policyRoleDefinitionsArray -join "$CsvDelimiterOpposite "
+ $policyRoleDefinitionsClear = $policyRoleDefinitionsClearArray -join "$CsvDelimiterOpposite "
+ }
+ else {
+ $policyRoleDefinitions = 'n/a'
+ $policyRoleDefinitionsClear = 'n/a'
+ }
+
+ # if ($tenantPolicy.Json.properties.metadata.version) {
+ # $policyVersion = $tenantPolicy.Json.properties.metadata.version
+ # }
+ # else {
+ # $policyVersion = 'n/a'
+ # }
+
+ if ($tenantPolicy.Type -eq 'Custom') {
+
+ $createdOn = ''
+ $createdBy = ''
+ $createdByJson = ''
+ $updatedOn = ''
+ $updatedBy = ''
+ $updatedByJson = ''
+ if ($tenantPolicy.Json.properties.metadata.createdOn) {
+ $createdOn = $tenantPolicy.Json.properties.metadata.createdOn
+ }
+ if ($tenantPolicy.Json.properties.metadata.createdBy) {
+ $createdBy = $tenantPolicy.Json.properties.metadata.createdBy
+ $createdByJson = $createdBy
+ if ($createdBy -ne 'n/a') {
+ if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) {
+ $createdByJson = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).detailsJson
+ $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details
+
+ }
+ }
+ }
+ if ($tenantPolicy.Json.properties.metadata.updatedOn) {
+ $updatedOn = $tenantPolicy.Json.properties.metadata.updatedOn
+ }
+ if ($tenantPolicy.Json.properties.metadata.updatedBy) {
+ $updatedBy = $tenantPolicy.Json.properties.metadata.updatedBy
+ $updatedByJson = $updatedBy
+ if ($updatedBy -ne 'n/a') {
+ if ($htIdentitiesWithRoleAssignmentsUnique.($updatedBy)) {
+ $updatedByJson = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).detailsJson
+ $updatedBy = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).details
+
+ }
+ }
+ }
+
+ $null = $script:customPoliciesDetailed.Add([PSCustomObject]@{
+ Type = 'Custom'
+ ScopeMGLevel = $tenantPolicy.ScopeMGLevel
+ Scope = $tenantPolicy.ScopeMgSub
+ ScopeId = $tenantPolicy.ScopeId
+ PolicyDisplayName = $tenantPolicy.DisplayName
+ PolicyDefinitionName = $tenantPolicy.Name
+ PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId
+ PolicyVersion = $tenantPolicy.Version
+ PolicyEffect = $effect
+ PolicyCategory = $tenantPolicy.Category
+ RoleDefinitions = $policyRoleDefinitions
+ RoleDefinitionsClear = $policyRoleDefinitionsClear
+ UniqueAssignments = $uniqueAssignments
+ UsedInPolicySetsCount = $usedInPolicySetCount
+ UsedInPolicySets = $usedInPolicySet
+ UsedInPolicySet4CSV = $usedInPolicySet4CSV
+ CreatedOn = $createdOn
+ CreatedBy = $createdBy
+ UpdatedOn = $updatedOn
+ UpdatedBy = $updatedBy
+ ALZ = $tenantPolicy.ALZ
+ ALZState = $tenantPolicy.ALZState
+ ALZLatestVer = $tenantPolicy.ALZLatestVer
+ ALZIdentificationLevel = $tenantPolicy.ALZIdentificationLevel
+ ALZPolicyName = $tenantPolicy.ALZPolicyName
+ #Json = [string]($tenantPolicy.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings)
+ })
+
+ $null = $script:tenantPoliciesDetailed.Add([PSCustomObject]@{
+ Type = 'Custom'
+ ScopeMGLevel = $tenantPolicy.ScopeMGLevel
+ Scope = $tenantPolicy.ScopeMgSub
+ ScopeId = $tenantPolicy.ScopeId
+ PolicyDisplayName = $tenantPolicy.DisplayName
+ PolicyDefinitionName = $tenantPolicy.Name
+ PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId
+ PolicyVersion = $tenantPolicy.Version
+ PolicyEffect = $effect
+ PolicyCategory = $tenantPolicy.Category
+ UniqueAssignmentsCount = $policyUniqueAssignmentsCount
+ UniqueAssignments = $policyUniqueAssignments
+ UsedInPolicySetsCount = $usedInPolicySetCount
+ UsedInPolicySets = $usedInPolicySet
+ UsedInPolicySet4CSV = $usedInPolicySet4CSV
+ UsedInPolicySet4JSON = $usedInPolicySet4JSON
+ CreatedOn = $createdOn
+ CreatedBy = $createdBy
+ CreatedByJson = $createdByJson
+ UpdatedOn = $updatedOn
+ UpdatedBy = $updatedBy
+ UpdatedByJson = $updatedByJson
+ #Json = [string]($tenantPolicy.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings)
+ Json = $tenantPolicy.Json
+ ALZ = $tenantPolicy.ALZ
+ ALZState = $tenantPolicy.ALZState
+ ALZLatestVer = $tenantPolicy.ALZLatestVer
+ ALZIdentificationLevel = $tenantPolicy.ALZIdentificationLevel
+ ALZPolicyName = $tenantPolicy.ALZPolicyName
+ })
+ }
+ else {
+ $null = $script:tenantPoliciesDetailed.Add([PSCustomObject]@{
+ Type = $tenantPolicy.Type
+ ScopeMGLevel = $null
+ Scope = $null
+ ScopeId = $null
+ PolicyDisplayName = $tenantPolicy.DisplayName
+ PolicyDefinitionName = $tenantPolicy.Name
+ PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId
+ PolicyVersion = $tenantPolicy.Version
+ PolicyEffect = $effect
+ PolicyCategory = $tenantPolicy.Category
+ UniqueAssignmentsCount = $policyUniqueAssignmentsCount
+ UniqueAssignments = $policyUniqueAssignments
+ UsedInPolicySetsCount = $usedInPolicySetCount
+ UsedInPolicySets = $usedInPolicySet
+ UsedInPolicySet4CSV = $usedInPolicySet4CSV
+ UsedInPolicySet4JSON = $usedInPolicySet4JSON
+ CreatedOn = $null
+ CreatedBy = $null
+ CreatedByJson = $null
+ UpdatedOn = $null
+ UpdatedBy = $null
+ UpdatedByJson = $null
+ #Json = [string]($tenantPolicy.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings)
+ Json = $tenantPolicy.Json
+ ALZ = $tenantPolicy.ALZ
+ ALZState = $tenantPolicy.ALZState
+ ALZLatestVer = $tenantPolicy.ALZLatestVer
+ ALZIdentificationLevel = $tenantPolicy.ALZIdentificationLevel
+ ALZPolicyName = $tenantPolicy.ALZPolicyName
+ })
+ }
+ }
+
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_PolicyDefinitions"
+ Write-Host " Exporting PolicyDefinitions CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ $tenantPoliciesDetailed | Sort-Object -Property Type, Scope, PolicyDefinitionId | Select-Object -ExcludeProperty UniqueAssignments, UsedInPolicySets, UsedInPolicySet4JSON, CreatedByJson, UpdatedByJson, Json | Export-Csv -Encoding utf8 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -NoTypeInformation
+ }
+
+ if ($getMgParentName -eq 'Tenant Root') {
+
+ if ($tenantCustomPoliciesCount -gt 0) {
+ $tfCount = $tenantCustomPoliciesCount
+ $htmlTableId = 'TenantSummary_customPolicies'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tenantCustomPoliciesCount Custom Policy definitions ($scopeNamingSummary)
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+Scope Id
+Policy DisplayName
+Policy Name
+PolicyId
+Category
+ALZ
+Effect
+Role definitions
+Unique assignments
+Used in PolicySets
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYcustompolicies = $null
+ $htmlSUMMARYcustompolicies = foreach ($customPolicy in ($customPoliciesDetailed | Sort-Object @{Expression = { $_.PolicyDisplayName } }, @{Expression = { $_.PolicyDefinitionId } })) {
+ if ($custompolicy.UsedInPolicySetsCount -gt 0) {
+ $customPolicyUsedInPolicySets = "$($customPolicy.UsedInPolicySetsCount) ($($customPolicy.UsedInPolicySets))"
+ }
+ else {
+ $customPolicyUsedInPolicySets = $($customPolicy.UsedInPolicySetsCount)
+ }
+ @"
+
+$($customPolicy.Scope)
+$($customPolicy.ScopeId)
+$($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')
+$($customPolicy.PolicyDefinitionName -replace '<', '<' -replace '>', '>')
+$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')
+$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')
+$($customPolicy.ALZ)
+$($customPolicy.PolicyEffect)
+$($customPolicy.RoleDefinitions)
+$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')
+$($customPolicyUsedInPolicySets)
+$($customPolicy.CreatedOn)
+$($customPolicy.CreatedBy)
+$($customPolicy.UpdatedOn)
+$($customPolicy.UpdatedBy)
+
+"@
+ }
+
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYcustompolicies)
+ $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlTenantSummary = [System.Text.StringBuilder]::new()
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tenantCustomPoliciesCount Custom Policy definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ #SUMMARY NOT tenant total custom policy definitions
+ else {
+ $faimage = "
"
+
+ if ($tenantCustomPoliciesCount -gt 0) {
+ $tfCount = $tenantCustomPoliciesCount
+ $customPoliciesInScopeArray = [System.Collections.ArrayList]@()
+ foreach ($customPolicy in ($tenantCustomPolicies | Sort-Object @{Expression = { $_.DisplayName } }, @{Expression = { $_.PolicyDefinitionId } })) {
+ if (($customPolicy.PolicyDefinitionId) -like '/providers/Microsoft.Management/managementGroups/*') {
+ $policyScopedMgSub = $customPolicy.PolicyDefinitionId -replace '/providers/Microsoft.Management/managementGroups/', '' -replace '/.*'
+ if ($mgsAndSubs.MgId -contains ($policyScopedMgSub)) {
+ $null = $customPoliciesInScopeArray.Add($customPolicy)
+ }
+ }
+
+ if (($customPolicy.PolicyDefinitionId) -like '/subscriptions/*') {
+ $policyScopedMgSub = $customPolicy.PolicyDefinitionId -replace '/subscriptions/', '' -replace '/.*'
+ if ($mgsAndSubs.SubscriptionId -contains ($policyScopedMgSub)) {
+ $null = $customPoliciesInScopeArray.Add($customPolicy)
+ }
+ else {
+ #Write-Host "$policyScopedMgSub NOT in Scope"
+ }
+ }
+ }
+ $customPoliciesFromSuperiorMGs = $tenantCustomPoliciesCount - (($customPoliciesInScopeArray).count)
+ }
+ else {
+ $customPoliciesFromSuperiorMGs = '0'
+ }
+
+ if ($tenantCustomPoliciesCount -gt 0) {
+ $tfCount = $tenantCustomPoliciesCount
+ $htmlTableId = 'TenantSummary_customPolicies'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tenantCustomPoliciesCount Custom Policy definitions $scopeNamingSummary ($customPoliciesFromSuperiorMGs from superior scopes)
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+Scope Id
+Policy DisplayName
+Policy Name
+PolicyId
+Category
+ALZ
+Policy Effect
+Role definitions
+Unique assignments
+Used in PolicySets
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYcustompolicies = $null
+ $htmlSUMMARYcustompolicies = foreach ($customPolicy in ($customPoliciesDetailed | Sort-Object @{Expression = { $_.PolicyDisplayName } }, @{Expression = { $_.PolicyDefinitionId } })) {
+ if ($custompolicy.UsedInPolicySetsCount -gt 0) {
+ $customPolicyUsedInPolicySets = "$($customPolicy.UsedInPolicySetsCount) ($($customPolicy.UsedInPolicySets))"
+ }
+ else {
+ $customPolicyUsedInPolicySets = $($customPolicy.UsedInPolicySetsCount)
+ }
+ @"
+
+$($customPolicy.Scope)
+$($customPolicy.ScopeId)
+$($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')
+$($customPolicy.PolicyDefinitionName -replace '<', '<' -replace '>', '>')
+$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')
+$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')
+$($customPolicy.ALZ)
+$($customPolicy.PolicyEffect)
+$($customPolicy.RoleDefinitions)
+$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')
+$($customPolicyUsedInPolicySets)
+$($customPolicy.CreatedOn)
+$($customPolicy.CreatedBy)
+$($customPolicy.UpdatedOn)
+$($customPolicy.UpdatedBy)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYcustompolicies)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tenantCustomPoliciesCount Custom Policy definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ $endCustPolLoop = Get-Date
+ Write-Host " Custom Policy processing duration: $((New-TimeSpan -Start $startCustPolLoop -End $endCustPolLoop).TotalMinutes) minutes ($((New-TimeSpan -Start $startCustPolLoop -End $endCustPolLoop).TotalSeconds) seconds)"
+ #endregion SUMMARYcustompolicies
+
+ $startcustpolorph = Get-Date
+ #region SUMMARYCustomPoliciesOrphandedTenantRoot
+ Write-Host ' processing TenantSummary Custom Policy definitions orphaned'
+ if ($getMgParentName -eq 'Tenant Root') {
+ $customPoliciesOrphaned = [System.Collections.ArrayList]@()
+ foreach ($customPolicyAll in $tenantCustomPolicies) {
+ if (($policyPolicyBaseQueryUniqueCustomDefinitions).count -eq 0) {
+ $null = $customPoliciesOrphaned.Add($customPolicyAll)
+ }
+ else {
+ if ($policyPolicyBaseQueryUniqueCustomDefinitions -notcontains ($customPolicyAll.PolicyDefinitionId)) {
+ $null = $customPoliciesOrphaned.Add($customPolicyAll)
+ }
+ }
+ }
+
+ $arrayCustomPoliciesOrphanedFinal = [System.Collections.ArrayList]@()
+ foreach ($customPolicyOrphaned in $customPoliciesOrphaned) {
+ if ($customPolicyOrphaned.Id) {
+ if (-not $htPoliciesUsedInPolicySets.($customPolicyOrphaned.Id)) {
+ $null = $arrayCustomPoliciesOrphanedFinal.Add($customPolicyOrphaned)
+ }
+ }
+ else {
+ Write-Host '!!!!!!!!!!!!!!!!!!!!! no Id'
+ Write-Host '## all:'
+ $customPoliciesOrphaned
+ Write-Host '## customPolicyOrphaned no Id:'
+ $customPolicyOrphaned
+ }
+ }
+
+ #rgchange
+ $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@()
+ foreach ($customPolicyOrphanedFinal in $arrayCustomPoliciesOrphanedFinal) {
+ if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $customPolicyOrphanedFinal.PolicyDefinitionId) {
+ $null = $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups.Add($customPolicyOrphanedFinal)
+ }
+ }
+
+ if (($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count -gt 0) {
+ $tfCount = ($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count
+ $htmlTableId = 'TenantSummary_customPoliciesOrphaned'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count) Orphaned Custom Policy definitions ($scopeNamingSummary)
+
+
Download CSV
semicolon |
comma
+
+
+
+Policy DisplayName
+PolicyId
+
+
+
+"@)
+ $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = $null
+ $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = foreach ($customPolicyOrphaned in $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) {
+ @"
+
+$($customPolicyOrphaned.DisplayName)
+$($customPolicyOrphaned.PolicyDefinitionId)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustomPoliciesOrphandedTenantRoot)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($customPoliciesOrphaned).count) Orphaned Custom Policy definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ #SUMMARY Custom Policy definitions Orphanded NOT TenantRoot
+ else {
+ $customPoliciesOrphaned = [System.Collections.ArrayList]@()
+ foreach ($customPolicyAll in $tenantCustomPolicies) {
+ if (($policyPolicyBaseQueryUniqueCustomDefinitions).count -eq 0) {
+ $null = $customPoliciesOrphaned.Add($customPolicyAll)
+ }
+ else {
+ if ($policyPolicyBaseQueryUniqueCustomDefinitions -notcontains ($customPolicyAll.PolicyDefinitionId)) {
+ $null = $customPoliciesOrphaned.Add($customPolicyAll)
+ }
+ }
+ }
+
+ $customPoliciesOrphanedInScopeArray = [System.Collections.ArrayList]@()
+ foreach ($customPolicyOrphaned in $customPoliciesOrphaned) {
+ $hlpOrphanedInScope = $customPolicyOrphaned
+ if (($hlpOrphanedInScope.PolicyDefinitionId) -like '/providers/Microsoft.Management/managementGroups/*') {
+ $policyScopedMgSub = $hlpOrphanedInScope.PolicyDefinitionId -replace '/providers/Microsoft.Management/managementGroups/' -replace '/.*'
+ if ($mgsAndSubs.MgId -contains ($policyScopedMgSub)) {
+ $null = $customPoliciesOrphanedInScopeArray.Add($hlpOrphanedInScope)
+ }
+ }
+ if (($hlpOrphanedInScope.PolicyDefinitionId) -like '/subscriptions/*') {
+ $policyScopedMgSub = $hlpOrphanedInScope.PolicyDefinitionId -replace '/subscriptions/' -replace '/.*'
+ if ($mgsAndSubs.SubscriptionId -contains ($policyScopedMgSub)) {
+ $null = $customPoliciesOrphanedInScopeArray.Add($hlpOrphanedInScope)
+ }
+ }
+ }
+
+ $arrayCustomPoliciesOrphanedFinal = [System.Collections.ArrayList]@()
+ foreach ($customPolicyOrphanedInScopeArray in $customPoliciesOrphanedInScopeArray) {
+ if (-not $htPoliciesUsedInPolicySets.($customPolicyOrphanedInScopeArray.Id)) {
+ $null = $arrayCustomPoliciesOrphanedFinal.Add($customPolicyOrphanedInScopeArray)
+ }
+ }
+
+ $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@()
+ foreach ($customPolicyOrphanedFinal in $arrayCustomPoliciesOrphanedFinal) {
+ if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $customPolicyOrphanedFinal.PolicyDefinitionId) {
+ $null = $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups.Add($customPolicyOrphanedFinal)
+ }
+ }
+
+ if (($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count -gt 0) {
+ $tfCount = ($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count
+ $htmlTableId = 'TenantSummary_customPoliciesOrphaned'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count) Orphaned Custom Policy definitions ($scopeNamingSummary)
+
+
Download CSV
semicolon |
comma
+
+
+
+Policy DisplayName
+PolicyId
+
+
+
+"@)
+ $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = $null
+ $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = foreach ($customPolicyOrphaned in $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) {
+ @"
+
+$($customPolicyOrphaned.DisplayName)
+$($customPolicyOrphaned.PolicyDefinitionId)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustomPoliciesOrphandedTenantRoot)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups.count) Orphaned Custom Policy definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ #endregion SUMMARYCustomPoliciesOrphandedTenantRoot
+ $endcustpolorph = Get-Date
+ Write-Host " processing TenantSummary Custom Policy definitions orphaned duration: $((New-TimeSpan -Start $startcustpolorph -End $endcustpolorph).TotalSeconds) seconds"
+
+ #region SUMMARYtenanttotalcustompolicySets
+ $startCustPolSetLoop = Get-Date
+ Write-Host ' processing TenantSummary Custom PolicySet definitions'
+ $script:customPolicySetsDetailed = [System.Collections.ArrayList]@()
+ $script:tenantPolicySetsDetailed = [System.Collections.ArrayList]@()
+ $custompolicySetsInScopeArray = [System.Collections.ArrayList]@()
+ foreach ($tenantPolicySet in ($tenantAllPolicySets)) {
+
+ $policySetUniqueAssignments = $policyPolicySetBaseQueryUniqueAssignments.where( { $_.PolicyDefinitionId -eq $tenantPolicySet.Id }).PolicyAssignmentId
+ $policySetUniqueAssignmentsArray = [System.Collections.ArrayList]@()
+ foreach ($policySetUniqueAssignment in $policySetUniqueAssignments) {
+ $null = $policySetUniqueAssignmentsArray.Add($policySetUniqueAssignment)
+ }
+ $policySetUniqueAssignmentsCount = ($policySetUniqueAssignments).count
+ if ($policySetUniqueAssignmentsCount -gt 0) {
+ $policySetUniqueAssignmentsList = "($($policySetUniqueAssignmentsArray -join "$CsvDelimiterOpposite "))"
+ $policySetUniqueAssignment = "$policySetUniqueAssignmentsCount $policySetUniqueAssignmentsList"
+ }
+ else {
+ $policySetUniqueAssignment = $policySetUniqueAssignmentsCount
+ }
+
+ $policySetPoliciesArray = [System.Collections.ArrayList]@()
+ $policySetPoliciesArrayClean = [System.Collections.ArrayList]@()
+ $policySetPoliciesArrayIdOnly = [System.Collections.ArrayList]@()
+ $policySetPoliciesBuiltinArrayIdOnlyCSV = [System.Collections.ArrayList]@()
+ $policySetPoliciesStaticArrayIdOnlyCSV = [System.Collections.ArrayList]@()
+ $policySetPoliciesCustomArrayIdOnlyCSV = [System.Collections.ArrayList]@()
+ foreach ($policyPolicySet in $tenantPolicySet.PolicySetPolicyIds) {
+ $hlpPolicyDef = ($htCacheDefinitionsPolicy).($policyPolicySet)
+
+ if ($hlpPolicyDef.Type -eq 'Builtin' -or $hlpPolicyDef.Type -eq 'Static') {
+ $null = $policySetPoliciesArray.Add("$($hlpPolicyDef.LinkToAzAdvertizer) ($policyPolicySet)")
+ if ($hlpPolicyDef.Type -eq 'Builtin') {
+ $null = $policySetPoliciesBuiltinArrayIdOnlyCSV.Add($policyPolicySet -replace '/providers/microsoft.authorization/policydefinitions/')
+ }
+ if ($hlpPolicyDef.Type -eq 'Static') {
+ $null = $policySetPoliciesStaticArrayIdOnlyCSV.Add($policyPolicySet -replace '/providers/microsoft.authorization/policydefinitions/')
+ }
+ }
+ else {
+ $null = $policySetPoliciesCustomArrayIdOnlyCSV.Add($policyPolicySet)
+ if ($hlpPolicyDef.DisplayName) {
+ if ([string]::IsNullOrEmpty($hlpPolicyDef.DisplayName)) {
+ $displayName = 'noDisplayNameGiven'
+ }
+ else {
+ $displayName = $hlpPolicyDef.DisplayName
+ }
+ }
+ else {
+ $displayName = 'noDisplayNameGiven'
+ }
+ $null = $policySetPoliciesArray.Add("
$($displayName -replace '<', '<' -replace '>', '>') ($policyPolicySet)")
+ }
+
+ if ($hlpPolicyDef.DisplayName) {
+ if ([string]::IsNullOrEmpty($hlpPolicyDef.DisplayName)) {
+ $displayName = 'noDisplayNameGiven'
+ }
+ else {
+ $displayName = $hlpPolicyDef.DisplayName
+ }
+ }
+ else {
+ $displayName = 'noDisplayNameGiven'
+ }
+
+ $null = $policySetPoliciesArrayClean.Add("$($displayName) ($policyPolicySet)")
+ $null = $policySetPoliciesArrayIdOnly.Add($policyPolicySet)
+ }
+
+ if ($policySetPoliciesArrayIdOnly.Count -eq 0) {
+ $policySetPoliciesArrayIdOnly = $null
+ }
+
+ if ($policySetPoliciesBuiltinArrayIdOnlyCSV.Count -eq 0) {
+ $policySetPoliciesBuiltinArrayIdOnlyCSV = $null
+ }
+ if ($policySetPoliciesStaticArrayIdOnlyCSV.Count -eq 0) {
+ $policySetPoliciesStaticArrayIdOnlyCSV = $null
+ }
+ if ($policySetPoliciesCustomArrayIdOnlyCSV.Count -eq 0) {
+ $policySetPoliciesCustomArrayIdOnlyCSV = $null
+ }
+
+ $policySetPoliciesCount = ($policySetPoliciesArray).count
+ if ($policySetPoliciesCount -gt 0) {
+ $policiesUsed = "$policySetPoliciesCount ($(($policySetPoliciesArray | Sort-Object) -join "$CsvDelimiterOpposite "))"
+ $policiesUsedClean = "$policySetPoliciesCount ($(($policySetPoliciesArrayClean | Sort-Object) -join "$CsvDelimiterOpposite "))"
+ }
+ else {
+ $policiesUsed = '0 really?'
+ $policiesUsedClean = '0 really?'
+ }
+
+ if ($tenantPolicySet.Json.properties.metadata.version) {
+ $policySetVersion = $tenantPolicySet.Json.properties.metadata.version
+ }
+ else {
+ $policySetVersion = 'n/a'
+ }
+
+ if ($tenantPolicySet.Type -eq 'Custom') {
+ #inscopeOrNot
+ if ($getMgParentName -ne 'Tenant Root') {
+ if ($mgsAndSubs.MgId -contains ($tenantPolicySet.ScopeId)) {
+ $null = $custompolicySetsInScopeArray.Add($tenantPolicySet)
+ }
+ if ($mgsAndSubs.SubscriptionId -contains ($tenantPolicySet.ScopeId)) {
+ $null = $custompolicySetsInScopeArray.Add($tenantPolicySet)
+ }
+ }
+
+ $createdOn = ''
+ $createdBy = ''
+ $createdByJson = ''
+ $updatedOn = ''
+ $updatedBy = ''
+ $updatedByJson = ''
+ if ($tenantPolicySet.Json.properties.metadata.createdOn) {
+ $createdOn = $tenantPolicySet.Json.properties.metadata.createdOn
+ }
+ if ($tenantPolicySet.Json.properties.metadata.createdBy) {
+ $createdBy = $tenantPolicySet.Json.properties.metadata.createdBy
+ $createdByJson = $createdBy
+ if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) {
+ $createdByJson = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).detailsJson
+ $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details
+ }
+ }
+ if ($tenantPolicySet.Json.properties.metadata.updatedOn) {
+ $updatedOn = $tenantPolicySet.Json.properties.metadata.updatedOn
+ }
+ if ($tenantPolicySet.Json.properties.metadata.updatedBy) {
+ $updatedBy = $tenantPolicySet.Json.properties.metadata.updatedBy
+ $updatedByJson = $updatedBy
+ if ($htIdentitiesWithRoleAssignmentsUnique.($updatedBy)) {
+ $updatedByJson = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).detailsJson
+ $updatedBy = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).details
+
+ }
+ }
+
+ $null = $script:customPolicySetsDetailed.Add([PSCustomObject]@{
+ Type = 'Custom'
+ ScopeMGLevel = $tenantPolicySet.ScopeMGLevel
+ Scope = $tenantPolicySet.ScopeMgSub
+ ScopeId = $tenantPolicySet.ScopeId
+ PolicySetDisplayName = $tenantPolicySet.DisplayName
+ PolicySetDefinitionName = $tenantPolicySet.Name
+ PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId
+ PolicySetCategory = $tenantPolicySet.Category
+ UniqueAssignments = $policySetUniqueAssignment
+ PoliciesUsed = $policiesUsed
+ PoliciesUsedClean = $policiesUsedClean
+ CreatedOn = $createdOn
+ CreatedBy = $createdBy
+ UpdatedOn = $updatedOn
+ UpdatedBy = $updatedBy
+ #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings)
+ ALZ = $tenantPolicySet.ALZ
+ ALZState = $tenantPolicySet.ALZState
+ ALZLatestVer = $tenantPolicySet.ALZLatestVer
+ ALZIdentificationLevel = $tenantPolicySet.ALZIdentificationLevel
+ ALZPolicySetName = $tenantPolicySet.ALZPolicySetName
+ })
+
+ $null = $script:tenantPolicySetsDetailed.Add([PSCustomObject]@{
+ Type = 'Custom'
+ ScopeMGLevel = $tenantPolicySet.ScopeMGLevel
+ Scope = $tenantPolicySet.ScopeMgSub
+ ScopeId = $tenantPolicySet.ScopeId
+ PolicySetDisplayName = $tenantPolicySet.DisplayName
+ PolicySetDescription = $tenantPolicySet.Description
+ PolicySetDefinitionName = $tenantPolicySet.Name
+ PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId
+ PolicySetCategory = $tenantPolicySet.Category
+ PolicySetVersion = $tenantPolicySet.Version
+ UniqueAssignmentsCount = $policySetUniqueAssignmentsCount
+ UniqueAssignments = $policySetUniqueAssignments
+ PoliciesUsedCount = $policySetPoliciesCount
+ PoliciesUsedBuiltinCount = $policySetPoliciesBuiltinArrayIdOnlyCSV.Count
+ PoliciesUsedStaticCount = $policySetPoliciesStaticArrayIdOnlyCSV.Count
+ PoliciesUsedCustomCount = $policySetPoliciesCustomArrayIdOnlyCSV.Count
+ PoliciesUsed = $policySetPoliciesArrayClean
+ PoliciesUsed4JSON = $policySetPoliciesArrayIdOnly
+ PoliciesUsedBuiltin = $policySetPoliciesBuiltinArrayIdOnlyCSV -join "$CsvDelimiterOpposite "
+ PoliciesUsedStatic = $policySetPoliciesStaticArrayIdOnlyCSV -join "$CsvDelimiterOpposite "
+ PoliciesUsedCustom = $policySetPoliciesCustomArrayIdOnlyCSV -join "$CsvDelimiterOpposite "
+ CreatedOn = $createdOn
+ CreatedBy = $createdBy
+ CreatedByJson = $createdByJson
+ UpdatedOn = $updatedOn
+ UpdatedBy = $updatedBy
+ UpdatedByJson = $updatedByJson
+ #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings)
+ Json = $tenantPolicySet.Json
+ ALZ = $tenantPolicySet.ALZ
+ ALZState = $tenantPolicySet.ALZState
+ ALZLatestVer = $tenantPolicySet.ALZLatestVer
+ ALZIdentificationLevel = $tenantPolicySet.ALZIdentificationLevel
+ ALZPolicySetName = $tenantPolicySet.ALZPolicySetName
+ })
+
+ }
+ else {
+ $null = $script:tenantPolicySetsDetailed.Add([PSCustomObject]@{
+ Type = 'BuiltIn'
+ ScopeMGLevel = $null
+ Scope = $null
+ ScopeId = $null
+ PolicySetDisplayName = $tenantPolicySet.DisplayName
+ PolicySetDescription = $tenantPolicySet.Description
+ PolicySetDefinitionName = $tenantPolicySet.Name
+ PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId
+ PolicySetCategory = $tenantPolicySet.Category
+ PolicySetVersion = $tenantPolicySet.Version
+ UniqueAssignmentsCount = $policySetUniqueAssignmentsCount
+ UniqueAssignments = $policySetUniqueAssignments
+ PoliciesUsedCount = $policySetPoliciesCount
+ PoliciesUsedBuiltinCount = $policySetPoliciesBuiltinArrayIdOnlyCSV.Count
+ PoliciesUsedStaticCount = $policySetPoliciesStaticArrayIdOnlyCSV.Count
+ PoliciesUsedCustomCount = $policySetPoliciesCustomArrayIdOnlyCSV.Count
+ PoliciesUsed = $policySetPoliciesArrayClean
+ PoliciesUsed4JSON = $policySetPoliciesArrayIdOnly
+ PoliciesUsedBuiltin = $policySetPoliciesBuiltinArrayIdOnlyCSV -join "$CsvDelimiterOpposite "
+ PoliciesUsedStatic = $policySetPoliciesStaticArrayIdOnlyCSV -join "$CsvDelimiterOpposite "
+ PoliciesUsedCustom = $policySetPoliciesCustomArrayIdOnlyCSV -join "$CsvDelimiterOpposite "
+ CreatedOn = ''
+ CreatedBy = ''
+ CreatedByJson = $null
+ UpdatedOn = ''
+ UpdatedBy = ''
+ UpdatedByJson = $null
+ #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings)
+ Json = $tenantPolicySet.Json
+ ALZ = $tenantPolicySet.ALZ
+ ALZState = $tenantPolicySet.ALZState
+ ALZLatestVer = $tenantPolicySet.ALZLatestVer
+ ALZIdentificationLevel = $tenantPolicySet.ALZIdentificationLevel
+ ALZPolicySetName = $tenantPolicySet.ALZPolicySetName
+ })
+ }
+ }
+
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_PolicySetDefinitions"
+ Write-Host " Exporting PolicySetDefinitions CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ $tenantPolicySetsDetailed | Select-Object -ExcludeProperty UniqueAssignments, PoliciesUsed, PoliciesUsed4JSON, CreatedByJson, UpdatedByJson, Json | Sort-Object -Property Type, Scope, PolicySetDefinitionId | Export-Csv -Encoding utf8 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -NoTypeInformation
+ }
+
+ if ($getMgParentName -eq 'Tenant Root') {
+ if ($tenantCustompolicySetsCount -gt $LimitPOLICYPolicySetDefinitionsScopedTenant * ($LimitCriticalPercentage / 100)) {
+ $faimage = "
"
+ }
+ else {
+ $faimage = "
"
+ }
+
+ if ($tenantCustompolicySetsCount -gt 0) {
+ $tfCount = $tenantCustompolicySetsCount
+ $htmlTableId = 'TenantSummary_customPolicySets'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$faimage $tenantCustompolicySetsCount Custom PolicySet definitions ($scopeNamingSummary) (Limit: $tenantCustompolicySetsCount/$LimitPOLICYPolicySetDefinitionsScopedTenant)
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+ScopeId
+PolicySet DisplayName
+PolicySet Name
+PolicySetId
+Category
+ALZ
+Unique assignments
+Policies used in PolicySet
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYtenanttotalcustompolicySets = $null
+ $htmlSUMMARYtenanttotalcustompolicySets = foreach ($customPolicySet in $customPolicySetsDetailed | Sort-Object @{Expression = { $_.Scope } }, @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) {
+ @"
+
+$($customPolicySet.Scope)
+$($customPolicySet.ScopeId)
+$($customPolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')
+$($customPolicySet.PolicySetDefinitionName -replace '<', '<' -replace '>', '>')
+$($customPolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')
+$($customPolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')
+$($customPolicySet.ALZ)
+$($customPolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')
+$($customPolicySet.PoliciesUsed)
+$($customPolicySet.CreatedOn)
+$($customPolicySet.CreatedBy)
+$($customPolicySet.UpdatedOn)
+$($customPolicySet.UpdatedBy)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustompolicySets)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tenantCustomPolicySetsCount Custom PolicySet definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ #SUMMARY NOT tenant total custom policySet definitions
+ else {
+ $faimage = "
"
+ if ($tenantCustompolicySetsCount -gt $LimitPOLICYPolicySetDefinitionsScopedTenant * ($LimitCriticalPercentage / 100)) {
+ $faimage = "
"
+ }
+ else {
+ $faimage = "
"
+ }
+
+ if ($tenantCustompolicySetsCount -gt 0) {
+ $custompolicySetsFromSuperiorMGs = $tenantCustompolicySetsCount - (($custompolicySetsInScopeArray).count)
+ }
+ else {
+ $custompolicySetsFromSuperiorMGs = '0'
+ }
+
+ if ($tenantCustompolicySetsCount -gt 0) {
+ $tfCount = $tenantCustompolicySetsCount
+ $htmlTableId = 'TenantSummary_customPolicySets'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$faimage $tenantCustomPolicySetsCount Custom PolicySet definitions $scopeNamingSummary ($custompolicySetsFromSuperiorMGs from superior scopes) (Limit: $tenantCustompolicySetsCount/$LimitPOLICYPolicySetDefinitionsScopedTenant)
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+Scope Id
+PolicySet DisplayName
+PolicySet Name
+PolicySetId
+Category
+ALZ
+Unique assignments
+Policies used in PolicySet
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYtenanttotalcustompolicySets = $null
+ $htmlSUMMARYtenanttotalcustompolicySets = foreach ($customPolicySet in $customPolicySetsDetailed | Sort-Object @{Expression = { $_.Scope } }, @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) {
+ @"
+
+$($customPolicySet.Scope)
+$($customPolicySet.ScopeId)
+$($customPolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')
+$($customPolicySet.PolicySetDefinitionName -replace '<', '<' -replace '>', '>')
+$($customPolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')
+$($customPolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')
+$($customPolicySet.ALZ)
+$($customPolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')
+$($customPolicySet.PoliciesUsed)
+$($customPolicySet.CreatedOn)
+$($customPolicySet.CreatedBy)
+$($customPolicySet.UpdatedOn)
+$($customPolicySet.UpdatedBy)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustompolicySets)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tenantCustomPolicySetsCount Custom PolicySet definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ $endCustPolSetLoop = Get-Date
+ Write-Host " Custom PolicySet processing duration: $((New-TimeSpan -Start $startCustPolSetLoop -End $endCustPolSetLoop).TotalMinutes) minutes ($((New-TimeSpan -Start $startCustPolSetLoop -End $endCustPolSetLoop).TotalSeconds) seconds)"
+ #endregion SUMMARYtenanttotalcustompolicySets
+
+ #region SUMMARYCustompolicySetOrphandedTenantRoot
+ Write-Host ' processing TenantSummary Custom PolicySet definitions orphaned'
+ if ($getMgParentName -eq 'Tenant Root') {
+ $custompolicySetSetsOrphaned = [System.Collections.ArrayList]@()
+ foreach ($custompolicySetAll in $tenantCustomPolicySets) {
+ if (($policyPolicySetBaseQueryUniqueCustomDefinitions).count -eq 0) {
+ $null = $custompolicySetSetsOrphaned.Add($custompolicySetAll)
+ }
+ else {
+ if ($policyPolicySetBaseQueryUniqueCustomDefinitions -notcontains ($custompolicySetAll.Id)) {
+ $null = $custompolicySetSetsOrphaned.Add($custompolicySetAll)
+ }
+ }
+ }
+
+ $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@()
+ foreach ($customPolicySetOrphaned in $custompolicySetSetsOrphaned) {
+ if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $customPolicySetOrphaned.PolicyDefinitionId) {
+ $null = $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups.Add($customPolicySetOrphaned)
+ }
+ }
+
+ if (($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count -gt 0) {
+ $tfCount = ($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count
+ $htmlTableId = 'TenantSummary_customPolicySetsOrphaned'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)
+
+
Download CSV
semicolon |
comma
+
+
+
+PolicySet DisplayName
+PolicySetId
+
+
+
+"@)
+ $htmlSUMMARYCustompolicySetOrphandedTenantRoot = $null
+ $htmlSUMMARYCustompolicySetOrphandedTenantRoot = foreach ($custompolicySetOrphaned in $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) {
+ @"
+
+$($custompolicySetOrphaned.DisplayName)
+$($custompolicySetOrphaned.PolicyDefinitionId)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustompolicySetOrphandedTenantRoot)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ #SUMMARY Custom policySetSets Orphanded NOT TenantRoot
+ else {
+ $arraycustompolicySetsOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@()
+ foreach ($custompolicySetAll in $tenantCustomPolicySets) {
+ $isOrphaned = 'unknown'
+ if (($policyPolicySetBaseQueryUniqueCustomDefinitions).count -eq 0) {
+ $isOrphaned = 'potentially'
+ }
+ else {
+ if ($policyPolicySetBaseQueryUniqueCustomDefinitions -notcontains $custompolicySetAll.Id) {
+ $isOrphaned = 'potentially'
+ }
+ }
+
+ if ($isOrphaned -eq 'potentially') {
+ $isInScope = 'unknown'
+ if ($custompolicySetAll.PolicyDefinitionId -like '/providers/Microsoft.Management/managementGroups/*') {
+ $policySetScopedMgSub = $custompolicySetAll.PolicyDefinitionId -replace '/providers/Microsoft.Management/managementGroups/', '' -replace '/.*'
+ if ($mgsAndSubs.MgId -contains ($policySetScopedMgSub)) {
+ $isInScope = 'inScope'
+ }
+ }
+ elseif ($custompolicySetAll.PolicyDefinitionId -like '/subscriptions/*') {
+ $policySetScopedMgSub = $custompolicySetAll.PolicyDefinitionId -replace '/subscriptions/', '' -replace '/.*'
+ if ($mgsAndSubs.SubscriptionId -contains ($policySetScopedMgSub)) {
+ $isInScope = 'inScope'
+ }
+ }
+ else {
+ Write-Host 'unexpected'
+ }
+
+ if ($isInScope -eq 'inScope') {
+ if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $custompolicySetAll.PolicyDefinitionId) {
+ $null = $arraycustompolicySetsOrphanedFinalIncludingResourceGroups.Add($custompolicySetAll)
+ }
+ }
+ }
+ }
+
+ if (($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count -gt 0) {
+ $tfCount = ($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count
+ $htmlTableId = 'TenantSummary_customPolicySetsOrphaned'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)
+
+
Download CSV
semicolon |
comma
+
+
+
+PolicySet DisplayName
+PolicySetId
+
+
+
+"@)
+ $htmlSUMMARYCustompolicySetOrphandedTenantRoot = $null
+ $htmlSUMMARYCustompolicySetOrphandedTenantRoot = foreach ($custompolicySetOrphaned in $arraycustompolicySetsOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) {
+ @"
+
+$($custompolicySetOrphaned.DisplayName)
+$($custompolicySetOrphaned.policyDefinitionId)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustompolicySetOrphandedTenantRoot)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ #endregion SUMMARYCustompolicySetOrphandedTenantRoot
+
+ #region SUMMARYPolicyParityCustomBuiltIn
+ Write-Host ' processing TenantSummary Policy parity custom built-in'
+
+ if ($arrayCustomBuiltInPolicyParity.Count -gt 0) {
+ $tfCount = $arrayCustomBuiltInPolicyParity.Count
+ $htmlTableId = 'TenantSummary_PolicyCustomBuiltInParity'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($arrayCustomBuiltInPolicyParity.Count) custom Policy definition(s) built-in Policy rule parity
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Policy Name
+Policy DisplayName
+Policy Category
+Policy Id
+# match built-in
+Built-In Policy
+
+
+
+"@)
+
+ $htmlSUMMARYPolicyCustomBuiltInParity = $null
+ $htmlSUMMARYPolicyCustomBuiltInParity = foreach ($entry in $arrayCustomBuiltInPolicyParity | Sort-Object -Property CustomPolicyId) {
+ $arrayBuiltinsRef = @()
+ foreach ($builtInPolicyId in $entry.BuiltInPolicyId) {
+ $arrayBuiltinsRef += "$($htCacheDefinitionsPolicy.($builtInPolicyId).DisplayName) ($($builtInPolicyId -replace '.*/'))"
+ }
+ $builtInPolicyAzA = $arrayBuiltinsRef -join ', '
+ @"
+
+$($entry.CustomPolicyName)
+$($entry.CustomPolicyDisplayName)
+$($entry.CustomPolicyCategory)
+$($entry.CustomPolicyId)
+$($entry.MatchBuiltinPolicyCount)
+$($builtInPolicyAzA)
+
+"@
+ }
+
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyCustomBuiltInParity)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No custom Policy definition(s) built-in Policy rule parity
+'@)
+ }
+ #endregion SUMMARYPolicyParityCustomBuiltIn
+
+ #region SUMMARYALZPolicies
+ Write-Host ' processing TenantSummary ALZPolicies'
+
+ if (-not $NoALZPolicyVersionChecker) {
+
+ $alzPoliciesInTenant = [System.Collections.ArrayList]@()
+ #policies
+ foreach ($policy in ($htCacheDefinitionsPolicy).Values.where({ $_.ALZ -eq $true })) {
+ if ($policy.ALZState -ne 'obsolete' -and $policy.ALZState -ne 'unknown') {
+ $ALZVersion = $alzPolicies.($policy.ALZPolicyName).latestVersion
+ $azAdvertizerUrl = "https://www.azadvertizer.net/azpolicyadvertizer/$($policy.ALZPolicyName).html"
+ }
+ else {
+ $ALZVersion = ''
+ $azAdvertizerUrl = ''
+ }
+ $null = $alzPoliciesInTenant.Add([PSCustomObject]@{
+ Type = 'Policy'
+ PolicyName = $policy.Name
+ PolicyId = $policy.PolicyDefinitionId
+ PolicyVersion = $policy.Version
+ PolicyScope = $policy.ScopeMgSub
+ PolicyScopeId = $policy.ScopeId
+ ALZPolicyName = $policy.ALZPolicyName
+ ALZVersion = $ALZVersion
+ ALZState = $policy.ALZState
+ InTenant = $true
+ DetectedBy = $policy.ALZIdentificationLevel
+ AzAdvertizerUrl = $azAdvertizerUrl
+ })
+ }
+ foreach ($alzPolicy in $alzPolicies.keys) {
+ if ($alzPolicies.($alzPolicy).status -eq 'Prod') {
+ if ($alzPoliciesInTenant.PolicyName -notcontains $alzPolicy) {
+ $null = $alzPoliciesInTenant.Add([PSCustomObject]@{
+ Type = 'Policy'
+ PolicyName = 'n/a'
+ PolicyId = 'n/a'
+ PolicyVersion = 'n/a'
+ PolicyScope = 'n/a'
+ PolicyScopeId = 'n/a'
+ ALZPolicyName = $alzPolicy
+ ALZVersion = $alzPolicies.($alzPolicy).latestVersion
+ ALZState = ''
+ InTenant = $false
+ DetectedBy = 'ALZ GitHub repository'
+ AzAdvertizerUrl = "https://www.azadvertizer.net/azpolicyadvertizer/$($alzPolicy).html"
+ })
+ }
+ }
+ }
+
+ #policysets
+ foreach ($policySet in ($htCacheDefinitionsPolicySet).Values.where({ $_.ALZ -eq $true })) {
+
+ if ($policySet.ALZState -ne 'obsolete' -and $policySet.ALZState -ne 'unknown') {
+ $ALZVersion = $alzPolicySets.($policySet.ALZPolicySetName).latestVersion
+ $azAdvertizerUrl = "https://www.azadvertizer.net/azpolicyinitiativesadvertizer/$($policySet.ALZPolicySetName).html"
+ }
+ else {
+ $ALZVersion = ''
+ $azAdvertizerUrl = ''
+ }
+ $null = $alzPoliciesInTenant.Add([PSCustomObject]@{
+ Type = 'PolicySet'
+ PolicyName = $policySet.Name
+ PolicyId = $policySet.PolicyDefinitionId
+ PolicyVersion = $policySet.Version
+ PolicyScope = $policySet.ScopeMgSub
+ PolicyScopeId = $policySet.ScopeId
+ ALZPolicyName = $policySet.ALZPolicySetName
+ ALZVersion = $ALZVersion
+ ALZState = $policySet.ALZState
+ InTenant = $true
+ DetectedBy = $policySet.ALZIdentificationLevel
+ AzAdvertizerUrl = $azAdvertizerUrl
+ })
+ }
+
+ foreach ($alzPolicySet in $alzPolicySets.keys) {
+ if ($alzPolicySets.($alzPolicySet).status -eq 'Prod') {
+ if ($alzPoliciesInTenant.PolicyName -notcontains $alzPolicySet) {
+ $null = $alzPoliciesInTenant.Add([PSCustomObject]@{
+ Type = 'PolicySet'
+ PolicyName = 'n/a'
+ PolicyId = 'n/a'
+ PolicyVersion = 'n/a'
+ PolicyScope = 'n/a'
+ PolicyScopeId = 'n/a'
+ ALZPolicyName = $alzPolicySet
+ ALZVersion = $alzPolicySets.($alzPolicySet).latestVersion
+ ALZState = ''
+ InTenant = $false
+ DetectedBy = 'ALZ GitHub repository'
+ AzAdvertizerUrl = "https://www.azadvertizer.net/azpolicyinitiativesadvertizer/$($alzPolicySet).html"
+ })
+ }
+ }
+ }
+
+ if ($alzPoliciesInTenant.Count -gt 0) {
+ $tfCount = $alzPoliciesInTenant.Count
+ $htmlTableId = 'TenantSummary_ALZPolicies'
+ $abbrALZ = "
"
+ [void]$htmlTenantSummary.AppendLine(@"
+
Azure Landing Zones (ALZ) Policy Version Checker
+
+
+
Azure Landing Zones (ALZ) GitHub
+
Download CSV
semicolon |
comma
+
+
+
+Type
+Policy Name (Id)
+Policy Version
+Policy Scope
+Policy Scope Id
+ALZ Policy Name (Id)
+ALZ Policy Version
+ALZ State$($abbrALZ)
+Exists in tenant
+Detection method
+AzAdvertizer Link
+
+
+
+"@)
+
+ $htmlSUMMARYALZPolicyVersionChecker = $null
+ $exemptionData4CSVExport = [System.Collections.ArrayList]@()
+ $alzPoliciesInTenantSorted = $alzPoliciesInTenant | Sort-Object -Property PolicyName, PolicyId, ALZPolicyName, Type
+ $htmlSUMMARYALZPolicyVersionChecker = foreach ($entry in $alzPoliciesInTenantSorted) {
+ if ([string]::IsNullOrWhiteSpace($entry.AzAdvertizerUrl)) {
+ $link = ''
+ }
+ else {
+ $link = "AzA Link "
+ }
+ @"
+
+$($entry.Type)
+$($entry.PolicyName)
+$($entry.PolicyVersion)
+$($entry.PolicyScope)
+$($entry.PolicyScopeId)
+$($entry.ALZPolicyName)
+$($entry.ALZVersion)
+$($entry.ALZState)
+$($entry.InTenant)
+$($entry.DetectedBy)
+$link
+
+"@
+ }
+
+ if (-not $NoCsvExport) {
+ Write-Host "Exporting 'Azure Landing Zones (ALZ) Policy Version Checker' CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_ALZPolicyVersionChecker.csv'"
+ $alzPoliciesInTenantSorted | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_ALZPolicyVersionChecker.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYALZPolicyVersionChecker)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
Azure Landing Zones (ALZ) Policy Version Checker
+'@)
+ }
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Azure Landing Zones (ALZ) Policy Version Checker (parameter -NoALZPolicyVersionChecker = $NoALZPolicyVersionChecker)
+"@)
+ }
+ #endregion SUMMARYALZPolicies
+
+ $startcustpolsetdeprpol = Get-Date
+ #region SUMMARYPolicySetsDeprecatedPolicy
+ Write-Host ' processing TenantSummary Custom PolicySet definitions using deprected Policy'
+ $policySetsDeprecated = [System.Collections.ArrayList]@()
+ $customPolicySetsCount = ($tenantCustomPolicySets).count
+ if ($customPolicySetsCount -gt 0) {
+ foreach ($polSetDef in $tenantCustomPolicySets) {
+ foreach ($polsetPolDefId in $polSetDef.PolicySetPolicyIds) {
+ $hlpDeprecatedPolicySet = (($htCacheDefinitionsPolicy).($polsetPolDefId))
+ if ($hlpDeprecatedPolicySet.Type -eq 'BuiltIn') {
+ if ($hlpDeprecatedPolicySet.Deprecated -eq $true -or ($hlpDeprecatedPolicySet.DisplayName).StartsWith('[Deprecated]', 'CurrentCultureIgnoreCase')) {
+ $null = $policySetsDeprecated.Add([PSCustomObject]@{
+ PolicySetDisplayName = $polSetDef.DisplayName
+ PolicySetDefinitionId = $polSetDef.PolicyDefinitionId
+ PolicyDisplayName = $hlpDeprecatedPolicySet.DisplayName
+ PolicyId = $hlpDeprecatedPolicySet.Id
+ DeprecatedProperty = $hlpDeprecatedPolicySet.Deprecated
+ })
+ }
+ }
+ }
+ }
+ }
+
+ if (($policySetsDeprecated).count -gt 0) {
+ $tfCount = ($policySetsDeprecated).count
+ $htmlTableId = 'TenantSummary_policySetsDeprecated'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($policySetsDeprecated).count) Custom PolicySet definitions / deprecated built-in Policy
+
+
+
Download CSV
semicolon |
comma
+
+
+
+PolicySet DisplayName
+PolicySetId
+Policy DisplayName
+PolicyId
+Deprecated Property
+
+
+
+"@)
+ $htmlSUMMARYPolicySetsDeprecatedPolicy = $null
+ $htmlSUMMARYPolicySetsDeprecatedPolicy = foreach ($policySetDeprecated in $policySetsDeprecated | Sort-Object @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) {
+
+ if ($policySetDeprecated.DeprecatedProperty -eq $true) {
+ $deprecatedProperty = 'true'
+ }
+ else {
+ $deprecatedProperty = 'false'
+ }
+ @"
+
+$($policySetDeprecated.PolicySetDisplayName -replace '<', '<' -replace '>', '>')
+$($policySetDeprecated.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')
+$($policySetDeprecated.PolicyDisplayName -replace '<', '<' -replace '>', '>')
+$($policySetDeprecated.PolicyId -replace '<', '<' -replace '>', '>')
+$deprecatedProperty
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicySetsDeprecatedPolicy)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($policySetsDeprecated).count) PolicySets / deprecated built-in Policy
+"@)
+ }
+ #endregion SUMMARYPolicySetsDeprecatedPolicy
+ $endcustpolsetdeprpol = Get-Date
+ Write-Host " processing PolicySetsDeprecatedPolicy duration: $((New-TimeSpan -Start $startcustpolsetdeprpol -End $endcustpolsetdeprpol).TotalSeconds) seconds"
+
+ $startcustpolassdeprpol = Get-Date
+ #region SUMMARYPolicyAssignmentsDeprecatedPolicy
+ Write-Host ' processing TenantSummary PolicyAssignments using deprecated Policy'
+ $policyAssignmentsDeprecated = [System.Collections.ArrayList]@()
+ foreach ($policyAssignmentAll in ($htCacheAssignmentsPolicy).Values) {
+
+ $hlpAssignmentDeprecatedPolicy = $policyAssignmentAll.Assignment
+ $hlpPolicyDefinitionId = ($hlpAssignmentDeprecatedPolicy.properties.policyDefinitionId).ToLower()
+ #policySet
+ if ($($htCacheDefinitionsPolicySet).(($hlpPolicyDefinitionId))) {
+ foreach ($polsetPolDefId in $($htCacheDefinitionsPolicySet).(($hlpPolicyDefinitionId)).PolicySetPolicyIds) {
+ $hlpDeprecatedAssignment = (($htCacheDefinitionsPolicy).(($polsetPolDefId)))
+ if ($hlpDeprecatedAssignment.type -eq 'BuiltIn') {
+ if ($hlpDeprecatedAssignment.Deprecated -eq $true) {
+ $null = $policyAssignmentsDeprecated.Add([PSCustomObject]@{
+ PolicyAssignmentDisplayName = $hlpAssignmentDeprecatedPolicy.properties.displayName
+ PolicyAssignmentId = ($hlpAssignmentDeprecatedPolicy.id).Tolower()
+ PolicyDisplayName = $hlpDeprecatedAssignment.DisplayName
+ PolicyId = $hlpDeprecatedAssignment.Id
+ PolicySetDisplayName = ($htCacheDefinitionsPolicySet).(($hlpPolicyDefinitionId)).DisplayName
+ PolicySetId = ($htCacheDefinitionsPolicySet).(($hlpPolicyDefinitionId)).PolicyDefinitionId
+ PolicyType = 'PolicySet'
+ DeprecatedProperty = $hlpDeprecatedAssignment.Deprecated
+ })
+ }
+ }
+ }
+ }
+
+ #Policy
+ $hlpDeprecatedAssignmentPol = ($htCacheDefinitionsPolicy).(($hlpPolicyDefinitionId))
+ if ($hlpDeprecatedAssignmentPol) {
+ if ($hlpDeprecatedAssignmentPol.type -eq 'BuiltIn') {
+ if ($hlpDeprecatedAssignmentPol.Deprecated -eq $true) {
+ $null = $policyAssignmentsDeprecated.Add([PSCustomObject]@{
+ PolicyAssignmentDisplayName = $hlpAssignmentDeprecatedPolicy.properties.displayName
+ PolicyAssignmentId = ($hlpAssignmentDeprecatedPolicy.id).Tolower()
+ PolicyDisplayName = $hlpDeprecatedAssignmentPol.DisplayName
+ PolicyId = $hlpDeprecatedAssignmentPol.Id
+ PolicyType = 'Policy'
+ DeprecatedProperty = $hlpDeprecatedAssignmentPol.Deprecated
+ PolicySetDisplayName = 'n/a'
+ PolicySetId = 'n/a'
+ })
+ }
+ }
+ }
+ }
+
+
+ if (($policyAssignmentsDeprecated).count -gt 0) {
+ $tfCount = ($policyAssignmentsDeprecated).count
+ $htmlTableId = 'TenantSummary_policyAssignmentsDeprecated'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($policyAssignmentsDeprecated).count) Policy assignments / deprecated built-in Policy
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Policy Assignment DisplayName
+Policy AssignmentId
+Policy/PolicySet
+PolicySet DisplayName
+PolicySetId
+Policy DisplayName
+PolicyId
+Deprecated Property
+
+
+
+"@)
+ $htmlSUMMARYPolicyAssignmentsDeprecatedPolicy = $null
+ $htmlSUMMARYPolicyAssignmentsDeprecatedPolicy = foreach ($policyAssignmentDeprecated in $policyAssignmentsDeprecated | Sort-Object @{Expression = { $_.PolicyAssignmentDisplayName } }, @{Expression = { $_.PolicyAssignmentId } }) {
+ @"
+
+$($policyAssignmentDeprecated.PolicyAssignmentDisplayName -replace '<', '<' -replace '>', '>')
+$($policyAssignmentDeprecated.PolicyAssignmentId -replace '<', '<' -replace '>', '>')
+$($policyAssignmentDeprecated.PolicyType)
+$($policyAssignmentDeprecated.PolicySetDisplayName -replace '<', '<' -replace '>', '>')
+$($policyAssignmentDeprecated.PolicySetId -replace '<', '<' -replace '>', '>')
+$($policyAssignmentDeprecated.PolicyDisplayName -replace '<', '<' -replace '>', '>')
+$($policyAssignmentDeprecated.PolicyId -replace '<', '<' -replace '>', '>')
+$($policyAssignmentDeprecated.DeprecatedProperty)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyAssignmentsDeprecatedPolicy)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($policyAssignmentsDeprecated).count) Policy assignments / deprecated built-in Policy
+"@)
+ }
+ #endregion SUMMARYPolicyAssignmentsDeprecatedPolicy
+ $endcustpolassdeprpol = Get-Date
+ Write-Host " processing PolicyAssignmentsDeprecatedPolicy duration: $((New-TimeSpan -Start $startcustpolassdeprpol -End $endcustpolassdeprpol).TotalSeconds) seconds"
+
+ #region SUMMARYPolicyExemptions
+ Write-Host ' processing TenantSummary Policy exemptions'
+ $policyExemptionsCount = ($htPolicyAssignmentExemptions.Keys).Count
+
+ if ($policyExemptionsCount -gt 0) {
+ $tfCount = $policyExemptionsCount
+ $htmlTableId = 'TenantSummary_policyExemptions'
+
+ $expiredExemptionsCount = ($htPolicyAssignmentExemptions.Keys | Where-Object { $htPolicyAssignmentExemptions.($_).exemption.properties.expiresOn -and $htPolicyAssignmentExemptions.($_).exemption.properties.expiresOn -lt (Get-Date).ToUniversalTime() } | Measure-Object).count
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($policyExemptionsCount) Policy exemptions | Expired: $($expiredExemptionsCount)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+Management Group Id
+Management Group Name
+SubscriptionId
+Subscription Name
+ResourceGroup
+ResourceName / ResourceType
+Exemption name
+Exemption description
+Category
+ExpiresOn (UTC)
+Exemption Id
+Policy AssignmentId
+Policy Type
+Policy
+Exempted Set Policies
+CreatedBy
+CreatedAt
+LastModifiedBy
+LastModifiedAt
+
+
+
+"@)
+
+ $htmlSUMMARYPolicyExemptions = $null
+ $exemptionData4CSVExport = [System.Collections.ArrayList]@()
+ $htmlSUMMARYPolicyExemptions = foreach ($policyExemption in $htPolicyAssignmentExemptions.Keys | Sort-Object) {
+ $exemption = $htPolicyAssignmentExemptions.$policyExemption.exemption
+ if ($exemption.properties.expiresOn) {
+ $exemptionExpiresOnFormated = (($exemption.properties.expiresOn))
+ if ($exemption.properties.expiresOn -gt (Get-Date).ToUniversalTime()) {
+ $exemptionExpiresOn = $exemptionExpiresOnFormated
+ }
+ else {
+ $exemptionExpiresOn = "expired $($exemptionExpiresOnFormated)"
+ }
+ }
+ else {
+ $exemptionExpiresOn = 'n/a'
+ }
+
+ $splitExemptionId = ($exemption.Id).Split('/')
+ if (($exemption.Id) -like '/subscriptions/*') {
+
+ switch (($splitExemptionId).Count - 1) {
+ #sub
+ 6 {
+ $exemptionScope = 'Sub'
+ $subId = $splitExemptionId[2]
+ $subdetails = $htSubDetails.($subId).details
+ $mgId = $subdetails.MgId
+ $mgName = $subdetails.MgName
+ $subName = $subdetails.Subscription
+ $rgName = ''
+ $resName = ''
+ }
+
+ #rg
+ 8 {
+ $exemptionScope = 'RG'
+ $subId = $splitExemptionId[2]
+ $subdetails = $htSubDetails.($subId).details
+ $mgId = $subdetails.MgId
+ $mgName = $subdetails.MgName
+ $subName = $subdetails.Subscription
+ $rgName = $splitExemptionId[4]
+ $resName = ''
+ }
+
+ #res
+ 12 {
+ $exemptionScope = 'Res'
+ $subId = $splitExemptionId[2]
+ $subdetails = $htSubDetails.($subId).details
+ $mgId = $subdetails.MgId
+ $mgName = $subdetails.MgName
+ $subName = $subdetails.Subscription
+ $rgName = $splitExemptionId[4]
+ $resName = "$($splitExemptionId[8]) / $($splitExemptionId[6..7] -join '/')"
+ }
+ }
+ }
+ else {
+ $exemptionScope = 'MG'
+ $mgId = $splitExemptionId[4]
+ $mgdetails = $htMgDetails.($mgId).details
+ $mgName = $mgdetails.MgName
+ $subId = ''
+ $subName = ''
+ $rgName = ''
+ $resName = ''
+ }
+
+ $policyType = 'unknown'
+ $policy = 'unknown'
+ $arrayExemptedPolicies = @()
+ $arrayExemptedPoliciesCSV = @()
+ $policiesExempted = $null
+ $policiesExemptedCSV = $null
+ $policiesExemptedCSVCount = $null
+ $policiesTotalCount = $null
+ if ($htCacheAssignmentsPolicy.(($exemption.properties.policyAssignmentId).tolower()).Assignment.properties.policyDefinitionId) {
+ $policyDefinitionId = $htCacheAssignmentsPolicy.(($exemption.properties.policyAssignmentId).tolower()).Assignment.properties.policyDefinitionId
+
+ if ($policyDefinitionId -like '*/providers/Microsoft.Authorization/policyDefinitions/*') {
+ $policyType = 'Policy'
+ if ($htCacheDefinitionsPolicy.($policyDefinitionId.tolower())) {
+ $policyDetail = $htCacheDefinitionsPolicy.($policyDefinitionId.tolower())
+ if ($policyDetail.Type -eq 'BuiltIn') {
+ $policy = $policyDetail.LinkToAzAdvertizer
+ }
+ else {
+ $policy = "$($policyDetail.DisplayName) ($($policyDetail.Id))"
+ }
+ $policiesExempted = $null
+ $policyClear = "$($policyDetail.DisplayName) ($($policyDetail.Id))"
+ }
+ }
+
+ if ($policyDefinitionId -like '*/providers/Microsoft.Authorization/policySetDefinitions/*') {
+ $policyType = 'PolicySet'
+ if ($htCacheDefinitionsPolicySet.($policyDefinitionId.tolower())) {
+ $policyDetail = $htCacheDefinitionsPolicySet.($policyDefinitionId.tolower())
+ if ($policyDetail.Type -eq 'BuiltIn') {
+ $policy = $policyDetail.LinkToAzAdvertizer
+ }
+ else {
+ $policy = "$($policyDetail.DisplayName) ($($policyDetail.Id))"
+ }
+ $policiesTotalCount = $htCacheDefinitionsPolicySet.($policyDefinitionId.tolower()).PolicySetPolicyRefIds.Count
+ if ($exemption.properties.policyDefinitionReferenceIds.Count -gt 0) {
+ foreach ($exemptedRefId in $exemption.properties.policyDefinitionReferenceIds) {
+ $policyExempted = 'unknown'
+ $policyExemptedCSV = 'unknown'
+ if ($htCacheDefinitionsPolicySet.($policyDefinitionId.tolower()).PolicySetPolicyRefIds.($exemptedRefId)) {
+ $exemptedPolicyId = $htCacheDefinitionsPolicySet.($policyDefinitionId.tolower()).PolicySetPolicyRefIds.($exemptedRefId)
+ if ($htCacheDefinitionsPolicy.($exemptedPolicyId.tolower())) {
+ $policyExemptedDetail = $htCacheDefinitionsPolicy.($exemptedPolicyId.tolower())
+ if ($policyExemptedDetail.Type -eq 'BuiltIn') {
+ $policyExempted = $policyExemptedDetail.LinkToAzAdvertizer
+ }
+ else {
+ $policyExempted = "$($policyExemptedDetail.DisplayName) ($($policyExemptedDetail.Id))"
+ }
+ $policyExemptedCSV = "$($policyExemptedDetail.DisplayName) ($($policyExemptedDetail.Id))"
+
+ }
+ }
+ $arrayExemptedPolicies += $policyExempted
+ $arrayExemptedPoliciesCSV += $policyExemptedCSV
+ }
+
+ $policiesExempted = "$($arrayExemptedPolicies.Count)/$($policiesTotalCount) ( $(($arrayExemptedPolicies | Sort-Object) -join ' '))"
+ $policiesExemptedCSV = ($arrayExemptedPoliciesCSV | Sort-Object) -join "$CsvDelimiterOpposite "
+ $policiesExemptedCSVCount = $arrayExemptedPoliciesCSV.Count
+ }
+ else {
+ $policiesExempted = "all $policiesTotalCount"
+ $policiesExemptedCSV = "all $policiesTotalCount"
+ $policiesExemptedCSVCount = $policiesTotalCount
+ }
+
+ $policyClear = "$($policyDetail.DisplayName) ($($policyDetail.Id))"
+ }
+ }
+
+ }
+
+ if (-not $NoCsvExport) {
+ $null = $exemptionData4CSVExport.Add([PSCustomObject]@{
+ Scope = $exemptionScope
+ ManagementGroupId = $mgId
+ ManagementGroupName = $mgName
+ SubscriptionId = $subId
+ SubscriptionName = $subName
+ ResourceGroup = $rgName
+ ResourceName_ResourceType = $resName
+ ExemptionName = $exemption.properties.DisplayName
+ ExemptionDescription = $exemption.properties.Description
+ Category = $exemption.properties.exemptionCategory
+ ExpiresOn_UTC = $exemptionExpiresOn
+ ExemptionId = $exemption.Id
+ PolicyAssignmentId = $exemption.properties.policyAssignmentId
+ PolicyType = $policyType
+ Policy = $policyClear
+ PoliciesTotalCount = $policiesTotalCount
+ PoliciesExemptedCount = $policiesExemptedCSVCount
+ PoliciesExempted = $policiesExemptedCSV
+ CreatedBy = "$($exemption.systemData.createdBy) ($($exemption.systemData.createdByType))"
+ CreatedAt = $exemption.systemData.createdAt.ToString('yyyy-MM-dd HH:mm:ss')
+ LastModifiedBy = "$($exemption.systemData.lastModifiedBy) ($($exemption.systemData.lastModifiedByType))"
+ LastModifiedAt = $exemption.systemData.lastModifiedAt.ToString('yyyy-MM-dd HH:mm:ss')
+ })
+ }
+
+ @"
+
+$($exemptionScope)
+$($mgId)
+$($mgName -replace '<', '<' -replace '>', '>')
+$($subId)
+$($subName)
+$($rgName)
+$($resName)
+$($exemption.properties.DisplayName -replace '<', '<' -replace '>', '>')
+$($exemption.properties.Description -replace '<', '<' -replace '>', '>')
+$($exemption.properties.exemptionCategory -replace '<', '<' -replace '>', '>')
+$($exemptionExpiresOn)
+$($exemption.Id)
+$($exemption.properties.policyAssignmentId -replace '<', '<' -replace '>', '>')
+$($policyType)
+$($policy)
+$($policiesExempted)
+$($exemption.systemData.createdBy) ($($exemption.systemData.createdByType))
+$($exemption.systemData.createdAt.ToString('yyyy-MM-dd HH:mm:ss'))
+$($exemption.systemData.lastModifiedBy) ($($exemption.systemData.lastModifiedByType))
+$($exemption.systemData.lastModifiedAt.ToString('yyyy-MM-dd HH:mm:ss'))
+
+"@
+ }
+
+ if (-not $NoCsvExport) {
+ Write-Host "Exporting PolicyExemptions CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_PolicyExemptions.csv'"
+ $exemptionData4CSVExport | Sort-Object -Property PolicyAssignmentId, Id | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_PolicyExemptions.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyExemptions)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($policyExemptionsCount) Policy exemptions
+"@)
+ }
+ #endregion SUMMARYPolicyExemptions
+
+ #region SUMMARYPolicyAssignmentsOrphaned
+ Write-Host ' processing TenantSummary PolicyAssignments orphaned'
+
+ if ($policyAssignmentsOrphanedCount -gt 0) {
+ $tfCount = $policyAssignmentsOrphanedCount
+ $htmlTableId = 'TenantSummary_policyAssignmentsOrphaned'
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($policyAssignmentsOrphanedCount) Policy assignments orphaned
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Policy AssignmentId
+Policy/Set definition
+
+
+
+"@)
+
+ $htmlSUMMARYPolicyassignmentsOrphaned = $null
+ $htmlSUMMARYPolicyassignmentsOrphaned = foreach ($orphanedPolicyAssignment in $policyAssignmentsOrphaned | Sort-Object -Property PolicyAssignmentId) {
+ @"
+
+$($orphanedPolicyAssignment.policyAssignmentId -replace '<', '<' -replace '>', '>')
+$($orphanedPolicyAssignment.PolicyDefinitionId -replace '<', '<' -replace '>', '>')
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyassignmentsOrphaned)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($policyAssignmentsOrphanedCount) Policy assignments orphaned
+"@)
+ }
+ #endregion SUMMARYPolicyAssignmentsOrphaned
+
+ #region SUMMARYPolicyAssignmentsAll
+ $startSummaryPolicyAssignmentsAll = Get-Date
+ $allPolicyAssignments = ($policyBaseQuery).count
+ Write-Host " processing TenantSummary PolicyAssignments (all $allPolicyAssignments)"
+
+ $script:arrayPolicyAssignmentsEnriched = [System.Collections.ArrayList]@()
+ $cnter = 0
+
+ #region PolicyAssignmentsRoleAssignmentMapping
+ $startPolicyAssignmentsRoleAssignmentMapping = Get-Date
+ Write-Host ' processing PolicyAssignmentsRoleAssignmentMapping'
+ $script:htPolicyAssignmentRoleAssignmentMapping = @{}
+ foreach ($roleassignmentId in ($htCacheAssignmentsRole).keys | Sort-Object) {
+ $roleAssignment = ($htCacheAssignmentsRole).($roleassignmentId).Assignment
+
+ if ($htManagedIdentityForPolicyAssignment.($roleAssignment.ObjectId)) {
+ $mi = $htManagedIdentityForPolicyAssignment.($roleAssignment.ObjectId)
+
+ #this
+ if (-not $htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower())) {
+ $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()) = @{}
+ }
+
+ if (($htCacheDefinitionsRole).($roleAssignment.RoleDefinitionId).IsCustom) {
+ $roleDefinitionType = 'custom'
+ }
+ else {
+ $roleDefinitionType = 'builtin'
+ }
+
+ $array = [System.Collections.ArrayList]@()
+ $null = $array.Add([PSCustomObject]@{
+ roleassignmentId = $roleassignmentId
+ roleDefinitionId = $roleAssignment.RoleDefinitionId
+ roleDefinitionName = $roleAssignment.RoleDefinitionName
+ roleDefinitionType = $roleDefinitionType
+ })
+
+ #this
+ if ($htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments) {
+ $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments += $array
+ }
+ else {
+ $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments = $array
+ }
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ foreach ($roleassignmentId in ($htCacheAssignmentsRBACOnResourceGroupsAndResources).keys | Sort-Object) {
+ $roleAssignment = ($htCacheAssignmentsRBACOnResourceGroupsAndResources).($roleassignmentId)
+
+ if ($htManagedIdentityForPolicyAssignment.($roleAssignment.ObjectId)) {
+ $mi = $htManagedIdentityForPolicyAssignment.($roleAssignment.ObjectId)
+
+ #this
+ if (-not $htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower())) {
+ $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()) = @{}
+ }
+
+ if (($htCacheDefinitionsRole).($roleAssignment.RoleDefinitionId).IsCustom) {
+ $roleDefinitionType = 'custom'
+ }
+ else {
+ $roleDefinitionType = 'builtin'
+ }
+
+ $array = [System.Collections.ArrayList]@()
+ $null = $array.Add([PSCustomObject]@{
+ roleassignmentId = $roleassignmentId
+ roleDefinitionId = $roleAssignment.RoleDefinitionId
+ roleDefinitionName = $roleAssignment.RoleDefinitionName
+ roleDefinitionType = $roleDefinitionType
+ })
+
+ #this
+ if ($htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments) {
+ $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments += $array
+ }
+ else {
+ $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments = $array
+ }
+ }
+ }
+ }
+ $htPolicyAssignmentRoleAssignmentMappingCount = ($htPolicyAssignmentRoleAssignmentMapping.keys).Count
+ $endPolicyAssignmentsRoleAssignmentMapping = Get-Date
+ Write-Host " PolicyAssignmentsRoleAssignmentMapping processing duration: $((New-TimeSpan -Start $startPolicyAssignmentsRoleAssignmentMapping -End $endPolicyAssignmentsRoleAssignmentMapping).TotalMinutes) minutes ($((New-TimeSpan -Start $startPolicyAssignmentsRoleAssignmentMapping -End $endPolicyAssignmentsRoleAssignmentMapping).TotalSeconds) seconds)"
+ #endregion PolicyAssignmentsRoleAssignmentMapping
+
+ #region PolicyAssignmentsUniqueRelations
+ $startPolicyAssignmnetsUniqueRelations = Get-Date
+ Write-Host ' processing PolicyAssignmnetsUniqueRelations'
+ $htPolicyAssignmentRelatedRoleAssignments = @{}
+ $htPolicyAssignmentRelatedExemptions = @{}
+
+ foreach ($policyAssignmentIdUnique in $policyBaseQueryUniqueAssignments) {
+
+ #region relatedRoleAssignments
+ $relatedRoleAssignmentsArray = @()
+ $relatedRoleAssignmentsArrayClear = @()
+ if ($htPolicyAssignmentRoleAssignmentMappingCount -gt 0) {
+ if ($htPolicyAssignmentRoleAssignmentMapping.($policyAssignmentIdUnique.PolicyAssignmentId)) {
+ foreach ($entry in $htPolicyAssignmentRoleAssignmentMapping.($policyAssignmentIdUnique.PolicyAssignmentId).roleassignments) {
+ if ($entry.roleDefinitionType -eq 'builtin') {
+ $relatedRoleAssignmentsArray += "
$($entry.roleDefinitionName) ($($entry.roleAssignmentId))"
+ }
+ else {
+ $relatedRoleAssignmentsArray += "
$($entry.roleDefinitionName -replace '<', '<' -replace '>', '>') ($($entry.roleAssignmentId))"
+ }
+ $relatedRoleAssignmentsArrayClear += "$($entry.roleDefinitionName) ($($entry.roleAssignmentId))"
+ }
+ }
+ }
+
+ $htPolicyAssignmentRelatedRoleAssignments.($policyAssignmentIdUnique.PolicyAssignmentId) = @{}
+ if (($relatedRoleAssignmentsArray).count -gt 0) {
+ $htPolicyAssignmentRelatedRoleAssignments.($policyAssignmentIdUnique.PolicyAssignmentId).relatedRoleAssignments = ($relatedRoleAssignmentsArray | Sort-Object) -join "$CsvDelimiterOpposite "
+ $htPolicyAssignmentRelatedRoleAssignments.($policyAssignmentIdUnique.PolicyAssignmentId).relatedRoleAssignmentsClear = ($relatedRoleAssignmentsArrayClear | Sort-Object) -join "$CsvDelimiterOpposite "
+ }
+ else {
+ $htPolicyAssignmentRelatedRoleAssignments.($policyAssignmentIdUnique.PolicyAssignmentId).relatedRoleAssignments = 'none'
+ $htPolicyAssignmentRelatedRoleAssignments.($policyAssignmentIdUnique.PolicyAssignmentId).relatedRoleAssignmentsClear = 'none'
+ }
+ #endregion relatedRoleAssignments
+
+ #region exemptions
+ $arrayExemptions = @()
+ foreach ($exemptionId in $htPolicyAssignmentExemptions.keys) {
+ if ($htPolicyAssignmentExemptions.($exemptionId).exemption.properties.policyAssignmentId -eq $policyAssignmentIdUnique.PolicyAssignmentId) {
+ $arrayExemptions += $htPolicyAssignmentExemptions.($exemptionId).exemption
+ if (-not $htPolicyAssignmentRelatedExemptions.($policyAssignmentIdUnique.PolicyAssignmentId)) {
+ $htPolicyAssignmentRelatedExemptions.($policyAssignmentIdUnique.PolicyAssignmentId) = @{}
+ $htPolicyAssignmentRelatedExemptions.($policyAssignmentIdUnique.PolicyAssignmentId).exemptionsCount = 1
+ $htPolicyAssignmentRelatedExemptions.($policyAssignmentIdUnique.PolicyAssignmentId).exemptions = $arrayExemptions
+ }
+ else {
+ $htPolicyAssignmentRelatedExemptions.($policyAssignmentIdUnique.PolicyAssignmentId).exemptionsCount += 1
+ $htPolicyAssignmentRelatedExemptions.($policyAssignmentIdUnique.PolicyAssignmentId).exemptions = $arrayExemptions
+ }
+ }
+ }
+ #endregion exemptions
+ }
+ $endPolicyAssignmnetsUniqueRelations = Get-Date
+ Write-Host " PolicyAssignmnetsUniqueRelations processing duration: $((New-TimeSpan -Start $startPolicyAssignmnetsUniqueRelations -End $endPolicyAssignmnetsUniqueRelations).TotalMinutes) minutes ($((New-TimeSpan -Start $startPolicyAssignmnetsUniqueRelations -End $endPolicyAssignmnetsUniqueRelations).TotalSeconds) seconds)"
+ #endregion PolicyAssignmentsUniqueRelations
+
+ #region PolicyAssignmentsAllCreateEnriched
+ $startPolicyAssignmentsAllCreateEnriched = Get-Date
+ Write-Host ' processing PolicyAssignmentsAllCreateEnriched'
+ foreach ($policyAssignmentAll in $policyBaseQuery) {
+
+ $cnter++
+ if ($cnter % 1000 -eq 0) {
+ $etappeSummaryPolicyAssignmentsAll = Get-Date
+ Write-Host " $cnter of $allPolicyAssignments PolicyAssignments processed: $((New-TimeSpan -Start $startSummaryPolicyAssignmentsAll -End $etappeSummaryPolicyAssignmentsAll).TotalSeconds) seconds"
+ }
+
+ #region AzAdvertizerLinkOrNot
+ if ($policyAssignmentAll.PolicyType -eq 'builtin') {
+ if ($policyAssignmentAll.PolicyVariant -eq 'Policy') {
+ $azaLinkOrNot = "
$($policyAssignmentAll.Policy) "
+ }
+ else {
+ $azaLinkOrNot = "
$($policyAssignmentAll.Policy) "
+ }
+ }
+ else {
+ $azaLinkOrNot = $policyAssignmentAll.Policy
+ }
+ #endregion AzAdvertizerLinkOrNot
+
+ #region excludedScope
+ $excludedScope = 'false'
+ if (($policyAssignmentAll.PolicyAssignmentNotScopes).count -gt 0) {
+ foreach ($policyAssignmentNotScope in $policyAssignmentAll.PolicyAssignmentNotScopes) {
+ if (-not [String]::IsNullOrEmpty($policyAssignmentAll.subscriptionId)) {
+ if ($htSubscriptionsMgPath.($policyAssignmentAll.subscriptionId).path -contains ($($policyAssignmentNotScope -replace '/subscriptions/' -replace '/providers/Microsoft.Management/managementGroups/'))) {
+ $excludedScope = 'true'
+ }
+ }
+ else {
+ if ($htManagementGroupsMgPath.($policyAssignmentAll.MgId).path -contains ($($policyAssignmentNotScope -replace '/providers/Microsoft.Management/managementGroups/'))) {
+ $excludedScope = 'true'
+ }
+ }
+ }
+ }
+ #endregion excludedScope
+
+ #region exemptions
+ $exemptionScope = 'false'
+ if ($htPolicyAssignmentRelatedExemptions.($policyAssignmentAll.PolicyAssignmentId)) {
+ foreach ($exemption in $htPolicyAssignmentRelatedExemptions.($policyAssignmentAll.PolicyAssignmentId).exemptions) {
+ if ($exemption.properties.expiresOn) {
+ if ($exemption.properties.expiresOn -gt (Get-Date).ToUniversalTime()) {
+ if (-not [String]::IsNullOrEmpty($policyAssignmentAll.subscriptionId)) {
+ if ($htSubscriptionsMgPath.($policyAssignmentAll.subscriptionId).path -contains ($(($exemption.Id -split '/providers/Microsoft.Authorization/policyExemptions/')[0] -replace '/subscriptions/' -replace '/providers/Microsoft.Management/managementGroups/'))) {
+ $exemptionScope = 'true'
+ }
+ }
+ else {
+ if ($htManagementGroupsMgPath.($policyAssignmentAll.MgId).path -contains ($(($exemption.Id -split '/providers/Microsoft.Authorization/policyExemptions/')[0] -replace '/subscriptions/' -replace '/providers/Microsoft.Management/managementGroups/'))) {
+ $exemptionScope = 'true'
+ }
+ }
+ }
+ else {
+ #Write-Host "$($exemption.Id) $($exemption.properties.expiresOn) $((Get-Date).ToUniversalTime()) expired"
+ }
+ }
+ else {
+ #same code as above / function?
+ if (-not [String]::IsNullOrEmpty($policyAssignmentAll.subscriptionId)) {
+ if ($htSubscriptionsMgPath.($policyAssignmentAll.subscriptionId).path -contains ($(($exemption.Id -split '/providers/Microsoft.Authorization/policyExemptions/')[0] -replace '/subscriptions/' -replace '/providers/Microsoft.Management/managementGroups/'))) {
+ $exemptionScope = 'true'
+ }
+ }
+ else {
+ if ($htManagementGroupsMgPath.($policyAssignmentAll.MgId).path -contains ($(($exemption.Id -split '/providers/Microsoft.Authorization/policyExemptions/')[0] -replace '/subscriptions/' -replace '/providers/Microsoft.Management/managementGroups/'))) {
+ $exemptionScope = 'true'
+ }
+ }
+ }
+ }
+ }
+ #endregion exemptions
+
+ #region inheritance
+ if ($policyAssignmentAll.PolicyAssignmentId -like '/providers/Microsoft.Management/managementGroups/*') {
+ if (-not [String]::IsNullOrEmpty($policyAssignmentAll.SubscriptionId)) {
+ $scope = "inherited $($policyAssignmentAll.PolicyAssignmentScope -replace '.*/')"
+ }
+ else {
+ if (($policyAssignmentAll.PolicyAssignmentScope -replace '.*/') -eq $policyAssignmentAll.MgId) {
+ $scope = 'thisScope Mg'
+ }
+ else {
+ $scope = "inherited $($policyAssignmentAll.PolicyAssignmentScope -replace '.*/')"
+ }
+ }
+ }
+
+ if ($policyAssignmentAll.PolicyAssignmentId -like '/subscriptions/*' -and $policyAssignmentAll.PolicyAssignmentId -notlike '/subscriptions/*/resourcegroups/*') {
+ $scope = 'thisScope Sub'
+ }
+
+ if ($policyAssignmentAll.PolicyAssignmentId -like '/subscriptions/*/resourcegroups/*') {
+ $scope = 'thisScope Sub RG'
+ }
+ #endregion inheritance
+
+ #region effect
+ $effect = 'unknown'
+ if ($policyAssignmentAll.PolicyVariant -eq 'Policy') {
+
+ $test0 = $policyAssignmentAll.PolicyAssignmentParameters.effect.value
+ if ($test0) {
+ $effect = $test0
+ }
+ else {
+ $test1 = $policyAssignmentAll.PolicyDefinitionEffectDefault
+ if ($test1 -ne 'n/a') {
+ $effect = $test1
+ }
+ $test2 = $policyAssignmentAll.PolicyDefinitionEffectFixed
+ if ($test2 -ne 'n/a') {
+ $effect = $test2
+ }
+ }
+ }
+ else {
+ $effect = 'n/a'
+ }
+ #endregion effect
+
+ #region mgOrSubOrRG
+ if ([String]::IsNullOrEmpty($policyAssignmentAll.SubscriptionId)) {
+ $mgOrSubOrRG = 'Mg'
+ }
+ else {
+ if ($scope -like '*RG') {
+ $mgOrSubOrRG = 'RG'
+ }
+ else {
+ $mgOrSubOrRG = 'Sub'
+ }
+ }
+ #endregion mgOrSubOrRG
+
+ #region category
+ if ([string]::IsNullOrEmpty($policyAssignmentAll.PolicyCategory)) {
+ $policyCategory = 'n/a'
+ }
+ else {
+ $policyCategory = $policyAssignmentAll.PolicyCategory
+ }
+ #endregion category
+
+ #region createdByUpdatedBy
+ #createdBy
+ if ($policyAssignmentAll.PolicyAssignmentCreatedBy) {
+ $createdBy = $policyAssignmentAll.PolicyAssignmentCreatedBy
+ if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) {
+ $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details
+ }
+ }
+ else {
+ $createdBy = ''
+ }
+
+ #UpdatedBy
+ if ($policyAssignmentAll.PolicyAssignmentUpdatedBy) {
+ $updatedBy = $policyAssignmentAll.PolicyAssignmentUpdatedBy
+ if ($htIdentitiesWithRoleAssignmentsUnique.($updatedBy)) {
+ $updatedBy = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).details
+ }
+ }
+ else {
+ $updatedBy = ''
+ }
+ #endregion createdByUpdatedBy
+
+ #region policyAssignmentNotScopes
+ if ($policyAssignmentAll.PolicyAssignmentNotScopes) {
+ $policyAssignmentNotScopes = $policyAssignmentAll.PolicyAssignmentNotScopes -join $CsvDelimiterOpposite
+ }
+ else {
+ $policyAssignmentNotScopes = 'n/a'
+ }
+ #endregion policyAssignmentNotScopes
+
+ #region
+ $policyAssignmentMI = ''
+ if ($htPolicyAssignmentRelatedRoleAssignments.($policyAssignmentAll.PolicyAssignmentId)) {
+ $hlp = $htPolicyAssignmentRelatedRoleAssignments.($policyAssignmentAll.PolicyAssignmentId)
+ $relatedRoleAssignments = $hlp.relatedRoleAssignments
+ $relatedRoleAssignmentsClear = $hlp.relatedRoleAssignmentsClear
+ if ($htManagedIdentityDisplayName.("$($policyAssignmentAll.PolicyAssignmentId -replace '.*/')_$($policyAssignmentAll.PolicyAssignmentId)")) {
+ $hlp = $htManagedIdentityDisplayName.("$($policyAssignmentAll.PolicyAssignmentId -replace '.*/')_$($policyAssignmentAll.PolicyAssignmentId)")
+ $policyAssignmentMI = "$($hlp.displayname) (SPObjId: $($hlp.id))"
+ }
+ }
+ #endregion
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ #region policyCompliance
+ $policyAssignmentIdToLower = ($policyAssignmentAll.policyAssignmentId).ToLower()
+
+ #mg
+ if ([String]::IsNullOrEmpty($policyAssignmentAll.subscriptionId)) {
+ if (($htCachePolicyComplianceResponseTooLargeMG).($policyAssignmentAll.MgId)) {
+ $NonCompliantPolicies = 'skipped'
+ $CompliantPolicies = 'skipped'
+ $NonCompliantResources = 'skipped'
+ $CompliantResources = 'skipped'
+ $ConflictingResources = 'skipped'
+ }
+ else {
+ $compliance = ($htCachePolicyComplianceMG).($policyAssignmentAll.MgId).($policyAssignmentIdToLower)
+ $NonCompliantPolicies = $compliance.NonCompliantPolicies
+ $CompliantPolicies = $compliance.CompliantPolicies
+ $NonCompliantResources = $compliance.NonCompliantResources
+ $CompliantResources = $compliance.CompliantResources
+ $ConflictingResources = $compliance.ConflictingResources
+
+ if (!$NonCompliantPolicies) {
+ $NonCompliantPolicies = 0
+ }
+ if (!$CompliantPolicies) {
+ $CompliantPolicies = 0
+ }
+ if (!$NonCompliantResources) {
+ $NonCompliantResources = 0
+ }
+ if (!$CompliantResources) {
+ $CompliantResources = 0
+ }
+ if (!$ConflictingResources) {
+ $ConflictingResources = 0
+ }
+ }
+ }
+
+ #sub/rg
+ if (-not [String]::IsNullOrEmpty($policyAssignmentAll.subscriptionId)) {
+ if (($htCachePolicyComplianceResponseTooLargeSUB).($policyAssignmentAll.SubscriptionId)) {
+ $NonCompliantPolicies = 'skipped'
+ $CompliantPolicies = 'skipped'
+ $NonCompliantResources = 'skipped'
+ $CompliantResources = 'skipped'
+ $ConflictingResources = 'skipped'
+ }
+ else {
+ $compliance = ($htCachePolicyComplianceSUB).($policyAssignmentAll.SubscriptionId).($policyAssignmentIdToLower)
+ $NonCompliantPolicies = $compliance.NonCompliantPolicies
+ $CompliantPolicies = $compliance.CompliantPolicies
+ $NonCompliantResources = $compliance.NonCompliantResources
+ $CompliantResources = $compliance.CompliantResources
+ $ConflictingResources = $compliance.ConflictingResources
+
+ if (!$NonCompliantPolicies) {
+ $NonCompliantPolicies = 0
+ }
+ if (!$CompliantPolicies) {
+ $CompliantPolicies = 0
+ }
+ if (!$NonCompliantResources) {
+ $NonCompliantResources = 0
+ }
+ if (!$CompliantResources) {
+ $CompliantResources = 0
+ }
+ if (!$ConflictingResources) {
+ $ConflictingResources = 0
+ }
+ }
+ }
+ #endregion policyCompliance
+
+ $null = $script:arrayPolicyAssignmentsEnriched.Add([PSCustomObject]@{
+ Level = $policyAssignmentAll.Level
+ MgId = $policyAssignmentAll.MgId
+ MgName = $policyAssignmentAll.MgName
+ MgParentId = $policyAssignmentAll.MgParentId
+ MgParentName = $policyAssignmentAll.MgParentName
+ subscriptionId = $policyAssignmentAll.SubscriptionId
+ subscriptionName = $policyAssignmentAll.Subscription
+ PolicyAssignmentId = (($policyAssignmentAll.PolicyAssignmentId).ToLower())
+ PolicyAssignmentScopeName = $policyAssignmentAll.PolicyAssignmentScopeName
+ PolicyAssignmentDisplayName = $policyAssignmentAll.PolicyAssignmentDisplayName
+ PolicyAssignmentDescription = $policyAssignmentAll.PolicyAssignmentDescription
+ PolicyAssignmentEnforcementMode = $policyAssignmentAll.PolicyAssignmentEnforcementMode
+ PolicyAssignmentNonComplianceMessages = $policyAssignmentAll.PolicyAssignmentNonComplianceMessages
+ PolicyAssignmentNotScopes = $policyAssignmentNotScopes
+ PolicyAssignmentParameters = $policyAssignmentAll.PolicyAssignmentParametersFormated
+ PolicyAssignmentMI = $policyAssignmentMI
+ AssignedBy = $policyAssignmentAll.PolicyAssignmentAssignedBy
+ CreatedOn = $policyAssignmentAll.PolicyAssignmentCreatedOn
+ CreatedBy = $createdBy
+ UpdatedOn = $policyAssignmentAll.PolicyAssignmentUpdatedOn
+ UpdatedBy = $updatedBy
+ Effect = $effect
+ PolicyName = $azaLinkOrNot
+ PolicyNameClear = $policyAssignmentAll.Policy
+ PolicyAvailability = $policyAssignmentAll.PolicyAvailability
+ PolicyDescription = $policyAssignmentAll.PolicyDescription
+ PolicyId = $policyAssignmentAll.PolicyDefinitionId
+ PolicyVariant = $policyAssignmentAll.PolicyVariant
+ PolicyType = $policyAssignmentAll.PolicyType
+ PolicyIsALZ = $policyAssignmentAll.PolicyIsALZ
+ PolicyCategory = $policyCategory
+ Inheritance = $scope
+ ExcludedScope = $excludedScope
+ RelatedRoleAssignments = $relatedRoleAssignments
+ RelatedRoleAssignmentsClear = $relatedRoleAssignmentsClear
+ mgOrSubOrRG = $mgOrSubOrRG
+ NonCompliantPolicies = $NonCompliantPolicies
+ CompliantPolicies = $CompliantPolicies
+ NonCompliantResources = $NonCompliantResources
+ CompliantResources = $CompliantResources
+ ConflictingResources = $ConflictingResources
+ ExemptionScope = $exemptionScope
+ })
+ }
+ else {
+ $null = $script:arrayPolicyAssignmentsEnriched.Add([PSCustomObject]@{
+ Level = $policyAssignmentAll.Level
+ MgId = $policyAssignmentAll.MgId
+ MgName = $policyAssignmentAll.MgName
+ MgParentId = $policyAssignmentAll.MgParentId
+ MgParentName = $policyAssignmentAll.MgParentName
+ subscriptionId = $policyAssignmentAll.SubscriptionId
+ subscriptionName = $policyAssignmentAll.Subscription
+ PolicyAssignmentId = (($policyAssignmentAll.PolicyAssignmentId).ToLower())
+ PolicyAssignmentScopeName = $policyAssignmentAll.PolicyAssignmentScopeName
+ PolicyAssignmentDisplayName = $policyAssignmentAll.PolicyAssignmentDisplayName
+ PolicyAssignmentDescription = $policyAssignmentAll.PolicyAssignmentDescription
+ PolicyAssignmentEnforcementMode = $policyAssignmentAll.PolicyAssignmentEnforcementMode
+ PolicyAssignmentNonComplianceMessages = $policyAssignmentAll.PolicyAssignmentNonComplianceMessages
+ PolicyAssignmentNotScopes = $policyAssignmentNotScopes
+ PolicyAssignmentParameters = $policyAssignmentAll.PolicyAssignmentParametersFormated
+ PolicyAssignmentMI = $policyAssignmentMI
+ AssignedBy = $policyAssignmentAll.PolicyAssignmentAssignedBy
+ CreatedOn = $policyAssignmentAll.PolicyAssignmentCreatedOn
+ CreatedBy = $createdBy
+ UpdatedOn = $policyAssignmentAll.PolicyAssignmentUpdatedOn
+ UpdatedBy = $updatedBy
+ Effect = $effect
+ PolicyName = $azaLinkOrNot
+ PolicyNameClear = $policyAssignmentAll.Policy
+ PolicyAvailability = $policyAssignmentAll.PolicyAvailability
+ PolicyDescription = $policyAssignmentAll.PolicyDescription
+ PolicyId = $policyAssignmentAll.PolicyDefinitionId
+ PolicyVariant = $policyAssignmentAll.PolicyVariant
+ PolicyType = $policyAssignmentAll.PolicyType
+ PolicyIsALZ = $policyAssignmentAll.PolicyIsALZ
+ PolicyCategory = $policyCategory
+ Inheritance = $scope
+ ExcludedScope = $excludedScope
+ RelatedRoleAssignments = $relatedRoleAssignments
+ RelatedRoleAssignmentsClear = $relatedRoleAssignmentsClear
+ mgOrSubOrRG = $mgOrSubOrRG
+ ExemptionScope = $exemptionScope
+ })
+ }
+ }
+ $EndPolicyAssignmentsAllCreateEnriched = Get-Date
+ Write-Host " PolicyAssignmentsAllCreateEnriched processing duration: $((New-TimeSpan -Start $startPolicyAssignmentsAllCreateEnriched -End $EndPolicyAssignmentsAllCreateEnriched).TotalMinutes) minutes ($((New-TimeSpan -Start $startPolicyAssignmentsAllCreateEnriched -End $EndPolicyAssignmentsAllCreateEnriched).TotalSeconds) seconds)"
+ #endregion PolicyAssignmentsAllCreateEnriched
+
+ #region PolicyAssignmentsAllResolveIdentities
+ Write-Host ' processing unresoved Identities (createdBy/updatedBy)'
+ $startUnResolvedIdentitiesCreatedByUpdatedByPolicy = Get-Date
+
+ $createdByNotResolved = ($arrayPolicyAssignmentsEnriched.where( { -not [string]::IsNullOrEmpty($_.CreatedBy) -and $_.CreatedBy -notlike 'ObjectType:*' })).CreatedBy | Sort-Object -Unique
+ $updatedByNotResolved = ($arrayPolicyAssignmentsEnriched.where( { -not [string]::IsNullOrEmpty($_.UpdatedBy) -and $_.UpdatedBy -notlike 'ObjectType:*' })).UpdatedBy | Sort-Object -Unique
+
+ $htNonResolvedIdentitiesPolicy = @{}
+ foreach ($createdByNotResolvedEntry in $createdByNotResolved) {
+ if (-not $htNonResolvedIdentitiesPolicy.($createdByNotResolvedEntry)) {
+ $htNonResolvedIdentitiesPolicy.($createdByNotResolvedEntry) = @{}
+ }
+ }
+ foreach ($updatedByNotResolvedEntry in $updatedByNotResolved) {
+ if (-not $htNonResolvedIdentitiesPolicy.($updatedByNotResolvedEntry)) {
+ $htNonResolvedIdentitiesPolicy.($updatedByNotResolvedEntry) = @{}
+ }
+ }
+
+ $htNonResolvedIdentitiesPolicyCount = $htNonResolvedIdentitiesPolicy.Count
+ if ($htNonResolvedIdentitiesPolicyCount -gt 0) {
+ Write-Host " $htNonResolvedIdentitiesPolicyCount unresolved identities that created/updated a Policy assignment (createdBy/updatedBy)"
+ $arrayUnresolvedIdentities = @()
+ $arrayUnresolvedIdentities = foreach ($unresolvedIdentity in $htNonResolvedIdentitiesPolicy.keys) {
+ if (-not [string]::IsNullOrEmpty($unresolvedIdentity)) {
+ $unresolvedIdentity
+ }
+ }
+ $arrayUnresolvedIdentitiesCount = $arrayUnresolvedIdentities.Count
+ Write-Host " $arrayUnresolvedIdentitiesCount unresolved identities that have a value"
+ if ($arrayUnresolvedIdentitiesCount.Count -gt 0) {
+ $counterBatch = [PSCustomObject] @{ Value = 0 }
+ $batchSize = 1000
+ $ObjectBatch = $arrayUnresolvedIdentities | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) }
+ $ObjectBatchCount = ($ObjectBatch | Measure-Object).Count
+ $batchCnt = 0
+
+ $script:htResolvedIdentitiesPolicy = @{}
+
+ foreach ($batch in $ObjectBatch) {
+ $batchCnt++
+
+ $nonResolvedIdentitiesToCheck = '"{0}"' -f ($batch.Group.where({ testGuid $_ }) -join '","')
+ Write-Host " IdentitiesToCheck: Batch #$batchCnt/$($ObjectBatchCount) ($(($batch.Group).Count))"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/v1.0/directoryObjects/getByIds"
+ $method = 'POST'
+ $body = @"
+ {
+ "ids":[$($nonResolvedIdentitiesToCheck)]
+ }
+"@
+
+ function resolveIdentitiesPolicy($currentTask) {
+ $resolvedIdentities = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask
+ $resolvedIdentitiesCount = $resolvedIdentities.Count
+ Write-Host " $resolvedIdentitiesCount identities resolved"
+ if ($resolvedIdentitiesCount -gt 0) {
+ foreach ($resolvedIdentity in $resolvedIdentities) {
+ if (-not $htResolvedIdentitiesPolicy.($resolvedIdentity.id)) {
+ $script:htResolvedIdentitiesPolicy.($resolvedIdentity.id) = @{}
+ if ($resolvedIdentity.'@odata.type' -eq '#microsoft.graph.servicePrincipal') {
+ if ($resolvedIdentity.servicePrincipalType -eq 'ManagedIdentity') {
+ $miType = 'unknown'
+ foreach ($altName in $resolvedIdentity.alternativeNames) {
+ if ($altName -like 'isExplicit=*') {
+ $splitAltName = $altName.split('=')
+ if ($splitAltName[1] -eq 'true') {
+ $miType = 'Usr'
+ }
+ if ($splitAltName[1] -eq 'false') {
+ $miType = 'Sys'
+ }
+ }
+ }
+ $sptype = "MI $miType"
+ $custObjectType = "ObjectType: SP $sptype, ObjectDisplayName: $($resolvedIdentity.displayName), ObjectSignInName: n/a, ObjectId: $($resolvedIdentity.id) (rp)"
+ }
+ else {
+ if ($resolvedIdentity.servicePrincipalType -eq 'Application') {
+ $sptype = 'App'
+ if ($resolvedIdentity.appOwnerOrganizationId -eq $azAPICallConf['checkContext'].Tenant.Id) {
+ $custObjectType = "ObjectType: SP $sptype INT, ObjectDisplayName: $($resolvedIdentity.displayName), ObjectSignInName: n/a, ObjectId: $($resolvedIdentity.id) (rp)"
+ }
+ else {
+ $custObjectType = "ObjectType: SP $sptype EXT, ObjectDisplayName: $($resolvedIdentity.displayName), ObjectSignInName: n/a, ObjectId: $($resolvedIdentity.id) (rp)"
+ }
+ }
+ else {
+ Write-Host "* * * Unexpected IdentityType $($resolvedIdentity.servicePrincipalType)"
+ }
+ }
+ $script:htResolvedIdentitiesPolicy.($resolvedIdentity.id).custObjectType = $custObjectType
+ $script:htResolvedIdentitiesPolicy.($resolvedIdentity.id).obj = $resolvedIdentity
+ }
+
+ if ($resolvedIdentity.'@odata.type' -eq '#microsoft.graph.user') {
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData) {
+ $hlpObjectDisplayName = 'scrubbed'
+ $hlpObjectSigninName = 'scrubbed'
+ }
+ else {
+ $hlpObjectDisplayName = $resolvedIdentity.displayName
+ $hlpObjectSigninName = $resolvedIdentity.userPrincipalName
+ }
+ $custObjectType = "ObjectType: User, ObjectDisplayName: $hlpObjectDisplayName, ObjectSignInName: $hlpObjectSigninName, ObjectId: $($resolvedIdentity.id) (rp)"
+
+ $script:htResolvedIdentitiesPolicy.($resolvedIdentity.id).custObjectType = $custObjectType
+ $script:htResolvedIdentitiesPolicy.($resolvedIdentity.id).obj = $resolvedIdentity
+ }
+
+ if ($resolvedIdentity.'@odata.type' -ne '#microsoft.graph.user' -and $resolvedIdentity.'@odata.type' -ne '#microsoft.graph.servicePrincipal') {
+ Write-Host "!!! * * * IdentityType '$($resolvedIdentity.'@odata.type')' was not considered by Azure Governance Visualizer - if you see this line, please file an issue on GitHub - thank you." -ForegroundColor Yellow
+ }
+ }
+ }
+ }
+ }
+ resolveIdentitiesPolicy -currentTask 'resolveObjectbyId PolicyAssignment #1'
+ }
+
+ foreach ($policyAssignment in $script:arrayPolicyAssignmentsEnriched.where( { -not [string]::IsNullOrEmpty($_.CreatedBy) -and $_.CreatedBy -notlike 'ObjectType*' })) {
+ if ($htResolvedIdentitiesPolicy.($policyAssignment.CreatedBy)) {
+ $policyAssignment.CreatedBy = $htResolvedIdentitiesPolicy.($policyAssignment.CreatedBy).custObjectType
+ }
+ }
+
+ foreach ($policyAssignment in $script:arrayPolicyAssignmentsEnriched.where( { -not [string]::IsNullOrEmpty($_.UpdatedBy) -and $_.UpdatedBy -notlike 'ObjectType*' })) {
+ if ($htResolvedIdentitiesPolicy.($policyAssignment.UpdatedBy)) {
+ $policyAssignment.UpdatedBy = $htResolvedIdentitiesPolicy.($policyAssignment.UpdatedBy).custObjectType
+ }
+ }
+ }
+ }
+
+ $endUnResolvedIdentitiesCreatedByUpdatedByPolicy = Get-Date
+ Write-Host " UnresolvedIdentities (createdBy/updatedBy) duration: $((New-TimeSpan -Start $startUnResolvedIdentitiesCreatedByUpdatedByPolicy -End $endUnResolvedIdentitiesCreatedByUpdatedByPolicy).TotalMinutes) minutes ($((New-TimeSpan -Start $startUnResolvedIdentitiesCreatedByUpdatedByPolicy -End $endUnResolvedIdentitiesCreatedByUpdatedByPolicy).TotalSeconds) seconds)"
+ #endregion PolicyAssignmentsAllResolveIdentities
+
+ $script:arrayPolicyAssignmentsEnrichedGroupedBySubscription = $arrayPolicyAssignmentsEnriched | Group-Object -Property subscriptionId
+ $script:arrayPolicyAssignmentsEnrichedGroupedByManagementGroup = $arrayPolicyAssignmentsEnriched | Group-Object -Property MgId
+
+ #region policyAssignmentsAllHTML
+ Write-Host ' processing SummaryPolicyAssignmentsAllHTML'
+ $startSummaryPolicyAssignmentsAllHTML = Get-Date
+ if (($arrayPolicyAssignmentsEnriched).count -gt 0) {
+
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_PolicyAssignments"
+ Write-Host " Exporting PolicyAssignments CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ if ($CsvExportUseQuotesAsNeeded) {
+ $arrayPolicyAssignmentsEnriched | Sort-Object -Property Level, MgId, SubscriptionId, PolicyAssignmentId | Select-Object -ExcludeProperty PolicyName, RelatedRoleAssignments | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded
+ }
+ else {
+ $arrayPolicyAssignmentsEnriched | Sort-Object -Property Level, MgId, SubscriptionId, PolicyAssignmentId | Select-Object -ExcludeProperty PolicyName, RelatedRoleAssignments | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+ }
+
+ $policyAssignmentsUniqueCount = ($arrayPolicyAssignmentsEnriched | Sort-Object -Property PolicyAssignmentId -Unique).count
+ if ($azAPICallConf['htParameters'].LargeTenant -or $azAPICallConf['htParameters'].PolicyAtScopeOnly) {
+ $policyAssignmentsCount = $policyAssignmentsUniqueCount
+ $tfCount = $policyAssignmentsCount
+ }
+ else {
+ $policyAssignmentsCount = ($arrayPolicyAssignmentsEnriched).count
+ $tfCount = $policyAssignmentsCount
+ }
+
+ if ($tfCount -gt $HtmlTableRowsLimit) {
+ Write-Host " !Skipping TenantSummary PolicyAssignments HTML processing as $tfCount lines is exceeding the critical rows limit of $HtmlTableRowsLimit" -ForegroundColor Yellow
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($policyAssignmentsCount) Policy assignments ($policyAssignmentsUniqueCount unique)
+
+
+
Output of $tfCount lines would exceed the html rows limit of $HtmlTableRowsLimit (html file potentially would become unresponsive). Work with the CSV file $($csvFilename).csv | Note: the CSV file will only exist if you did NOT use parameter -NoCsvExport
+
You can adjust the html row limit by using parameter -HtmlTableRowsLimit
+
You can reduce the number of lines by using parameter -LargeTenant and/or -DoNotIncludeResourceGroupsAndResourcesOnRBAC
+
Check the parameters documentation Azure Governance Visualizer docs
+
+"@)
+ }
+ else {
+
+ $htmlTableId = 'TenantSummary_policyAssignmentsAll'
+ $noteOrNot = ''
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($policyAssignmentsCount) Policy assignments ($policyAssignmentsUniqueCount unique)
+
+
Download CSV
semicolon |
comma
+
*Depending on the number of rows and your computer´s performance the table may respond with delay, download the csv for better filtering experience
+
+
+
+Scope
+Management Group Id
+Management Group Name
+SubscriptionId
+Subscription Name
+Inheritance
+ScopeExcluded
+Exemption applies
+Policy/Set DisplayName
+Policy/Set Description
+Policy/SetId
+Policy/Set
+Type
+Category
+ALZ
+Effect
+Parameters
+Enforcement
+NonCompliance Message
+"@)
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ [void]$htmlTenantSummary.AppendLine(@'
+Policies NonCmplnt
+Policies Compliant
+Resources NonCmplnt
+Resources Compliant
+Resources Conflicting
+'@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+Role/Assignment $noteOrNot
+Managed Identity
+Assignment DisplayName
+Assignment Description
+AssignmentId
+AssignedBy
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+
+ $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlTenantSummary = [System.Text.StringBuilder]::new()
+ $htmlSummaryPolicyAssignmentsAll = $null
+ $startloop = Get-Date
+
+ $htmlSummaryPolicyAssignmentsAll = foreach ($policyAssignment in $arrayPolicyAssignmentsEnriched | Sort-Object -Property Level, MgName, MgId, SubscriptionName, SubscriptionId, PolicyAssignmentId) {
+ if ($azAPICallConf['htParameters'].LargeTenant -or $azAPICallConf['htParameters'].PolicyAtScopeOnly) {
+ if ($policyAssignment.Inheritance -like 'inherited *' -and $policyAssignment.MgParentId -ne "'upperScopes'") {
+ continue
+ }
+ }
+ if ($policyAssignment.PolicyType -eq 'Custom') {
+ $policyName = ($policyAssignment.PolicyName -replace '<', '<' -replace '>', '>')
+ }
+ else {
+ $policyName = $policyAssignment.PolicyName
+ }
+ @"
+
+$($policyAssignment.mgOrSubOrRG)
+$($policyAssignment.MgId)
+$($policyAssignment.MgName -replace '<', '<' -replace '>', '>')
+$($policyAssignment.SubscriptionId)
+$($policyAssignment.SubscriptionName)
+$($policyAssignment.Inheritance)
+$($policyAssignment.ExcludedScope)
+$($policyAssignment.ExemptionScope)
+$($policyName)
+$($policyAssignment.PolicyDescription -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyId -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyVariant)
+$($policyAssignment.PolicyType)
+$($policyAssignment.PolicyCategory -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyIsALZ)
+$($policyAssignment.Effect)
+$($policyAssignment.PolicyAssignmentParameters)
+$($policyAssignment.PolicyAssignmentEnforcementMode)
+$($policyAssignment.PolicyAssignmentNonComplianceMessages -replace '<', '<' -replace '>', '>')
+"@
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ @"
+$($policyAssignment.NonCompliantPolicies)
+$($policyAssignment.CompliantPolicies)
+$($policyAssignment.NonCompliantResources)
+$($policyAssignment.CompliantResources)
+$($policyAssignment.ConflictingResources)
+"@
+ }
+
+ @"
+$($policyAssignment.RelatedRoleAssignments)
+$($policyAssignment.PolicyAssignmentMI)
+$($policyAssignment.PolicyAssignmentDisplayName -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyAssignmentDescription -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyAssignmentId -replace '<', '<' -replace '>', '>')
+$($policyAssignment.AssignedBy)
+$($policyAssignment.CreatedOn)
+$($policyAssignment.CreatedBy)
+$($policyAssignment.UpdatedOn)
+$($policyAssignment.UpdatedBy)
+
+"@
+ }
+
+ $endloop = Get-Date
+ Write-Host " html foreach loop duration: $((New-TimeSpan -Start $startloop -End $endloop).TotalSeconds) seconds"
+
+ $start = Get-Date
+ [void]$htmlTenantSummary.AppendLine($htmlSummaryPolicyAssignmentsAll)
+ $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlTenantSummary = [System.Text.StringBuilder]::new()
+ $end = Get-Date
+ Write-Host " html append file duration: $((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds"
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arrayPolicyAssignmentsEnriched).count) Policy assignments
+"@)
+ }
+ $endSummaryPolicyAssignmentsAllHTML = Get-Date
+ Write-Host " SummaryPolicyAssignmentsAllHTML duration: $((New-TimeSpan -Start $startSummaryPolicyAssignmentsAllHTML -End $endSummaryPolicyAssignmentsAllHTML).TotalMinutes) minutes ($((New-TimeSpan -Start $startSummaryPolicyAssignmentsAllHTML -End $endSummaryPolicyAssignmentsAllHTML).TotalSeconds) seconds)"
+ #endregion policyAssignmentsAllHTML
+ $endSummaryPolicyAssignmentsAll = Get-Date
+ Write-Host " SummaryPolicyAssignmentsAll duration: $((New-TimeSpan -Start $startSummaryPolicyAssignmentsAll -End $endSummaryPolicyAssignmentsAll).TotalMinutes) minutes ($((New-TimeSpan -Start $startSummaryPolicyAssignmentsAll -End $endSummaryPolicyAssignmentsAll).TotalSeconds) seconds)"
+ #endregion SUMMARYPolicyAssignmentsAll
+
+ #region SUMMARYPolicyRemediation
+ Write-Host ' processing TenantSummary Policy Remediation'
+
+ if ($arrayRemediatable.Count -gt 0) {
+ $tfCount = $arrayRemediatable.Count
+ $nonCompliantResourcesTotal = ($arrayRemediatable.nonCompliantResourcesCount | Measure-Object -Sum).Sum
+ $htmlTableId = 'TenantSummary_PolicyRemediation'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($arrayRemediatable.Count) Policies to remediate ($($nonCompliantResourcesTotal) nonCompliant resources)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Assignment Scope Type
+Assignment Scope
+Assignment Id
+Assignment DisplayName
+Assignment Policy/Set
+Effect
+Policy definition id
+Policy definition displayName
+Policy definition type
+Policy definition refId
+PolicySet definition id
+PolicySet definition displayName
+PolicySet definition type
+NonCompliant resources
+
+
+
+"@)
+
+ $htmlSUMMARYPolicyRemediation = $null
+ $arrayRemediatableSorted = $arrayRemediatable | Sort-Object -Property nonCompliantResourcesCount, policySetPolicyDefinitionReferenceId, policyDefinitionId, policyAssignmentId -Descending
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_PolicyRemediation"
+ Write-Host " Exporting PolicyRemediation CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ $arrayRemediatableSorted | Export-Csv -Encoding utf8 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -NoTypeInformation
+ }
+ $htmlSUMMARYPolicyRemediation = foreach ($entry in $arrayRemediatableSorted) {
+
+ if ($entry.policyDefinitionType -eq 'builtin') {
+ $pd = "$($entry.policyDefinitionDisplayName) ($($entry.policyDefinitionName))"
+ }
+ else {
+ $pd = "$($entry.policyDefinitionDisplayName) ($($entry.policyDefinitionName))"
+ }
+
+ if ($entry.policySetDefinitionType -ne 'n/a') {
+ if ($entry.policySetDefinitionType -eq 'builtIn') {
+ $psd = "$($entry.policySetDefinitionDisplayName) ($($entry.policySetDefinitionName))"
+ }
+ else {
+ $psd = "$($entry.policySetDefinitionDisplayName) ($($entry.policySetDefinitionName))"
+ }
+ }
+ else {
+ $psd = $entry.policySetDefinitionType
+ }
+
+ @"
+
+$($entry.policyAssignmentScopeType)
+$($entry.policyAssignmentScope)
+$($entry.policyAssignmentId)
+$($entry.policyAssignmentDisplayName)
+$($entry.policyAssignmentPolicyOrPolicySet)
+$($entry.effect)
+$($entry.policyDefinitionId)
+$($pd)
+$($entry.policyDefinitionType)
+$($entry.policySetPolicyDefinitionReferenceId)
+$($entry.policySetDefinitionId)
+$($psd)
+$($entry.policySetDefinitionType)
+$($entry.nonCompliantResourcesCount)
+
+"@
+ }
+
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyRemediation)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Policies to remediate
+'@)
+ }
+ #endregion SUMMARYPolicyRemediation
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryPolicy
+
+ showMemoryUsage
+
+ #region tenantSummaryRBAC
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+'@)
+
+ #region SUMMARYtenanttotalcustomroles
+ Write-Host ' processing TenantSummary Custom Roles'
+ if ($tenantCustomRolesCount -gt $LimitRBACCustomRoleDefinitionsTenant * ($LimitCriticalPercentage / 100)) {
+ $faimage = "
"
+ }
+ else {
+ $faimage = "
"
+ }
+
+ if ($tenantCustomRolesCount -gt 0) {
+ $tfCount = $tenantCustomRolesCount
+ $htmlTableId = 'TenantSummary_customRoles'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$faimage $tenantCustomRolesCount Custom Role definitions ($scopeNamingSummary) (Limit: $tenantCustomRolesCount/$LimitRBACCustomRoleDefinitionsTenant)
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Assignable Scopes
+Data
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYtenanttotalcustomroles = $null
+ $htmlSUMMARYtenanttotalcustomroles = foreach ($tenantCustomRole in $tenantCustomRoles | Sort-Object @{Expression = { $_.Name } }, @{Expression = { $_.Id } }) {
+ $cachedTenantCustomRole = ($htCacheDefinitionsRole).($tenantCustomRole.Id)
+ if (-not [string]::IsNullOrEmpty($cachedTenantCustomRole.DataActions) -or -not [string]::IsNullOrEmpty($cachedTenantCustomRole.NotDataActions)) {
+ $roleManageData = 'true'
+ }
+ else {
+ $roleManageData = 'false'
+ }
+
+ if (-not [string]::IsNullOrEmpty($cachedTenantCustomRole.Json.properties.createdBy)) {
+ $createdBy = $cachedTenantCustomRole.Json.properties.createdBy
+ if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) {
+ $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details
+ }
+ }
+ else {
+ $createdBy = 'IsNullOrEmpty'
+ }
+
+ $createdOn = $cachedTenantCustomRole.Json.properties.createdOn
+ $createdOnFormated = $createdOn
+ $updatedOn = $cachedTenantCustomRole.Json.properties.updatedOn
+ if ($updatedOn -eq $createdOn) {
+ $updatedOnFormated = ''
+ $updatedByRemoveNoiseOrNot = ''
+ }
+ else {
+ $updatedOnFormated = $updatedOn
+ if (-not [string]::IsNullOrEmpty($cachedTenantCustomRole.Json.properties.updatedBy)) {
+ $updatedByRemoveNoiseOrNot = $cachedTenantCustomRole.Json.properties.updatedBy
+ if ($htIdentitiesWithRoleAssignmentsUnique.($updatedByRemoveNoiseOrNot)) {
+ $updatedByRemoveNoiseOrNot = $htIdentitiesWithRoleAssignmentsUnique.($updatedByRemoveNoiseOrNot).details
+ }
+ }
+ else {
+ $updatedByRemoveNoiseOrNot = 'IsNullOrEmpty'
+ }
+ }
+ @"
+
+$($cachedTenantCustomRole.Name -replace '<', '<' -replace '>', '>')
+$($cachedTenantCustomRole.Id)
+$(($cachedTenantCustomRole.AssignableScopes).count) ($($cachedTenantCustomRole.AssignableScopes -join "$CsvDelimiterOpposite "))
+$($roleManageData)
+$createdOnFormated
+$createdBy
+$updatedOnFormated
+$updatedByRemoveNoiseOrNot
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustomroles)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tenantCustomRolesCount Custom Role definitions ($scopeNamingSummary)
+"@)
+ }
+ #endregion SUMMARYtenanttotalcustomroles
+
+ #region SUMMARYOrphanedCustomRoles
+ $startSUMMARYOrphanedCustomRoles = Get-Date
+ Write-Host ' processing TenantSummary Custom Roles orphaned'
+ if ($getMgParentName -eq 'Tenant Root') {
+ $arrayCustomRolesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@()
+
+ if (($tenantCustomRoles).count -gt 0) {
+ $mgSubRoleAssignmentsArrayRoleDefinitionIdUnique = $mgSubRoleAssignmentsArrayFromHTValues.RoleDefinitionId | Sort-Object -Unique
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ $rgResRoleAssignmentsArrayRoleDefinitionIdUnique = $rgResRoleAssignmentsArrayFromHTValues.RoleDefinitionId | Sort-Object -Unique
+ }
+ foreach ($customRoleAll in $tenantCustomRoles) {
+ $roleIsUsed = $false
+ if (($mgSubRoleAssignmentsArrayRoleDefinitionIdUnique) -contains ($customRoleAll.Id)) {
+ $roleIsUsed = $true
+ }
+
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ if ($roleIsUsed -eq $false) {
+ if (($rgResRoleAssignmentsArrayRoleDefinitionIdUnique) -contains ($customRoleAll.Id)) {
+ $roleIsUsed = $true
+ }
+ }
+ }
+
+ #role used in a policyDef (rule roledefinitionIds)
+ if ($htRoleDefinitionIdsUsedInPolicy.Keys -contains "/providers/Microsoft.Authorization/roleDefinitions/$($customRoleAll.Id)") {
+ $roleIsUsed = $true
+ }
+
+ if ($roleIsUsed -eq $false) {
+ $null = $arrayCustomRolesOrphanedFinalIncludingResourceGroups.Add($customRoleAll)
+ }
+ }
+ }
+
+ if (($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count -gt 0) {
+ $tfCount = ($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count
+ $htmlTableId = 'TenantSummary_customRolesOrphaned'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count) Orphaned Custom Role definitions ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Assignable Scopes
+
+
+
+"@)
+ $htmlSUMMARYOrphanedCustomRoles = $null
+ $htmlSUMMARYOrphanedCustomRoles = foreach ($customRoleOrphaned in $arrayCustomRolesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.Name } }) {
+ @"
+
+$($customRoleOrphaned.Name -replace '<', '<' -replace '>', '>')
+$($customRoleOrphaned.Id)
+$(($customRoleOrphaned.AssignableScopes).count) ($($customRoleOrphaned.AssignableScopes -join "$CsvDelimiterOpposite "))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedCustomRoles)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count) Orphaned Custom Role definitions ($scopeNamingSummary)
+"@)
+ }
+ #not renant root
+ }
+ else {
+ $mgs = (($optimizedTableForPathQueryMg.where( { $_.mgId -ne '' -and $_.Level -ne '0' })) | Select-Object MgId -Unique)
+ $arrayCustomRolesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@()
+
+ $mgSubRoleAssignmentsArrayRoleDefinitionIdUnique = $mgSubRoleAssignmentsArrayFromHTValues.RoleDefinitionId | Sort-Object -Unique
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ $rgResRoleAssignmentsArrayRoleDefinitionIdUnique = $rgResRoleAssignmentsArrayFromHTValues.RoleDefinitionId | Sort-Object -Unique
+ }
+ if (($tenantCustomRoles).count -gt 0) {
+ foreach ($customRoleAll in $tenantCustomRoles) {
+ $roleIsUsed = $false
+ $customRoleAssignableScopes = $customRoleAll.AssignableScopes
+ foreach ($customRoleAssignableScope in $customRoleAssignableScopes) {
+ if (($customRoleAssignableScope) -like '/providers/Microsoft.Management/managementGroups/*') {
+ $roleAssignableScopeMg = $customRoleAssignableScope -replace '/providers/Microsoft.Management/managementGroups/', ''
+ if ($mgs.MgId -notcontains ($roleAssignableScopeMg)) {
+ #assignableScope outside of the ManagementGroupId Scope
+ $roleIsUsed = $true
+ Continue
+ }
+ }
+ }
+ if ($roleIsUsed -eq $false) {
+ if (($mgSubRoleAssignmentsArrayRoleDefinitionIdUnique) -contains ($customRoleAll.Id)) {
+ $roleIsUsed = $true
+ }
+ }
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ if ($roleIsUsed -eq $false) {
+ if (($rgResRoleAssignmentsArrayRoleDefinitionIdUnique) -contains ($customRoleAll.Id)) {
+ $roleIsUsed = $true
+ }
+ }
+ }
+
+ #role used in a policyDef (rule roledefinitionIds)
+ if ($htRoleDefinitionIdsUsedInPolicy.Keys -contains "/providers/Microsoft.Authorization/roleDefinitions/$($customRoleAll.Id)") {
+ $roleIsUsed = $true
+ }
+
+ if ($roleIsUsed -eq $false) {
+ $null = $arrayCustomRolesOrphanedFinalIncludingResourceGroups.Add($customRoleAll)
+ }
+ }
+ }
+
+ if (($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count -gt 0) {
+ $tfCount = ($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count
+ $htmlTableId = 'TenantSummary_customRolesOrphaned'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count) Orphaned Custom Role definitions ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Role Assignable Scopes
+
+
+
+"@)
+ $htmlSUMMARYOrphanedCustomRoles = $null
+ $htmlSUMMARYOrphanedCustomRoles = foreach ($inScopeCustomRole in $arrayCustomRolesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.Name } }) {
+ @"
+
+$($inScopeCustomRole.Name -replace '<', '<' -replace '>', '>')
+$($inScopeCustomRole.Id)
+$(($inScopeCustomRole.AssignableScopes).count) ($($inScopeCustomRole.AssignableScopes -join "$CsvDelimiterOpposite "))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedCustomRoles)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count) Orphaned Custom Role definitions ($scopeNamingSummary)
+"@)
+ }
+ }
+ $endSUMMARYOrphanedCustomRoles = Get-Date
+ Write-Host " SUMMARYOrphanedCustomRoles duration: $((New-TimeSpan -Start $startSUMMARYOrphanedCustomRoles -End $endSUMMARYOrphanedCustomRoles).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYOrphanedCustomRoles -End $endSUMMARYOrphanedCustomRoles).TotalSeconds) seconds)"
+
+ #endregion SUMMARYOrphanedCustomRoles
+
+ #region SUMMARYOrphanedRoleAssignments
+ Write-Host ' processing TenantSummary RoleAssignments orphaned'
+ $roleAssignmentsOrphanedAll = ($rbacBaseQuery.where( { $_.RoleAssignmentIdentityObjectType -eq 'Unknown' })) | Sort-Object -Property RoleAssignmentId
+ $roleAssignmentsOrphanedUnique = $roleAssignmentsOrphanedAll | Sort-Object -Property RoleAssignmentId -Unique
+
+ if (($roleAssignmentsOrphanedUnique).count -gt 0) {
+ $tfCount = ($roleAssignmentsOrphanedUnique).count
+ $htmlTableId = 'TenantSummary_roleAssignmentsOrphaned'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($roleAssignmentsOrphanedUnique).count) Orphaned Role assignments ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role AssignmentId
+Role Name
+RoleId
+Impacted Mg/Sub
+
+
+
+"@)
+ $htmlSUMMARYOrphanedRoleAssignments = $null
+ foreach ($roleAssignmentOrphanedUnique in $roleAssignmentsOrphanedUnique) {
+ $hlpRoleAssignmentsAll = $roleAssignmentsOrphanedAll.where( { $_.RoleAssignmentId -eq $roleAssignmentOrphanedUnique.RoleAssignmentId })
+ $impactedMgs = $hlpRoleAssignmentsAll.where( { [String]::IsNullOrEmpty($_.SubscriptionId) }) | Sort-Object -Property MgId
+ $impactedSubs = $hlpRoleAssignmentsAll.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) }) | Sort-Object -Property SubscriptionId
+ $htmlSUMMARYOrphanedRoleAssignments += @"
+
+$($roleAssignmentOrphanedUnique.RoleAssignmentId)
+$($roleAssignmentOrphanedUnique.RoleDefinitionName -replace '<', '<' -replace '>', '>')
+$($roleAssignmentOrphanedUnique.RoleDefinitionId)
+Mg: $(($impactedMgs).count); Sub: $(($impactedSubs).count)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedRoleAssignments)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($roleAssignmentsOrphanedUnique).count) Orphaned Role assignments ($scopeNamingSummary)
+"@)
+ }
+ #endregion SUMMARYOrphanedRoleAssignments
+
+ #region SUMMARYClassicAdministrators
+ Write-Host ' processing TenantSummary ClassicAdministrators'
+
+ if ($htClassicAdministrators.Keys.Count -gt 0) {
+ $tfCount = $htClassicAdministrators.Values.ClassicAdministrators.Count
+ $htmlTableId = 'TenantSummary_ClassicAdministrators'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($tfCount) Classic Administrators
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+MgPath
+Role
+Identity
+
+
+
+"@)
+ $htmlSUMMARYClassicAdministrators = $null
+ $classicAdministrators = $htClassicAdministrators.Values.ClassicAdministrators | Sort-Object -Property Subscription, Role, Identity
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_ClassicAdministrators"
+ Write-Host " Exporting ClassicAdministrators CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ $classicAdministrators | Select-Object -ExcludeProperty Id | Sort-Object -Property Subscription, SubscriptionId, Role | Export-Csv -Encoding utf8 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -NoTypeInformation
+ }
+ $htmlSUMMARYClassicAdministrators = foreach ($classicAdministrator in $classicAdministrators) {
+ @"
+
+$($classicAdministrator.Subscription)
+$($classicAdministrator.SubscriptionId)
+$($classicAdministrator.SubscriptionMgPath)
+$($classicAdministrator.Role)
+$($classicAdministrator.Identity)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYClassicAdministrators)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No ClassicAdministrators
+'@)
+ }
+ #endregion SUMMARYClassicAdministrators
+
+ #region SUMMARYRoleAssignmentsAll
+ $startRoleAssignmentsAll = Get-Date
+ Write-Host ' processing TenantSummary RoleAssignments'
+
+ $startCreateRBACAllHTMLbeforeForeach = Get-Date
+
+ if ($azAPICallConf['htParameters'].LargeTenant -or $azAPICallConf['htParameters'].RBACAtScopeOnly) {
+ $rbacAllAtScope = ($rbacAll.where( { ((-not [string]::IsNullOrEmpty($_.SubscriptionId) -and $_.scope -notlike 'inherited *')) -or ([string]::IsNullOrEmpty($_.SubscriptionId)) }))
+ $rbacAllCount = $rbacAllAtScope.Count
+ $rbacAllUniqueCount = ($rbacAllAtScope.where({ $_.roleAssignmentId }).RoleAssignmentId | Sort-Object -Unique).count
+ }
+ else {
+ $rbacAllCount = $rbacAll.Count
+ $rbacAllUniqueCount = ($rbacAll.where({ $_.roleAssignmentId }).RoleAssignmentId | Sort-Object -Unique).count
+ }
+
+ if ($rbacAllCount -gt 0) {
+ $uniqueRoleAssignmentsCount = ($rbacAll.RoleAssignmentId | Sort-Object -Unique).count
+ $tfCount = $rbacAllCount
+
+ if (-not $NoCsvExport) {
+ $startCreateRBACAllCSV = Get-Date
+
+ $csvFilename = "$($filename)_RoleAssignments"
+ Write-Host " Exporting RoleAssignments CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ if ($CsvExportUseQuotesAsNeeded) {
+ if ($azAPICallConf['htParameters'].LargeTenant -or $azAPICallConf['htParameters'].RBACAtScopeOnly) {
+ $rbacAllAtScope | Sort-Object -Property Level, RoleAssignmentId, MgId, SubscriptionId, RoleClear, ObjectId | Select-Object -ExcludeProperty Role, RbacRelatedPolicyAssignment | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded
+ }
+ else {
+ $rbacAll | Sort-Object -Property Level, RoleAssignmentId, MgId, SubscriptionId, RoleClear, ObjectId | Select-Object -ExcludeProperty Role, RbacRelatedPolicyAssignment | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded
+ }
+ }
+ else {
+ if ($azAPICallConf['htParameters'].LargeTenant -or $azAPICallConf['htParameters'].RBACAtScopeOnly) {
+ $rbacAllAtScope | Sort-Object -Property Level, RoleAssignmentId, MgId, SubscriptionId, RoleClear, ObjectId | Select-Object -ExcludeProperty Role, RbacRelatedPolicyAssignment | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+ else {
+ $rbacAll | Sort-Object -Property Level, RoleAssignmentId, MgId, SubscriptionId, RoleClear, ObjectId | Select-Object -ExcludeProperty Role, RbacRelatedPolicyAssignment | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+ }
+
+ $endCreateRBACAllCSV = Get-Date
+ Write-Host " CreateRBACAll CSV duration: $((New-TimeSpan -Start $startCreateRBACAllCSV -End $endCreateRBACAllCSV).TotalMinutes) minutes ($((New-TimeSpan -Start $startCreateRBACAllCSV -End $endCreateRBACAllCSV).TotalSeconds) seconds)"
+ }
+
+ if ($tfCount -gt $HtmlTableRowsLimit) {
+ Write-Host " !Skipping TenantSummary RoleAssignments HTML processing as $tfCount lines is exceeding the critical rows limit of $HtmlTableRowsLimit" -ForegroundColor Yellow
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($rbacAllCount) Role assignments ($uniqueRoleAssignmentsCount unique)
+
+
+
Output of $tfCount lines would exceed the html rows limit of $HtmlTableRowsLimit (html file potentially would become unresponsive). Work with the CSV file $($csvFilename).csv | Note: the CSV file will only exist if you did NOT use parameter -NoCsvExport
+
You can adjust the html row limit by using parameter -HtmlTableRowsLimit
+
You can reduce the number of lines by using parameter -LargeTenant and/or -DoNotIncludeResourceGroupsAndResourcesOnRBAC
+
Check the parameters documentation Azure Governance Visualizer docs
+
+"@)
+ }
+ else {
+
+ $roleAssignmentsInfo = @()
+ #all
+ $roleAssignmentsInfo += "All: $($rbacAllUniqueCount)"
+ #static
+ $roleAssignmentsInfo += "Standing: $((($rbacAll.where({ $_.RoleAssignmentPIMRelated -eq $false })).roleAssignmentId | Sort-Object -Unique).count)"
+ #PIM
+ foreach ($pimAssignmentInfo in ($rbacAll.where({ $_.RoleAssignmentPIMRelated -and $_.Scope -notlike 'inherited*' })) | Group-Object -Property RoleAssignmentPIMAssignmentType) {
+ $roleAssignmentsInfo += "PIM-$($pimAssignmentInfo.Name): $($pimAssignmentInfo.Count)"
+ }
+
+ $htmlTableId = 'TenantSummary_roleAssignmentsAll'
+ $noteOrNot = ''
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($rbacAllCount) Role assignment related entries (unique -> $($roleAssignmentsInfo -join ', '))
+
+
+
Download CSV
semicolon |
comma
+
*Depending on the number of rows and your computer´s performance the table may respond with delay, download the csv for better filtering experience
+"@)
+
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+Scope
+Management Group Id
+Management Group Name
+SubscriptionId
+Subscription Name
+Assignment Scope
+Role
+Role Id
+Role Type
+Data
+Can do Role assignment
+Identity Displayname
+Identity SignInName
+Identity ObjectId
+Identity Type
+Applicability
+Applies through membership
+Group Details
+PIM
+PIM assignment type
+PIM start
+PIM end
+Role AssignmentId
+Related Policy Assignment $noteOrNot
+CreatedOn
+CreatedBy
+
+
+
+"@)
+ $cnter = 0
+ $roleAssignmentsAllCount = $rbacAllCount
+ $htmlSummaryRoleAssignmentsAll = $null
+ $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlTenantSummary = [System.Text.StringBuilder]::new()
+
+ $endCreateRBACAllHTMLbeforeForeach = Get-Date
+ Write-Host " CreateRBACAll HTML before Foreach duration: $((New-TimeSpan -Start $startCreateRBACAllHTMLbeforeForeach -End $endCreateRBACAllHTMLbeforeForeach).TotalMinutes) minutes ($((New-TimeSpan -Start $startCreateRBACAllHTMLbeforeForeach -End $endCreateRBACAllHTMLbeforeForeach).TotalSeconds) seconds)"
+
+ $startSortRBACAll = Get-Date
+ if ($azAPICallConf['htParameters'].LargeTenant -or $azAPICallConf['htParameters'].RBACAtScopeOnly) {
+ $rbacAllSorted = $rbacAllAtScope | Sort-Object -Property Level, MgName, MgId, SubscriptionName, SubscriptionId, Scope, Role, RoleId, ObjectId, RoleAssignmentId
+ }
+ else {
+ $rbacAllSorted = $rbacAll | Sort-Object -Property Level, MgName, MgId, SubscriptionName, SubscriptionId, Scope, Role, RoleId, ObjectId, RoleAssignmentId
+ }
+
+ $endSortRBACAll = Get-Date
+ Write-Host " Sort RBACAll duration: $((New-TimeSpan -Start $startSortRBACAll -End $endSortRBACAll).TotalMinutes) minutes ($((New-TimeSpan -Start $startSortRBACAll -End $endSortRBACAll).TotalSeconds) seconds)"
+
+ $startCreateRBACAllHTMLForeach = Get-Date
+ $htmlSummaryRoleAssignmentsAll = [System.Text.StringBuilder]::new()
+ foreach ($roleAssignment in $rbacAllSorted) {
+ $cnter++
+ if ($cnter % 1000 -eq 0) {
+ Write-Host " create HTML $cnter of $rbacAllCount RoleAssignments processed"
+ if ($cnter % 5000 -eq 0) {
+ Write-Host ' appending..'
+ $htmlSummaryRoleAssignmentsAll | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlSummaryRoleAssignmentsAll = [System.Text.StringBuilder]::new()
+ }
+ }
+
+ if ($roleAssignment.RoleType -eq 'Custom') {
+ $roleName = ($roleAssignment.Role -replace '<', '<' -replace '>', '>')
+ }
+ else {
+ $roleName = $roleAssignment.Role
+ }
+
+ [void]$htmlSummaryRoleAssignmentsAll.AppendFormat(
+ @'
+
+{0}
+{1}
+{2}
+{3}
+{4}
+{5}
+{6}
+{7}
+{8}
+{9}
+{10}
+{11}
+{12}
+{13}
+{14}
+{15}
+{16}
+{17}
+{18}
+{19}
+{20}
+{21}
+{22}
+{23}
+{24}
+{25}
+
+'@, $roleAssignment.ScopeTenOrMgOrSubOrRGOrRes,
+ $roleAssignment.MgId,
+ ($roleAssignment.MgName -replace '<', '<' -replace '>', '>'),
+ $roleAssignment.SubscriptionId,
+ $roleAssignment.SubscriptionName,
+ $roleAssignment.Scope,
+ $roleName,
+ $roleAssignment.RoleId,
+ $roleAssignment.RoleType,
+ $roleAssignment.RoleDataRelated,
+ $roleAssignment.RoleCanDoRoleAssignments,
+ $roleAssignment.ObjectDisplayName,
+ $roleAssignment.ObjectSignInName,
+ $roleAssignment.ObjectId,
+ $roleAssignment.ObjectType,
+ $roleAssignment.AssignmentType,
+ $roleAssignment.AssignmentInheritFrom,
+ $roleAssignment.GroupMembersCount,
+ $roleAssignment.RoleAssignmentPIMRelated,
+ $roleAssignment.RoleAssignmentPIMAssignmentType,
+ $roleAssignment.RoleAssignmentPIMAssignmentSlotStart,
+ $roleAssignment.RoleAssignmentPIMAssignmentSlotEnd,
+ $roleAssignment.RoleAssignmentId,
+ ($roleAssignment.RbacRelatedPolicyAssignment),
+ $roleAssignment.CreatedOn,
+ $roleAssignment.CreatedBy
+ )
+
+ }
+ $start = Get-Date
+ [void]$htmlTenantSummary.AppendLine($htmlSummaryRoleAssignmentsAll)
+
+ $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $htmlSummaryRoleAssignmentsAll = $null #cleanup
+ $htmlTenantSummary = [System.Text.StringBuilder]::new()
+ $end = Get-Date
+
+ $endCreateRBACAllHTMLForeach = Get-Date
+ Write-Host " CreateRBACAll HTML Foreach duration: $((New-TimeSpan -Start $startCreateRBACAllHTMLForeach -End $endCreateRBACAllHTMLForeach).TotalMinutes) minutes ($((New-TimeSpan -Start $startCreateRBACAllHTMLForeach -End $endCreateRBACAllHTMLForeach).TotalSeconds) seconds)"
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+
+ }
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($rbacAllCount) Role assignments
+"@)
+ }
+
+ $endRoleAssignmentsAll = Get-Date
+ Write-Host " SummaryRoleAssignmentsAll duration: $((New-TimeSpan -Start $startRoleAssignmentsAll -End $endRoleAssignmentsAll).TotalMinutes) minutes ($((New-TimeSpan -Start $startRoleAssignmentsAll -End $endRoleAssignmentsAll).TotalSeconds) seconds)"
+ #endregion SUMMARYRoleAssignmentsAll
+
+ #region SUMMARYPIMEligibility
+ if (-not $NoPIMEligibility) {
+ $startPIMEligibility = Get-Date
+ Write-Host ' processing TenantSummary PIMEligibility'
+
+ if ($arrayPIMEligible.Count -gt 0) {
+ $tfCount = $arrayPIMEligible.Count
+ $htmlTableId = 'TenantSummary_PIMEligibility'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($tfCount) direct PIM Eligible assignments
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+ScopeId
+ScopeName
+MgPath
+MgLevel
+Role
+Role Id
+Role type
+Identity ObjectId
+Identity DisplayName
+Identity SignInName
+Identity Type
+Identity Applicability
+Applies through (AAD Grp)
+PIM Eligibility
+PIM Eligibility inhherted (MG)
+PIM start
+PIM end
+PIM Eligibility Id
+
+
+
+"@)
+ $htmlSUMMARYPIMEligibility = $null
+ $PIMEligibleEnrichedSorted = $PIMEligibleEnriched | Sort-Object -Property Scope, MgLevel, ScopeName, IdentityDisplayName, PIMEligibilityId
+ $tfCountCnt = $PIMEligibleEnrichedSorted.Count
+ $htmlSUMMARYPIMEligibility = foreach ($PIMEligible in $PIMEligibleEnrichedSorted) {
+ @"
+
+$($PIMEligible.Scope)
+$($PIMEligible.ScopeId)
+$($PIMEligible.ScopeName)
+$($PIMEligible.MgPath -join '/')
+$($PIMEligible.MgLevel)
+$($PIMEligible.Role)
+$($PIMEligible.RoleIdGuid)
+$($PIMEligible.RoleType)
+$($PIMEligible.IdentityObjectId)
+$($PIMEligible.IdentityDisplayName)
+$($PIMEligible.IdentitySignInName)
+$($PIMEligible.IdentityType)
+$($PIMEligible.IdentityApplicability)
+$($PIMEligible.AppliesThrough)
+$($PIMEligible.PIMEligibility)
+$($PIMEligible.PIMEligibilityInheritedFrom)
+$($PIMEligible.PIMEligibilityStartDateTime)
+$($PIMEligible.PIMEligibilityEndDateTime)
+$($PIMEligible.PIMEligibilityId)
+
+"@
+ }
+
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_PIMEligibility"
+ Write-Host " Exporting PIMEligibility CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ $PIMEligibleEnrichedSorted | Select-Object -ExcludeProperty RoleClear | Export-Csv -Encoding utf8 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -NoTypeInformation
+ }
+
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPIMEligibility)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No PIM Eligibility
+'@)
+ }
+
+ $endPIMEligibility = Get-Date
+ Write-Host " TenantSummary PIMEligibility duration: $((New-TimeSpan -Start $startPIMEligibility -End $endPIMEligibility).TotalMinutes) minutes ($((New-TimeSpan -Start $startPIMEligibility -End $endPIMEligibility).TotalSeconds) seconds)"
+ }
+ else {
+ if ($azAPICallConf['htParameters'].accountType -ne 'User' -and $NoPIMEligibility) {
+ [void]$htmlTenantSummary.AppendLine(@"
+
No PIM Eligibility - parameter -NoPIMEligibility = $NoPIMEligibility
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No PIM Eligibility - run Azure Governance Visualizer with a Service Principal to get PIM Eligibility insights
+'@)
+ }
+ }
+ #endregion SUMMARYPIMEligibility
+
+ #region SUMMARYSecurityCustomRoles
+ Write-Host ' processing TenantSummary Custom Roles security (owner permissions)'
+ $customRolesOwnerAll = ($rbacBaseQuery.where( { $_.RoleSecurityCustomRoleOwner -eq 1 })) | Sort-Object -Property RoleDefinitionId
+ $customRolesOwnerHtAll = $tenantCustomRoles.where( { $_.Actions -eq '*' -and ($_.NotActions).length -eq 0 })
+ if (($customRolesOwnerHtAll).count -gt 0) {
+ $tfCount = ($customRolesOwnerHtAll).count
+ $htmlTableId = 'TenantSummary_CustomRoleOwner'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($customRolesOwnerHtAll).count) Custom Role definitions Owner permissions ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Role assignments
+Assignable Scopes
+
+
+
+"@)
+ $htmlSUMMARYSecurityCustomRoles = $null
+ foreach ($customRole in ($customRolesOwnerHtAll | Sort-Object -Property Name, Id)) {
+ $customRoleOwnersAllAssignmentsCount = ((($customRolesOwnerAll.where( { $_.RoleDefinitionId -eq $customRole.Id })).RoleAssignmentId | Sort-Object -Unique)).count
+ if ($customRoleOwnersAllAssignmentsCount -gt 0) {
+ $customRoleRoleAssignmentsArray = [System.Collections.ArrayList]@()
+ $customRoleRoleAssignmentIds = ($customRolesOwnerAll.where( { $_.RoleDefinitionId -eq $customRole.Id })).RoleAssignmentId | Sort-Object -Unique
+ foreach ($customRoleRoleAssignmentId in $customRoleRoleAssignmentIds) {
+ $null = $customRoleRoleAssignmentsArray.Add($customRoleRoleAssignmentId)
+ }
+ $customRoleRoleAssignmentsOutput = "$customRoleOwnersAllAssignmentsCount ($($customRoleRoleAssignmentsArray -join "$CsvDelimiterOpposite "))"
+ }
+ else {
+ $customRoleRoleAssignmentsOutput = "$customRoleOwnersAllAssignmentsCount"
+ }
+ $htmlSUMMARYSecurityCustomRoles += @"
+
+$($customRole.Name -replace '<', '<' -replace '>', '>')
+$($customRole.Id)
+$($customRoleRoleAssignmentsOutput)
+$(($customRole.AssignableScopes).count) ($($customRole.AssignableScopes -join "$CsvDelimiterOpposite "))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityCustomRoles)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($customRolesOwnerHtAll).count) Custom Role definitions Owner permissions ($scopeNamingSummary)
+"@)
+ }
+ #endregion SUMMARYSecurityCustomRoles
+
+ #region SUMMARYSecurityRolesCanDoRoleAssignments
+ Write-Host ' processing TenantSummary Roles security (can apply Role assignments)'
+ if ($tenantAllRolesCanDoRoleAssignmentsCount -gt 0) {
+
+ #$roleAssignments4RolesCanDoRoleAssignments = (($rbacBaseQuery.where( { $_.RoleCanDoRoleAssignments -eq $true })) | Sort-Object -Property RoleAssignmentId -Unique) | Select-Object RoleAssignmentId, RoleDefinitionId
+ $roleAssignments4RolesCanDoRoleAssignments = (($rbacBaseQuery | Sort-Object -Property RoleAssignmentId -Unique).where( { $_.RoleCanDoRoleAssignments -eq $true })) | Select-Object RoleAssignmentId, RoleDefinitionId
+ $htRoleAssignments4RolesCanDoRoleAssignments = @{}
+ foreach ($roleAssignment4RolesCanDoRoleAssignments in $roleAssignments4RolesCanDoRoleAssignments) {
+ if (-not $htRoleAssignments4RolesCanDoRoleAssignments.($roleAssignment4RolesCanDoRoleAssignments.RoleDefinitionId)) {
+ $htRoleAssignments4RolesCanDoRoleAssignments.($roleAssignment4RolesCanDoRoleAssignments.RoleDefinitionId) = @{}
+ $htRoleAssignments4RolesCanDoRoleAssignments.($roleAssignment4RolesCanDoRoleAssignments.RoleDefinitionId).roleAssignments = [System.Collections.ArrayList]@()
+ }
+ $null = $htRoleAssignments4RolesCanDoRoleAssignments.($roleAssignment4RolesCanDoRoleAssignments.RoleDefinitionId).roleAssignments.Add($roleAssignment4RolesCanDoRoleAssignments.RoleAssignmentId)
+ }
+
+ $tfCount = $tenantAllRolesCanDoRoleAssignmentsCount
+ $htmlTableId = 'TenantSummary_RolesCanDoRoleAssignments'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($tenantAllRolesCanDoRoleAssignmentsCount) Role definitions can apply Role assignments
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Type
+Role assignments
+Assignable Scopes
+
+
+
+"@)
+ $htmlSUMMARYSecurityRolesCanDoRoleAssignments = $null
+ foreach ($role in ($tenantAllRolesCanDoRoleAssignments | Sort-Object -Property Name)) {
+ if ($role.IsCustom) {
+ $roleType = 'Custom'
+ $roleAssignableScopes = "$(($role.AssignableScopes).count) ($($role.AssignableScopes -join "$CsvDelimiterOpposite "))"
+ }
+ else {
+ $roleType = 'BuiltIn'
+ $roleAssignableScopes = ''
+ }
+
+ if ($htRoleAssignments4RolesCanDoRoleAssignments.($role.Id).roleAssignments.Count -gt 0) {
+ $roleAssignments = "$($htRoleAssignments4RolesCanDoRoleAssignments.($role.Id).roleAssignments.Count) ($($htRoleAssignments4RolesCanDoRoleAssignments.($role.Id).roleAssignments -join ', '))"
+ }
+ else {
+ $roleAssignments = 0
+ }
+
+ $htmlSUMMARYSecurityRolesCanDoRoleAssignments += @"
+
+$($role.Name -replace '<', '<' -replace '>', '>')
+$($role.Id)
+$($roleType)
+$($roleAssignments)
+$($roleAssignableScopes)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityRolesCanDoRoleAssignments)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($tenantAllRolesCanDoRoleAssignmentsCount) Role definitions can apply Role assignments
+"@)
+ }
+ #endregion SUMMARYSecurityRolesCanDoRoleAssignments
+
+ #region SUMMARYSecurityOwnerAssignmentSP
+ $startSUMMARYSecurityOwnerAssignmentSP = Get-Date
+ Write-Host ' processing TenantSummary RoleAssignments security (owner SP)'
+ $roleAssignmentsOwnerAssignmentSPAll = ($rbacBaseQuery.where( { $_.RoleSecurityOwnerAssignmentSP -eq 1 })) | Sort-Object -Property RoleAssignmentId
+ $roleAssignmentsOwnerAssignmentSP = $roleAssignmentsOwnerAssignmentSPAll | Sort-Object -Property RoleAssignmentId -Unique
+ if (($roleAssignmentsOwnerAssignmentSP).count -gt 0) {
+ $tfCount = ($roleAssignmentsOwnerAssignmentSP).count
+ $htmlTableId = 'TenantSummary_roleAssignmentsOwnerAssignmentSP'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($roleAssignmentsOwnerAssignmentSP).count) Owner permission assignments to ServicePrincipal ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Role Assignment
+ServicePrincipal (ObjId)
+Impacted Mg/Sub
+
+
+
+"@)
+ $htmlSUMMARYSecurityOwnerAssignmentSP = $null
+ $htmlSUMMARYSecurityOwnerAssignmentSP = foreach ($roleAssignmentOwnerAssignmentSP in ($roleAssignmentsOwnerAssignmentSP)) {
+ $hlpRoleAssignmentsAll = $roleAssignmentsOwnerAssignmentSPAll.where( { $_.RoleAssignmentId -eq $roleAssignmentOwnerAssignmentSP.RoleAssignmentId })
+ $impactedMgs = $hlpRoleAssignmentsAll.where( { [String]::IsNullOrEmpty($_.SubscriptionId) })
+ $impactedSubs = $hlpRoleAssignmentsAll.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) })
+ $servicePrincipal = $roleAssignmentsOwnerAssignmentSP.where( { $_.RoleAssignmentId -eq $roleAssignmentOwnerAssignmentSP.RoleAssignmentId }) | Get-Unique
+ @"
+
+$($roleAssignmentOwnerAssignmentSP.RoleDefinitionName -replace '<', '<' -replace '>', '>')
+$($roleAssignmentOwnerAssignmentSP.RoleDefinitionId)
+$($roleAssignmentOwnerAssignmentSP.RoleAssignmentId)
+$($servicePrincipal.RoleAssignmentIdentityDisplayname) ($($servicePrincipal.RoleAssignmentIdentityObjectId))
+Mg: $(($impactedMgs.mgid | Sort-Object -Unique).count); Sub: $(($impactedSubs.subscriptionId | Sort-Object -Unique).count)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityOwnerAssignmentSP)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($roleAssignmentsOwnerAssignmentSP).count) Owner permission assignments to ServicePrincipal ($scopeNamingSummary)
+"@)
+ }
+ $endSUMMARYSecurityOwnerAssignmentSP = Get-Date
+ Write-Host " TenantSummary RoleAssignments security (owner SP) duration: $((New-TimeSpan -Start $startSUMMARYSecurityOwnerAssignmentSP -End $endSUMMARYSecurityOwnerAssignmentSP).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYSecurityOwnerAssignmentSP -End $endSUMMARYSecurityOwnerAssignmentSP).TotalSeconds) seconds)"
+ #endregion SUMMARYSecurityOwnerAssignmentSP
+
+ #region SUMMARYSecurityOwnerAssignmentNotGroup
+ Write-Host ' processing TenantSummary RoleAssignments security (owner notGroup)'
+ $startSUMMARYSecurityOwnerAssignmentNotGroup = Get-Date
+
+ $roleAssignmentsOwnerAssignmentNotGroup = $rbacBaseQueryArrayListNotGroupOwner | Sort-Object -Property RoleAssignmentId -Unique
+ $roleAssignmentsOwnerAssignmentNotGroupGrouped = ($rbacBaseQueryArrayListNotGroupOwner | Group-Object -Property roleassignmentId)
+
+ if (($roleAssignmentsOwnerAssignmentNotGroup).count -gt 0) {
+ $tfCount = ($roleAssignmentsOwnerAssignmentNotGroup).count
+ $htmlTableId = 'TenantSummary_roleAssignmentsOwnerAssignmentNotGroup'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($roleAssignmentsOwnerAssignmentNotGroup).count) Owner permission assignments to notGroup ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Role Assignment
+Obj Type
+Obj DisplayName
+Obj SignInName
+ObjId
+Impacted Mg/Sub
+
+
+
+"@)
+ $htmlSUMMARYSecurityOwnerAssignmentNotGroup = $null
+ $htmlSUMMARYSecurityOwnerAssignmentNotGroup = foreach ($roleAssignmentOwnerAssignmentNotGroup in ($roleAssignmentsOwnerAssignmentNotGroup)) {
+ $impactedMgSubBaseQuery = $roleAssignmentsOwnerAssignmentNotGroupGrouped.where( { $_.Name -eq $roleAssignmentOwnerAssignmentNotGroup.RoleAssignmentId })
+ $impactedMgs = $impactedMgSubBaseQuery.Group.where( { [String]::IsNullOrEmpty($_.SubscriptionId) })
+ $impactedSubs = $impactedMgSubBaseQuery.Group.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) })
+ @"
+
+$($roleAssignmentOwnerAssignmentNotGroup.RoleDefinitionName -replace '<', '<' -replace '>', '>')
+$($roleAssignmentOwnerAssignmentNotGroup.RoleDefinitionId)
+$($roleAssignmentOwnerAssignmentNotGroup.RoleAssignmentId)
+$($roleAssignmentOwnerAssignmentNotGroup.RoleAssignmentIdentityObjectType)
+$($roleAssignmentOwnerAssignmentNotGroup.RoleAssignmentIdentityDisplayname)
+$($roleAssignmentOwnerAssignmentNotGroup.RoleAssignmentIdentitySignInName)
+$($roleAssignmentOwnerAssignmentNotGroup.RoleAssignmentIdentityObjectId)
+Mg: $(($impactedMgs.mgid | Sort-Object -Unique).count); Sub: $(($impactedSubs.subscriptionId | Sort-Object -Unique).count)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityOwnerAssignmentNotGroup)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($roleAssignmentsOwnerAssignmentNotGroup).count) Owner permission assignments to notGroup ($scopeNamingSummary)
+"@)
+ }
+ $endSUMMARYSecurityOwnerAssignmentNotGroup = Get-Date
+ Write-Host " TenantSummary RoleAssignments security (owner notGroup) duration: $((New-TimeSpan -Start $startSUMMARYSecurityOwnerAssignmentNotGroup -End $endSUMMARYSecurityOwnerAssignmentNotGroup).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYSecurityOwnerAssignmentNotGroup -End $endSUMMARYSecurityOwnerAssignmentNotGroup).TotalSeconds) seconds)"
+ #endregion SUMMARYSecurityOwnerAssignmentNotGroup
+
+ #region SUMMARYSecurityUserAccessAdministratorAssignmentNotGroup
+ $startSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup = Get-Date
+ Write-Host ' processing TenantSummary RoleAssignments security (userAccessAdministrator notGroup)'
+ $roleAssignmentsUserAccessAdministratorAssignmentNotGroup = $rbacBaseQueryArrayListNotGroupUserAccessAdministrator | Sort-Object -Property RoleAssignmentId -Unique
+ $roleAssignmentsUserAccessAdministratorAssignmentNotGroupGrouped = ($rbacBaseQueryArrayListNotGroupUserAccessAdministrator | Group-Object -Property roleassignmentId)
+
+ if (($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count -gt 0) {
+ $tfCount = ($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count
+ $htmlTableId = 'TenantSummary_roleAssignmentsUserAccessAdministratorAssignmentNotGroup'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count) UserAccessAdministrator permission assignments to notGroup ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Role Assignment
+Obj Type
+Obj DisplayName
+Obj SignInName
+ObjId
+Impacted Mg/Sub
+
+
+
+"@)
+ $htmlSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup = $null
+ $htmlSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup = foreach ($roleAssignmentUserAccessAdministratorAssignmentNotGroup in ($roleAssignmentsUserAccessAdministratorAssignmentNotGroup)) {
+ $impactedMgSubBaseQuery = $roleAssignmentsUserAccessAdministratorAssignmentNotGroupGrouped.where( { $_.Name -eq $roleAssignmentUserAccessAdministratorAssignmentNotGroup.RoleAssignmentId })
+ $impactedMgs = $impactedMgSubBaseQuery.Group.where( { [String]::IsNullOrEmpty($_.SubscriptionId) })
+ $impactedSubs = $impactedMgSubBaseQuery.Group.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) })
+ @"
+
+$($roleAssignmentUserAccessAdministratorAssignmentNotGroup.RoleDefinitionName -replace '<', '<' -replace '>', '>')
+$($roleAssignmentUserAccessAdministratorAssignmentNotGroup.RoleDefinitionId)
+$($roleAssignmentUserAccessAdministratorAssignmentNotGroup.RoleAssignmentId)
+$($roleAssignmentUserAccessAdministratorAssignmentNotGroup.RoleAssignmentIdentityObjectType)
+$($roleAssignmentUserAccessAdministratorAssignmentNotGroup.RoleAssignmentIdentityDisplayname)
+$($roleAssignmentUserAccessAdministratorAssignmentNotGroup.RoleAssignmentIdentitySignInName)
+$($roleAssignmentUserAccessAdministratorAssignmentNotGroup.RoleAssignmentIdentityObjectId)
+Mg: $(($impactedMgs.mgid | Sort-Object -Unique).count); Sub: $(($impactedSubs.subscriptionId | Sort-Object -Unique).count)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count) UserAccessAdministrator permission assignments to notGroup ($scopeNamingSummary)
+"@)
+ }
+ $endSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup = Get-Date
+ Write-Host " TenantSummary RoleAssignments security (userAccessAdministrator notGroup) duration: $((New-TimeSpan -Start $startSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup -End $endSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup -End $endSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup).TotalSeconds) seconds)"
+ #endregion SUMMARYSecurityUserAccessAdministratorAssignmentNotGroup
+
+ #region SUMMARYSecurityGuestUserHighPriviledgesAssignments
+
+ $startSUMMARYSecurityGuestUserHighPriviledgesAssignments = Get-Date
+ Write-Host ' processing TenantSummary RoleAssignments security (high privileged Guest User)'
+ $highPrivilegedGuestUserRoleAssignments = $rbacAll.where( { ($_.RoleId -eq '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' -or $_.RoleId -eq '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') -and $_.ObjectType -eq 'User Guest' }) | Sort-Object -Property RoleAssignmentId, ObjectId -Unique
+ $highPrivilegedGuestUserRoleAssignmentsCount = ($highPrivilegedGuestUserRoleAssignments).Count
+ if ($highPrivilegedGuestUserRoleAssignmentsCount -gt 0) {
+ $tfCount = $highPrivilegedGuestUserRoleAssignmentsCount
+ $htmlTableId = 'TenantSummary_SecurityGuestUserHighPriviledgesAssignments'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($highPrivilegedGuestUserRoleAssignmentsCount) Guest Users with high permissions ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Role Assignment
+Obj Type
+Obj DisplayName
+Obj SignInName
+ObjId
+Assignment direct/indirect
+
+
+
+"@)
+ $htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments = $null
+ $htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments = foreach ($highPrivilegedGuestUserRoleAssignment in ($highPrivilegedGuestUserRoleAssignments)) {
+ if ($highPrivilededGuestUserRoleAssignment.AssignmentType -eq 'indirect') {
+ $assignmentInfo = "indirect / AAD Group Membership '$($highPrivilededGuestUserRoleAssignment.AssignmentInheritFrom)'"
+ }
+ else {
+ $assignmentInfo = 'direct'
+ }
+ @"
+
+$($highPrivilegedGuestUserRoleAssignment.Role <#-replace "<", "<" -replace ">", ">"#>)
+$($highPrivilegedGuestUserRoleAssignment.RoleId)
+$($highPrivilegedGuestUserRoleAssignment.RoleAssignmentId)
+$($highPrivilegedGuestUserRoleAssignment.ObjectType)
+$($highPrivilegedGuestUserRoleAssignment.ObjectDisplayName)
+$($highPrivilegedGuestUserRoleAssignment.ObjectSignInName)
+$($highPrivilegedGuestUserRoleAssignment.ObjectId)
+$assignmentInfo
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($highPrivilegedGuestUserRoleAssignmentsCount) Guest Users with high permissions ($scopeNamingSummary)
+"@)
+ }
+ $endSUMMARYSecurityGuestUserHighPriviledgesAssignments = Get-Date
+ Write-Host " TenantSummary RoleAssignments security (high privileged Guest User) duration: $((New-TimeSpan -Start $startSUMMARYSecurityGuestUserHighPriviledgesAssignments -End $endSUMMARYSecurityGuestUserHighPriviledgesAssignments).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYSecurityGuestUserHighPriviledgesAssignments -End $endSUMMARYSecurityGuestUserHighPriviledgesAssignments).TotalSeconds) seconds)"
+ #endregion SUMMARYSecurityGuestUserHighPriviledgesAssignments
+
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryRBAC
+
+ showMemoryUsage
+
+ #region tenantSummaryBlueprints
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+'@)
+
+ #region SUMMARYBlueprintDefinitions
+ Write-Host ' processing TenantSummary Blueprints'
+ $blueprintDefinitions = ($blueprintBaseQuery | Where-Object { [String]::IsNullOrEmpty($_.BlueprintAssignmentId) })
+ $blueprintDefinitionsCount = ($blueprintDefinitions).count
+ if ($blueprintDefinitionsCount -gt 0) {
+ $htmlTableId = 'TenantSummary_BlueprintDefinitions'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$blueprintDefinitionsCount Blueprint definitions
+
+
Download CSV
semicolon |
comma
+
+
+
+Blueprint Name
+Blueprint DisplayName
+Blueprint Description
+BlueprintId
+
+
+
+"@)
+ $htmlSUMMARYBlueprintDefinitions = $null
+ $htmlSUMMARYBlueprintDefinitions = foreach ($blueprintDefinition in $blueprintDefinitions | Sort-Object -Property BlueprintName, BlueprintDisplayName) {
+ @"
+
+$($blueprintDefinition.BlueprintName -replace '<', '<' -replace '>', '>')
+$($blueprintDefinition.BlueprintDisplayName -replace '<', '<' -replace '>', '>')
+$($blueprintDefinition.BlueprintDescription -replace '<', '<' -replace '>', '>')
+$($blueprintDefinition.BlueprintId -replace '<', '<' -replace '>', '>')
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintDefinitions)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$blueprintDefinitionsCount Blueprint definitions
+"@)
+ }
+ #endregion SUMMARYBlueprintDefinitions
+
+ #region SUMMARYBlueprintAssignments
+ Write-Host ' processing TenantSummary BlueprintAssignments'
+ $blueprintAssignments = ($blueprintBaseQuery | Where-Object { -not [String]::IsNullOrEmpty($_.BlueprintAssignmentId) })
+ $blueprintAssignmentsCount = ($blueprintAssignments).count
+
+ if ($blueprintAssignmentsCount -gt 0) {
+ $htmlTableId = 'TenantSummary_BlueprintAssignments'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$blueprintAssignmentsCount Blueprint assignments
+
+
Download CSV
semicolon |
comma
+
+
+
+Blueprint Name
+Blueprint DisplayName
+Blueprint Description
+BlueprintId
+Blueprint Version
+Blueprint AssignmentId
+
+
+
+"@)
+ $htmlSUMMARYBlueprintAssignments = $null
+ $htmlSUMMARYBlueprintAssignments = foreach ($blueprintAssignment in $blueprintAssignments | Sort-Object -Property level, BlueprintAssignmentId) {
+ @"
+
+$($blueprintAssignment.BlueprintName -replace '<', '<' -replace '>', '>')
+$($blueprintAssignment.BlueprintDisplayName -replace '<', '<' -replace '>', '>')
+$($blueprintAssignment.BlueprintDescription -replace '<', '<' -replace '>', '>')
+$($blueprintAssignment.BlueprintId -replace '<', '<' -replace '>', '>')
+$($blueprintAssignment.BlueprintAssignmentVersion)
+$($blueprintAssignment.BlueprintAssignmentId -replace '<', '<' -replace '>', '>')
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintAssignments)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$blueprintAssignmentsCount Blueprint assignments
+"@)
+ }
+ #endregion SUMMARYBlueprintAssignments
+
+ #region SUMMARYBlueprintsOrphaned
+ Write-Host ' processing TenantSummary Blueprint definitions orphaned'
+ $blueprintDefinitionsOrphanedArray = @()
+ if ($blueprintDefinitionsCount -gt 0) {
+ if ($blueprintAssignmentsCount -gt 0) {
+ $blueprintDefinitionsOrphanedArray += foreach ($blueprintDefinition in $blueprintDefinitions) {
+ if (($blueprintAssignments.BlueprintId) -notcontains ($blueprintDefinition.BlueprintId)) {
+ $blueprintDefinition
+ }
+ }
+ }
+ else {
+ $blueprintDefinitionsOrphanedArray += foreach ($blueprintDefinition in $blueprintDefinitions) {
+ $blueprintDefinition
+ }
+ }
+ }
+ $blueprintDefinitionsOrphanedCount = ($blueprintDefinitionsOrphanedArray).count
+
+ if ($blueprintDefinitionsOrphanedCount -gt 0) {
+
+ $htmlTableId = 'TenantSummary_BlueprintsOrphaned'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$blueprintDefinitionsOrphanedCount Orphaned Blueprints
+
+
Download CSV
semicolon |
comma
+
+
+
+Blueprint Name
+Blueprint DisplayName
+Blueprint Description
+BlueprintId
+
+
+
+"@)
+ $htmlSUMMARYBlueprintsOrphaned = $null
+ $htmlSUMMARYBlueprintsOrphaned = foreach ($blueprintDefinition in $blueprintDefinitionsOrphanedArray | Sort-Object -Property BlueprintId) {
+ @"
+
+$($blueprintDefinition.BlueprintName -replace '<', '<' -replace '>', '>')
+$($blueprintDefinition.BlueprintDisplayName -replace '<', '<' -replace '>', '>')
+$($blueprintDefinition.BlueprintDescription -replace '<', '<' -replace '>', '>')
+$($blueprintDefinition.BlueprintId -replace '<', '<' -replace '>', '>')
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintsOrphaned)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$blueprintDefinitionsOrphanedCount Orphaned Blueprint definitions
+"@)
+ }
+ #endregion SUMMARYBlueprintsOrphaned
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryBlueprints
+
+ showMemoryUsage
+
+ #region tenantSummaryManagementGroups
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+'@)
+
+ #region SUMMARYMGs
+ $startSUMMARYMGs = Get-Date
+ Write-Host ' processing TenantSummary ManagementGroups'
+
+ $summaryManagementGroups = $optimizedTableForPathQueryMg | Sort-Object -Property Level, mgid, mgParentId
+ $summaryManagementGroupsCount = ($summaryManagementGroups).Count
+ if ($summaryManagementGroupsCount -gt 0) {
+ $tfCount = $summaryManagementGroupsCount
+ $htmlTableId = 'TenantSummary_ManagementGroups'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($summaryManagementGroupsCount) Management Groups
+
+
Download CSV
semicolon |
comma
+
+
+
+Level
+ManagementGroup
+ManagementGroup Id
+Mg children (total)
+Mg children (direct)
+Sub children (total)
+Sub children (direct)
+"@)
+ if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) {
+ [void]$htmlTenantSummary.AppendLine(@'
+MG MDfC Score
+'@)
+ }
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ [void]$htmlTenantSummary.AppendLine(@"
+Cost ($($AzureConsumptionPeriod)d)
+"@)
+ }
+ [void]$htmlTenantSummary.AppendLine(@'
+Path
+
+
+
+'@)
+ $htmlSUMMARYManagementGroups = $null
+ $cnter = 0
+ $htmlSUMMARYManagementGroups = foreach ($summaryManagementGroup in $summaryManagementGroups) {
+
+ $mgPath = $htManagementGroupsMgPath.($summaryManagementGroup.mgId).pathDelimited
+
+ if ($summaryManagementGroup.mgid -eq $mgSubPathTopMg -and ($azAPICallConf['checkContext']).Tenant.Id -ne $ManagementGroupId) {
+ $pathhlper = "$($mgPath)"
+ $arrayTotalCostSummaryMgSummary = 'n/a'
+ $mgAllChildMgsCountTotal = 'n/a'
+ $mgAllChildMgsCountDirect = 'n/a'
+ $mgAllChildSubscriptionsCountTotal = 'n/a'
+ $mgAllChildSubscriptionsCountDirect = 'n/a'
+ $mgSecureScore = 'n/a'
+ }
+ else {
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ if ($allConsumptionDataCount -gt 0) {
+ $arrayTotalCostSummaryMgSummary = @()
+ if ($htManagementGroupsCost.($summaryManagementGroup.mgid)) {
+ foreach ($currency in $htManagementGroupsCost.($summaryManagementGroup.mgid).currencies) {
+ $hlper = $htManagementGroupsCost.($summaryManagementGroup.mgid)
+ $totalCost = $hlper."mgTotalCost_$($currency)"
+ if ([math]::Round($totalCost, 2) -eq 0) {
+ $totalCost = $totalCost.ToString('0.0000')
+ }
+ else {
+ $totalCost = [math]::Round($totalCost, 2).ToString('0.00')
+ }
+ $totalCostGeneratedByResourceTypes = ($hlper."resourceTypesThatGeneratedCost_$($currency)").Count
+ $totalCostGeneratedByResources = $hlper."resourcesThatGeneratedCost_$($currency)"
+ $totalCostGeneratedBySubscriptions = $hlper."subscriptionsThatGeneratedCost_$($currency)"
+ $arrayTotalCostSummaryMgSummary += "$($totalCost) $($currency) generated by $($totalCostGeneratedByResources) Resources ($($totalCostGeneratedByResourceTypes) ResourceTypes) in $($totalCostGeneratedBySubscriptions) Subscriptions"
+ }
+ }
+ else {
+ $arrayTotalCostSummaryMgSummary = 'no consumption data available'
+ }
+ }
+ else {
+ $arrayTotalCostSummaryMgSummary = 'no consumption data available'
+ }
+ }
+ $pathhlper = " $($mgPath)"
+
+ #childrenMgInfo
+ $mgAllChildMgs = [System.Collections.ArrayList]@()
+ foreach ($entry in $htManagementGroupsMgPath.keys) {
+ if (($htManagementGroupsMgPath.($entry).path) -contains $($summaryManagementGroup.mgid)) {
+ $null = $mgAllChildMgs.Add($entry)
+ }
+ }
+ $mgAllChildMgsCountTotal = (($mgAllChildMgs).Count - 1)
+ $mgAllChildMgsCountDirect = $htMgDetails.($summaryManagementGroup.mgid).mgChildrenCount
+
+ $mgAllChildSubscriptions = [System.Collections.ArrayList]@()
+ $mgDirectChildSubscriptions = [System.Collections.ArrayList]@()
+ foreach ($entry in $htSubscriptionsMgPath.keys) {
+ if (($htSubscriptionsMgPath.($entry).path) -contains $($summaryManagementGroup.mgid)) {
+ $null = $mgAllChildSubscriptions.Add($entry)
+ }
+ if (($htSubscriptionsMgPath.($entry).parent) -eq $($summaryManagementGroup.mgid)) {
+ $null = $mgDirectChildSubscriptions.Add($entry)
+ }
+ }
+
+ $mgAllChildSubscriptionsCountTotal = (($mgAllChildSubscriptions).Count)
+ $mgAllChildSubscriptionsCountDirect = (($mgDirectChildSubscriptions).Count)
+
+ if ($htMgASCSecureScore.($summaryManagementGroup.mgId).SecureScore) {
+ if ([string]::IsNullOrEmpty($htMgASCSecureScore.($summaryManagementGroup.mgId).SecureScore) -or [string]::IsNullOrWhiteSpace($htMgASCSecureScore.($summaryManagementGroup.mgId).SecureScore)) {
+ $mgSecureScore = 'n/a'
+ }
+ else {
+ $mgSecureScore = $htMgASCSecureScore.($summaryManagementGroup.mgId).SecureScore
+ }
+ }
+ else {
+ $mgSecureScore = 'n/a'
+ }
+ }
+
+ @"
+
+$($summaryManagementGroup.level)
+$($summaryManagementGroup.mgName -replace '<', '<' -replace '>', '>')
+$($summaryManagementGroup.mgId)
+$($mgAllChildMgsCountTotal)
+$($mgAllChildMgsCountDirect)
+$($mgAllChildSubscriptionsCountTotal)
+$($mgAllChildSubscriptionsCountDirect)
+"@
+ if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) {
+ @"
+$($mgSecureScore)
+"@
+ }
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ @"
+$($arrayTotalCostSummaryMgSummary -join ', ')
+"@
+ }
+ @"
+$($pathhlper)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYManagementGroups)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($summaryManagementGroupsCount) Management Groups
+"@)
+ }
+ $endSUMMARYMGs = Get-Date
+ Write-Host " SUMMARYMGs duration: $((New-TimeSpan -Start $startSUMMARYMGs -End $endSUMMARYMGs).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYMGs -End $endSUMMARYMGs).TotalSeconds) seconds)"
+ #endregion SUMMARYMGs
+
+ #region SUMMARYMGdefault
+ Write-Host ' processing TenantSummary ManagementGroups - default Management Group'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Hierarchy Settings | Default Management Group Id: '$($defaultManagementGroupId) ' docs
+"@)
+ #endregion SUMMARYMGdefault
+
+ #region SUMMARYMGRequireAuthorizationForGroupCreation
+ Write-Host ' processing TenantSummary ManagementGroups - requireAuthorizationForGroupCreation Management Group'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Hierarchy Settings | Require authorization for Management Group creation: '$($requireAuthorizationForGroupCreation) ' docs
+"@)
+ #endregion SUMMARYMGRequireAuthorizationForGroupCreation
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryManagementGroups
+
+ showMemoryUsage
+
+ #region tenantSummarySubscriptionsResourceDefenderPSRule
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+'@)
+
+ #region SUMMARYSubs
+ $startSUMMARYSubs = Get-Date
+ Write-Host ' processing TenantSummary Subscriptions'
+ $summarySubscriptions = $optimizedTableForPathQueryMgAndSub | Sort-Object -Property Subscription
+ $summarySubscriptionsCount = ($summarySubscriptions).Count
+
+ $arrayPIMEligibleGroupedBySubscription = $arrayPIMEligible.where({ $_.ScopeType -eq 'Sub' }) | Group-Object -Property ScopeId
+
+ if ($summarySubscriptionsCount -gt 0) {
+
+ $advisorScoreCategories = $arrayAdvisorScores.category | Sort-Object -Unique
+ $htAdvisorScoresSubscriptions = @{}
+ if ($advisorScoreCategories.Count -gt 0) {
+ $arrayAdvisorScoresGroupedBySubscriptionId = $arrayAdvisorScores | Group-Object -Property subscriptionId
+ foreach ($subEntry in $arrayAdvisorScoresGroupedBySubscriptionId) {
+ $htAdvisorScoresSubscriptions.($subEntry.Name) = @{}
+ foreach ($possibleCategory in $advisorScoreCategories) {
+ if ($subEntry.Group.category -eq $possibleCategory) {
+ $htAdvisorScoresSubscriptions.($subEntry.Name).($possibleCategory) = $subEntry.Group.where({ $_.category -eq $possibleCategory }).score
+ }
+ }
+ }
+ }
+
+ $tfCount = $summarySubscriptionsCount
+ $htmlTableId = 'TenantSummary_subs'
+ $abbr = "
"
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($summarySubscriptionsCount) Subscriptions (state: enabled)
+
+
Supported Microsoft Azure offers docs
+
Understand Microsoft Defender for Cloud Secure Score Video ,
Blog ,
docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+QuotaId
+Role assignment limit
+Tags
+Owner (at Scope) direct
+Owner (at Scope) indirect$($abbr)
+Owner (PIM eligible at scope)
+User Access Administrator (at Scope) direct
+User Access Administrator (at Scope) indirect$($abbr)
+User Access Administrator (PIM eligible at scope)
+MDfC Score
+MDfC 'Email notifications' state
+MDfC 'Email notifications' severity
+MDfC 'Email notifications' roles
+MDfC 'Email notifications' emails
+"@)
+
+ foreach ($possibleCategory in $advisorScoreCategories) {
+ if ($possibleCategory -eq 'Advisor') {
+ [void]$htmlTenantSummary.AppendLine(@"
+ $possibleCategory score
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+ Advisor $possibleCategory score
+"@)
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ [void]$htmlTenantSummary.AppendLine(@"
+Cost ($($AzureConsumptionPeriod)d)
+Currency
+"@)
+ }
+ [void]$htmlTenantSummary.AppendLine(@'
+Management Group Path
+
+
+
+'@)
+
+ if (-not $ManagementGroupsOnly) {
+ if (-not $NoCsvExport) {
+ Write-Host " Exporting MDfC Email Notifications CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_MDfCEmailNotifications.csv'"
+ $htDefenderEmailContacts.values | Sort-Object -Property subscriptionName | Select-Object -Property subscriptionId, subscriptionName, alertNotificationsState, alertNotificationsminimalSeverity, roles, emails | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_MDfCEmailNotifications.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+ }
+
+ $subscriptionDetails4CSVExport = [System.Collections.ArrayList]@()
+ $htmlSUMMARYSubs = $null
+ $htmlSUMMARYSubs = foreach ($summarySubscription in $summarySubscriptions) {
+ $subPath = $htSubscriptionsMgPath.($summarySubscription.subscriptionId).ParentNameChainDelimited
+ $subscriptionTagsArray = [System.Collections.ArrayList]@()
+ foreach ($tag in ($htSubscriptionTags).($summarySubscription.subscriptionId).keys) {
+ $null = $subscriptionTagsArray.Add("'$($tag)':'$(($htSubscriptionTags).$($summarySubscription.subscriptionId).$tag)'")
+ }
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ if ($htAzureConsumptionSubscriptions.($summarySubscription.subscriptionId)) {
+ if ([math]::Round($htAzureConsumptionSubscriptions.($summarySubscription.subscriptionId).TotalCost, 2) -eq 0) {
+ $totalCost = $htAzureConsumptionSubscriptions.($summarySubscription.subscriptionId).TotalCost.ToString('0.0000')
+ }
+ else {
+ $totalCost = (([math]::Round($htAzureConsumptionSubscriptions.($summarySubscription.subscriptionId).TotalCost, 2))).ToString('0.00')
+ }
+ $currency = $htAzureConsumptionSubscriptions.($summarySubscription.subscriptionId).Currency
+ }
+ else {
+ $totalCost = '0'
+ $currency = 'n/a'
+ }
+ }
+ else {
+ $totalCost = 'n/a'
+ $currency = 'n/a'
+ }
+
+ if ($htDefenderEmailContacts.($summarySubscription.subscriptionId)) {
+ $hlpDefenderEmailContacts = $htDefenderEmailContacts.($summarySubscription.subscriptionId)
+ $MDfCEmailNotificationsState = $hlpDefenderEmailContacts.alertNotificationsState
+ $MDfCEmailNotificationsSeverity = $hlpDefenderEmailContacts.alertNotificationsminimalSeverity
+ $MDfCEmailNotificationsRoles = $hlpDefenderEmailContacts.roles
+ $MDfCEmailNotificationsEmails = $hlpDefenderEmailContacts.emails
+ }
+ else {
+ $MDfCEmailNotificationsState = ''
+ $MDfCEmailNotificationsSeverity = ''
+ $MDfCEmailNotificationsRoles = ''
+ $MDfCEmailNotificationsEmails = ''
+ }
+
+ #rbac assignments owner and userAccountAdministrator
+ $rbacAtScopeForThisSubscription = ($rbacAllGroupedBySubscription.where( { $_.name -eq $summarySubscription.subscriptionId } )).group
+
+ $rbacOwnersAtScopeForThisSubscription = ($rbacAtScopeForThisSubscription.where({ $_.Scope -eq 'thisScope Sub' -and $_.RoleId -eq '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' }))
+ $rbacOwnersAtScopeForThisSubscriptionDirectCount = ($rbacOwnersAtScopeForThisSubscription.where( { $_.AssignmentType -eq 'direct' } )).Count
+ $rbacOwnersAtScopeForThisSubscriptionInDirectCount = $rbacOwnersAtScopeForThisSubscription.Count - $rbacOwnersAtScopeForThisSubscriptionDirectCount
+
+ $rbacUAAsAtScopeForThisSubscription = ($rbacAtScopeForThisSubscription.where({ $_.Scope -eq 'thisScope Sub' -and $_.RoleId -eq '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' }))
+ $rbacUAAsAtScopeForThisSubscriptionDirectCount = ($rbacUAAsAtScopeForThisSubscription.where( { $_.AssignmentType -eq 'direct' } )).Count
+ $rbacUAAsAtScopeForThisSubscriptionInDirectCount = $rbacUAAsAtScopeForThisSubscription.Count - $rbacUAAsAtScopeForThisSubscriptionDirectCount
+
+ #pim eligibility owner and userAccountAdministrator
+ $pimEligibleOwnersAtScopeForThisSubscriptionCount = ''
+ $pimEligibleUAAsAtScopeForThisSubscriptionCount = ''
+ if (-not $NoPIMEligibility) {
+ $pimEligibleAtScopeForThisSubscription = ($arrayPIMEligibleGroupedBySubscription.where( { $_.name -eq $summarySubscription.subscriptionId } )).group
+ $pimEligibleOwnersAtScopeForThisSubscriptionCount = ($pimEligibleAtScopeForThisSubscription.where( { $_.RoleIdGuid -eq '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' } )).Count
+ $pimEligibleUAAsAtScopeForThisSubscriptionCount = ($pimEligibleAtScopeForThisSubscription.where( { $_.RoleIdGuid -eq '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' } )).Count
+ }
+
+ $htColl = [ordered]@{}
+ @"
+
+$($summarySubscription.subscription -replace '<', '<' -replace '>', '>')
+$($summarySubscription.subscriptionId)
+$($summarySubscription.SubscriptionQuotaId)
+$($htSubscriptionsRoleAssignmentLimit.($summarySubscription.subscriptionId))
+$(($subscriptionTagsArray | Sort-Object) -join "$CsvDelimiterOpposite ")
+$($rbacOwnersAtScopeForThisSubscriptionDirectCount)
+$($rbacOwnersAtScopeForThisSubscriptionInDirectCount)
+$($pimEligibleOwnersAtScopeForThisSubscriptionCount)
+$($rbacUAAsAtScopeForThisSubscriptionDirectCount)
+$($rbacUAAsAtScopeForThisSubscriptionInDirectCount)
+$($pimEligibleUAAsAtScopeForThisSubscriptionCount)
+$($summarySubscription.SubscriptionASCSecureScore)
+$($MDfCEmailNotificationsState)
+$($MDfCEmailNotificationsSeverity)
+$($MDfCEmailNotificationsRoles)
+$($MDfCEmailNotificationsEmails)
+"@
+
+ $htColl.Subscription = $summarySubscription.subscription
+ $htColl.SubscriptionId = $summarySubscription.subscriptionId
+ $htColl.QuotaId = $summarySubscription.SubscriptionQuotaId
+ $htColl.ManagementGroupPath = $subPath
+ $htColl.RoleAssignmentLimit = $htSubscriptionsRoleAssignmentLimit.($summarySubscription.subscriptionId)
+ $htColl.Tags = ($subscriptionTagsArray | Sort-Object) -join "$CsvDelimiterOpposite "
+ $htColl.'Owner(atScope)Direct' = $rbacOwnersAtScopeForThisSubscriptionDirectCount
+ $htColl.'Owner(atScope)Indirect' = $rbacOwnersAtScopeForThisSubscriptionInDirectCount
+ $htColl.'Owner(PIMEligibleAtScope)' = $pimEligibleOwnersAtScopeForThisSubscriptionCount
+ $htColl.'UserAccessAdministrator(atScope)Direct' = $rbacUAAsAtScopeForThisSubscriptionDirectCount
+ $htColl.'UserAccessAdministrator(atScope)Indirect' = $rbacUAAsAtScopeForThisSubscriptionInDirectCount
+ $htColl.'UserAccessAdministrator(PIMEligibleAtScope)' = $pimEligibleUAAsAtScopeForThisSubscriptionCount
+ $htColl.MDfCScore = $summarySubscription.SubscriptionASCSecureScore
+ $htColl.MDfCEmailNotificationsState = $MDfCEmailNotificationsState
+ $htColl.MDfCEmailNotificationsSeverity = $MDfCEmailNotificationsSeverity
+ $htColl.MDfCEmailNotificationsRoles = $MDfCEmailNotificationsRoles
+ $htColl.MDfCEmailNotificationsEmails = $MDfCEmailNotificationsEmails
+
+ foreach ($possibleCategory in $advisorScoreCategories) {
+ if ($htAdvisorScoresSubscriptions.($summarySubscription.subscriptionId).($possibleCategory)) {
+ @"
+ $([math]::Round(($htAdvisorScoresSubscriptions.($summarySubscription.subscriptionId).($possibleCategory)), 2))
+"@
+ if ($possibleCategory -eq 'Advisor') {
+ $htColl.("$($possibleCategory)Score") = $htAdvisorScoresSubscriptions.($summarySubscription.subscriptionId).($possibleCategory)
+ }
+ else {
+ $htColl.("Advisor$($possibleCategory)Score") = $htAdvisorScoresSubscriptions.($summarySubscription.subscriptionId).($possibleCategory)
+ }
+ }
+ else {
+ @'
+ n/a
+'@
+ $htColl.($possibleCategory) = 'n/a'
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ @"
+$totalCost
+$currency
+"@
+ $htColl."Cost($($AzureConsumptionPeriod)d)" = $totalCost
+ $htColl.Currency = $currency
+ }
+ @"
+ $subPath
+
+"@
+ if (-not $NoCsvExport) {
+ $null = $subscriptionDetails4CSVExport.Add($htColl)
+ }
+ }
+
+ if (-not $ManagementGroupsOnly) {
+ if (-not $NoCsvExport) {
+ Write-Host " Exporting SubscriptionDetails CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_SubscriptionDetails.csv'"
+ $subscriptionDetails4CSVExport | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_SubscriptionDetails.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+ }
+
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubs)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($summarySubscriptionsCount) Subscriptions
+"@)
+ }
+
+ $endSUMMARYSubs = Get-Date
+ Write-Host " SUMMARYSubs duration: $((New-TimeSpan -Start $startSUMMARYSubs -End $endSUMMARYSubs).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYSubs -End $endSUMMARYSubs).TotalSeconds) seconds)"
+ #endregion SUMMARYSubs
+
+ #region SUMMARYOutOfScopeSubscriptions
+ Write-Host ' processing TenantSummary Subscriptions (out-of-scope)'
+ $outOfScopeSubscriptionsCount = ($outOfScopeSubscriptions).Count
+ if ($outOfScopeSubscriptionsCount -gt 0) {
+ $tfCount = $outOfScopeSubscriptionsCount
+ $htmlTableId = 'TenantSummary_outOfScopeSubscriptions'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$outOfScopeSubscriptionsCount Subscriptions out-of-scope
+
+
Download CSV
semicolon |
comma
+
+
+
+Subscription Name
+SubscriptionId
+out-of-scope reason
+Management Group
+
+
+
+"@)
+ $htmlSUMMARYOutOfScopeSubscriptions = $null
+ $htmlSUMMARYOutOfScopeSubscriptions = foreach ($outOfScopeSubscription in $outOfScopeSubscriptions) {
+ @"
+
+$($outOfScopeSubscription.SubscriptionName)
+$($outOfScopeSubscription.SubscriptionId)
+$($outOfScopeSubscription.outOfScopeReason)
+ $($outOfScopeSubscription.ManagementGroupName -replace '<', '<' -replace '>', '>') ($($outOfScopeSubscription.ManagementGroupId))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOutOfScopeSubscriptions)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$outOfScopeSubscriptionsCount Subscriptions out-of-scope
+"@)
+ }
+ #endregion SUMMARYOutOfScopeSubscriptions
+
+ #region SUMMARYTagNameUsage
+ Write-Host ' processing TenantSummary TagsUsage'
+ $tagsUsageCount = ($arrayTagList).Count
+ if ($tagsUsageCount -gt 0) {
+ $tagNamesUniqueCount = ($arrayTagList | Sort-Object -Property TagName -Unique).Count
+ $tagNamesUsedInScopes = ($arrayTagList.where( { $_.Scope -ne 'AllScopes' }) | Sort-Object -Property Scope -Unique).scope -join "$($CsvDelimiterOpposite) "
+ $tfCount = $tagsUsageCount
+ $htmlTableId = 'TenantSummary_tagsUsage'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Tag Name Usage ($tagNamesUniqueCount unique Tag Names applied at $($tagNamesUsedInScopes))
+
+
Resource naming and tagging decision guide docs
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+TagName
+Count
+
+
+
+"@)
+ $htmlSUMMARYtagsUsage = $null
+ $htmlSUMMARYtagsUsage = foreach ($tagEntry in $arrayTagList | Sort-Object -Property Scope, TagName -CaseSensitive) {
+ @"
+
+$($tagEntry.Scope)
+$($tagEntry.TagName)
+$($tagEntry.TagCount)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtagsUsage)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Tag Name Usage ($tagsUsageCount Tags) docs
+"@)
+ }
+ #endregion SUMMARYTagNameUsage
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #region SUMMARYResources
+ $startSUMMARYResources = Get-Date
+ Write-Host ' processing TenantSummary Subscriptions Resources'
+ if (($resourcesAll).count -gt 0) {
+ $resourcesAllGroupedByType = $resourcesAll | Select-Object -Property type, count_ | Group-Object type
+ $resourcesTotal = ($resourcesAll.count_ | Measure-Object -Sum).Sum
+ $resourcesResourceTypeCount = ($resourcesAll.type | Sort-Object -Unique).Count
+
+ if ($resourcesResourceTypeCount -gt 0) {
+ $tfCount = ($resourcesAllGroupedByType | Measure-Object).Count
+ $htmlTableId = 'TenantSummary_resources'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resources ($resourcesResourceTypeCount ResourceTypes) ($resourcesTotal Resources) ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Resource Count
+
+
+
+"@)
+ $htmlSUMMARYResources = $null
+ $htmlSUMMARYResources = foreach ($resourceAllSummarized in $resourcesAllGroupedByType) {
+ $type = $resourceAllSummarized.Name
+ $script:htDailySummary."ResourceType_$($resourceAllSummarized.Name)" = ($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum
+ @"
+
+$($type)
+$(($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum)
+
+"@
+
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResources)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resources ($resourcesResourceTypeCount ResourceTypes)
+"@)
+ }
+
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
Resources (0 ResourceTypes)
+'@)
+ }
+ $endSUMMARYResources = Get-Date
+ Write-Host " SUMMARY Resources processing duration: $((New-TimeSpan -Start $startSUMMARYResources -End $endSUMMARYResources).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYResources -End $endSUMMARYResources).TotalSeconds) seconds)"
+ #endregion SUMMARYResources
+
+ #region SUMMARYResourcesByLocation
+ $startSUMMARYResources = Get-Date
+ Write-Host ' processing TenantSummary Subscriptions Resources by Location'
+ if (($resourcesAll | Measure-Object).count -gt 0) {
+ $resourcesAllGroupedByTypeLocation = $resourcesAll | Select-Object -Property type, location, count_ | Group-Object type, location
+ $resourcesTotal = ($resourcesAll.count_ | Measure-Object -Sum).Sum
+ $resourcesResourceTypeCount = ($resourcesAll.type | Sort-Object -Unique).Count
+ $resourcesLocationCount = ($resourcesAll.location | Sort-Object -Unique).Count
+
+ if ($resourcesResourceTypeCount -gt 0) {
+ $tfCount = ($resourcesAllGroupedByTypeLocation | Measure-Object).Count
+ $htmlTableId = 'TenantSummary_resourcesByLocation'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resources byLocation ($resourcesResourceTypeCount ResourceTypes) ($resourcesTotal Resources) in $resourcesLocationCount Locations ($scopeNamingSummary)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Location
+Resource Count
+
+
+
+"@)
+ $htmlSUMMARYResources = $null
+ $htmlSUMMARYResources = foreach ($resourceAllSummarized in $resourcesAllGroupedByTypeLocation) {
+ $typeLocation = $resourceAllSummarized.Name.Split(', ')
+ $type = $typeLocation[0]
+ $location = $typeLocation[1]
+ @"
+
+$($type)
+$($location)
+$(($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum)
+
+"@
+
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResources)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resources ($resourcesResourceTypeCount ResourceTypes)
+"@)
+ }
+
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
Resources (0 ResourceTypes)
+'@)
+ }
+ $endSUMMARYResources = Get-Date
+ Write-Host " SUMMARY Resources ByLocation processing duration: $((New-TimeSpan -Start $startSUMMARYResources -End $endSUMMARYResources).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYResources -End $endSUMMARYResources).TotalSeconds) seconds)"
+ #endregion SUMMARYResourcesByLocation
+
+ #region SUMMARYResourceFluctuation
+ $startSUMMARYResourceFluctuation = Get-Date
+ Write-Host ' processing TenantSummary Resource fluctuation'
+ if (($arrayResourceFluctuationFinal).count -gt 0) {
+ $resourceTypesCount = ($arrayResourceFluctuationFinal | Group-Object -Property ResourceType | Measure-Object).Count
+ $addedCount = ($arrayResourceFluctuationFinal.where({ $_.Event -eq 'Added' }).'Resource count' | Measure-Object -Sum).Sum
+ $removedCount = ($arrayResourceFluctuationFinal.where({ $_.Event -eq 'Removed' }).'Resource count' | Measure-Object -Sum).Sum
+
+ $tfCount = ($arrayResourceFluctuationFinal).count
+ $htmlTableId = 'TenantSummary_resourceFluctuation'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resource fluctuation - $resourceTypesCount Resource types (Resources: $addedCount added, $removedCount removed)
+
+
+
Download CSV
semicolon |
comma
+
+
+
+Event
+ResourceType
+Resource count
+Subscription count
+
+
+
+"@)
+ $htmlSUMMARYResourceFluctuation = $null
+ $htmlSUMMARYResourceFluctuation = foreach ($entry in $arrayResourceFluctuationFinal | Sort-Object -Property ResourceType, Event) {
+ @"
+
+$($entry.Event)
+$($entry.ResourceType)
+$($entry.'Resource count')
+$($entry.'Subscription count')
+
+"@
+
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResourceFluctuation)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Resource fluctuation since last run
+'@)
+ }
+ $endSUMMARYResourceFluctuation = Get-Date
+ Write-Host " SUMMARY Resource fluctuation processing duration: $((New-TimeSpan -Start $startSUMMARYResourceFluctuation -End $endSUMMARYResourceFluctuation).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYResourceFluctuation -End $endSUMMARYResourceFluctuation).TotalSeconds) seconds)"
+ #endregion SUMMARYResourceFluctuation
+
+ #region SUMMARYCAFResourceNamingALL
+ $startSUMMARYCAFResourceNamingALL = Get-Date
+ Write-Host ' processing TenantSummary CAFResourceNamingALL'
+ $script:resourcesIdsAllCAFNamingRelevant = $resourcesIdsAll.where({ $_.cafResourceNamingResult -ne 'n/a' })
+ $resourcesIdsAllCAFNamingRelevantGroupedByType = $resourcesIdsAllCAFNamingRelevant | Group-Object -Property type
+ $resourcesIdsAllCAFNamingRelevantGroupedByTypeCount = ($resourcesIdsAllCAFNamingRelevantGroupedByType | Measure-Object).Count
+
+ if ($resourcesIdsAllCAFNamingRelevantGroupedByTypeCount -gt 0) {
+
+ $tfCount = $resourcesIdsAllCAFNamingRelevantGroupedByTypeCount
+ $htmlTableId = 'TenantSummary_CAFResourceNamingALL'
+ [void]$htmlTenantSummary.AppendLine(@"
+
CAF Naming Recommendation Compliance
+
+
+
CAF - Recommended abbreviations for Azure resource types docs
+
Resource details can be found in the CSV output *_ResourcesAll.csv
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Recommendation
+ResourceFriendlyName
+passed
+failed
+passed percentage
+
+
+
+"@)
+
+ $htmlSUMMARYCAFResourceNamingALL = $null
+ $htmlSUMMARYCAFResourceNamingALL = foreach ($entry in $resourcesIdsAllCAFNamingRelevantGroupedByType) {
+
+ $resourceTypeGroupedByCAFResourceNamingResult = $entry.Group | Group-Object -Property cafResourceNamingResult, cafResourceNaming
+ if ($entry.Group.cafResourceNaming.Count -gt 1) {
+ $namingConvention = ($entry.Group.cafResourceNaming)[0]
+ $namingConventionFriendlyName = ($entry.Group.cafResourceNamingFriendlyName)[0]
+ }
+ else {
+ $namingConvention = $entry.Group.cafResourceNaming
+ $namingConventionFriendlyName = $entry.Group.cafResourceNamingFriendlyName
+ }
+
+ $passed = 0
+ $failed = 0
+ foreach ($result in $resourceTypeGroupedByCAFResourceNamingResult) {
+ $resultNameSplitted = $result.Name -split ', '
+ if ($resultNameSplitted[0] -eq 'passed') {
+ $passed = $result.Count
+ }
+
+ if ($resultNameSplitted[0] -eq 'failed') {
+ $failed = $result.Count
+ }
+ }
+
+ if ($passed -gt 0) {
+ $percentage = [math]::Round(($passed / ($passed + $failed) * 100), 2)
+ }
+ else {
+ $percentage = 0
+ }
+
+ @"
+
+$($entry.Name)
+$($namingConvention)
+$($namingConventionFriendlyName)
+$($passed)
+$($failed)
+$($percentage)%
+
+"@
+
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCAFResourceNamingALL)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No CAF Naming Recommendation Compliance data
+'@)
+ }
+ $endSUMMARYCAFResourceNamingALL = Get-Date
+ Write-Host " SUMMARY CAFResourceNamingALL processing duration: $((New-TimeSpan -Start $startSUMMARYCAFResourceNamingALL -End $endSUMMARYCAFResourceNamingALL).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYCAFResourceNamingALL -End $endSUMMARYCAFResourceNamingALL).TotalSeconds) seconds)"
+ #endregion SUMMARYCAFResourceNamingALL
+ }
+
+ #region SUMMARYOrphanedResources
+ $startSUMMARYOrphanedResources = Get-Date
+ Write-Host ' processing TenantSummary Orphaned/unused Resources'
+ if ($arrayOrphanedResources.count -gt 0) {
+ $script:arrayOrphanedResourcesSlim = $arrayOrphanedResources | Sort-Object -Property type
+
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ $orphanedIncludingCost = $true
+ $hintTableTH = " ($($AzureConsumptionPeriod) days)"
+
+ $arrayOrphanedResourcesGroupedByType = $arrayOrphanedResourcesSlim | Group-Object type, currency
+ $orphanedResourceTypesCount = ($arrayOrphanedResourcesGroupedByType | Measure-Object).Count
+ $orphanedResourceTypesCountUnique = ($arrayOrphanedResourcesSlim.type | Sort-Object -Unique).Count
+ }
+ else {
+ $orphanedIncludingCost = $false
+ $hintTableTH = ''
+
+ $arrayOrphanedResourcesGroupedByType = $arrayOrphanedResourcesSlim | Group-Object type
+ $orphanedResourceTypesCount = ($arrayOrphanedResourcesGroupedByType | Measure-Object).Count
+ $orphanedResourceTypesCountUnique = ($arrayOrphanedResourcesSlim.type | Sort-Object -Unique).Count
+ }
+
+ $tfCount = $orphanedResourceTypesCount
+ $htmlTableId = 'TenantSummary_orphanedResources'
+ [void]$htmlTenantSummary.AppendLine(@"
+
= Cost optimization & cleanup - $($arrayOrphanedResources.count) Resources, $orphanedResourceTypesCountUnique Resource Types
+
+
+
'Azure Orphan Resources' ARG queries and workbooks GitHub
+
Resource details can be found in the CSV output *_ResourcesCostOptimizationAndCleanup.csv
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Resource count
+Subscriptions count
+Intent
+Cost$($hintTableTH)
+Currency
+
+
+
+"@)
+
+ $htmlSUMMARYOrphanedResources = $null
+ $htmlSUMMARYOrphanedResources = foreach ($orphanedResourceType in $arrayOrphanedResourcesGroupedByType | Sort-Object -Property Name) {
+ $script:htDailySummary."OrpanedResourceType_$($orphanedResourceType.Name)" = ($orphanedResourceType.count)
+ if ($orphanedIncludingCost) {
+ if (($orphanedResourceType.Group[0].Intent) -like 'cost savings*') {
+ $orphCost = ($orphanedResourceType.Group.Cost | Measure-Object -Sum).Sum
+ if ($orphCost -eq 0) {
+ $orphCost = ''
+ }
+ $orphCurrency = $orphanedResourceType.Group[0].Currency
+ $script:htDailySummary."OrpanedResourceType_$($orphanedResourceType.Name)_Costs" = $orphCost
+ $script:htDailySummary."OrpanedResourceType_$($orphanedResourceType.Name)_Costs_ConsumptionPeriodInDays" = $AzureConsumptionPeriod
+ }
+ else {
+ $orphCost = ''
+ $orphCurrency = ''
+ }
+
+ }
+ else {
+ if (($orphanedResourceType.Group.Intent | Get-Unique) -like 'cost savings*') {
+ $orphCost = "use parameter -DoAzureConsumption to show potential savings "
+ $orphCurrency = ''
+ }
+ else {
+ $orphCost = ''
+ $orphCurrency = ''
+ }
+ }
+
+ @"
+
+$(($orphanedResourceType.Name -split ',')[0])
+$($orphanedResourceType.count)
+$(($orphanedResourceType.Group.SubscriptionId | Sort-Object -Unique).Count)
+$($orphanedResourceType.Group[0].Intent)
+$($orphCost)
+$($orphCurrency)
+
+"@
+
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedResources)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No cost optimization & cleanup
+'@)
+ }
+ $endSUMMARYOrphanedResources = Get-Date
+ Write-Host " SUMMARY Orphaned/unused Resources processing duration: $((New-TimeSpan -Start $startSUMMARYOrphanedResources -End $endSUMMARYOrphanedResources).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYOrphanedResources -End $endSUMMARYOrphanedResources).TotalSeconds) seconds)"
+ #endregion SUMMARYOrphanedResources
+
+ #region SUMMARYSubResourceProviders
+ if ($azAPICallConf['htParameters'].NoResourceProvidersAtAll -eq $false) {
+ $startSUMMARYSubResourceProviders = Get-Date
+ Write-Host ' processing TenantSummary Subscriptions Resource Providers'
+ $resourceProvidersAllCount = (($htResourceProvidersAll).Keys | Measure-Object).count
+ if ($resourceProvidersAllCount -gt 0) {
+ $grped = (($htResourceProvidersAll).values.Providers) | Sort-Object -Property namespace, registrationState | Group-Object namespace
+ $htResProvSummary = @{}
+ foreach ($grp in $grped) {
+ $htResProvSummary.($grp.name) = @{}
+ $regstates = ($grp.group | Sort-Object -Property registrationState -Unique).registrationstate
+ foreach ($regstate in $regstates) {
+ $htResProvSummary.($grp.name).$regstate = (($grp.group).where( { $_.registrationstate -eq $regstate }) | Measure-Object).count
+ }
+ }
+ $providerSummary = [System.Collections.ArrayList]@()
+ foreach ($provider in $htResProvSummary.keys) {
+ $hlperProvider = $htResProvSummary.$provider
+ if ($hlperProvider.registered) {
+ $registered = $hlperProvider.registered
+ }
+ else {
+ $registered = '0'
+ }
+
+ if ($hlperProvider.registering) {
+ $registering = $hlperProvider.registering
+ }
+ else {
+ $registering = '0'
+ }
+
+ if ($hlperProvider.notregistered) {
+ $notregistered = $hlperProvider.notregistered
+ }
+ else {
+ $notregistered = '0'
+ }
+
+ if ($hlperProvider.unregistering) {
+ $unregistering = $hlperProvider.unregistering
+ }
+ else {
+ $unregistering = '0'
+ }
+
+ $null = $providerSummary.Add([PSCustomObject]@{
+ Provider = $provider
+ Registered = $registered
+ NotRegistered = $notregistered
+ Registering = $registering
+ Unregistering = $unregistering
+ })
+ }
+
+ $uniqueNamespaces = (($htResourceProvidersAll).values.Providers) | Sort-Object -Property namespace -Unique
+ $uniqueNamespacesCount = ($uniqueNamespaces | Measure-Object).count
+ $uniqueNamespaceRegistrationState = (($htResourceProvidersAll).values.Providers) | Sort-Object -Property namespace, registrationState -Unique
+ $providersRegistered = ($uniqueNamespaceRegistrationState.where( { $_.registrationState -eq 'registered' -or $_.registrationState -eq 'registering' }) | Sort-Object namespace -Unique).namespace
+ $providersRegisteredCount = ($providersRegistered | Measure-Object).count
+
+ $providersNotRegisteredUniqueCount = 0
+ foreach ($uniqueNamespace in $uniqueNamespaces) {
+ if ($providersRegistered -notcontains ($uniqueNamespace.namespace)) {
+ $providersNotRegisteredUniqueCount++
+ }
+ }
+ $tfCount = $uniqueNamespacesCount
+ $htmlTableId = 'TenantSummary_SubResourceProviders'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resource Providers Total: $uniqueNamespacesCount Registered/Registering: $providersRegisteredCount NotRegistered/Unregistering: $providersNotRegisteredUniqueCount
+
+
Download CSV
semicolon |
comma
+
+
+
+Provider
+Registered
+Registering
+NotRegistered
+Unregistering
+
+
+
+"@)
+ $htmlSUMMARYSubResourceProviders = $null
+ $htmlSUMMARYSubResourceProviders = foreach ($provider in ($providerSummary | Sort-Object -Property Provider)) {
+ @"
+
+$($provider.Provider)
+$($provider.Registered)
+$($provider.Registering)
+$($provider.NotRegistered)
+$($provider.Unregistering)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubResourceProviders)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$resourceProvidersAllCount Resource Providers
+"@)
+ }
+ $endSUMMARYSubResourceProviders = Get-Date
+ Write-Host " TenantSummary Subscriptions Resource Providers duration: $((New-TimeSpan -Start $startSUMMARYSubResourceProviders -End $endSUMMARYSubResourceProviders).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYSubResourceProviders -End $endSUMMARYSubResourceProviders).TotalSeconds) seconds)"
+ }
+ #endregion SUMMARYSubResourceProviders
+
+ #region SUMMARYSubResourceProvidersDetailed
+ if ($azAPICallConf['htParameters'].NoResourceProvidersAtAll -eq $false) {
+ if ($azAPICallConf['htParameters'].NoResourceProvidersDetailed -eq $false) {
+
+ Write-Host ' processing TenantSummary Subscriptions Resource Providers detailed'
+ $startsumRPDetailed = Get-Date
+ $resourceProvidersAllCount = (($htResourceProvidersAll).Keys).count
+ if ($resourceProvidersAllCount -gt 0) {
+ $tfCount = ($htResourceProvidersAll).values.Providers.Count
+ if ($tfCount -lt $HtmlTableRowsLimit) {
+ $htmlTableId = 'TenantSummary_SubResourceProvidersDetailed'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resource Providers Detailed
+
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Subscription MG path
+ Provider
+State
+
+
+
+"@)
+
+ }
+ else {
+ Write-Host " !Skipping TenantSummary ResourceProvidersDetailed HTML processing as $tfCount lines is exceeding the critical rows limit of $HtmlTableRowsLimit" -ForegroundColor Yellow
+ }
+ $cnter = 0
+ $startResProvDetailed = Get-Date
+ $htmlSUMMARYSubResourceProvidersDetailed = $null
+
+ $arrayResourceProvidersDetailedForCSVExport = [System.Collections.ArrayList]@()
+ $htmlSUMMARYSubResourceProvidersDetailed = foreach ($subscriptionResProv in (($htResourceProvidersAll).Keys | Sort-Object)) {
+ $subscriptionResProvDetails = $htSubscriptionsMgPath.($subscriptionResProv)
+ foreach ($provider in ($htResourceProvidersAll).($subscriptionResProv).Providers | Sort-Object @{Expression = { $_.namespace } }) {
+ $cnter++
+ if ($cnter % 1000 -eq 0) {
+ $etappeResProvDetailed = Get-Date
+ Write-Host " $cnter ResProv processed; $((New-TimeSpan -Start $startResProvDetailed -End $etappeResProvDetailed).TotalSeconds) seconds"
+ }
+
+ #array for exportCSV
+ if (-not $NoCsvExport) {
+ $null = $arrayResourceProvidersDetailedForCSVExport.Add([PSCustomObject]@{
+ Subscription = $subscriptionResProvDetails.DisplayName
+ SubscriptionId = $subscriptionResProv
+ SubscriptionMGpath = $subscriptionResProvDetails.pathDelimited
+ Provider = $provider.namespace
+ State = $provider.registrationState
+ })
+ }
+
+ @"
+
+$($subscriptionResProvDetails.DisplayName)
+$($subscriptionResProv)
+$($subscriptionResProvDetails.pathDelimited)
+$($provider.namespace)
+$($provider.registrationState)
+
+"@
+ }
+ }
+
+ #region exportCSV
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_ResourceProviders"
+ Write-Host " Exporting ResourceProviders CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ $arrayResourceProvidersDetailedForCSVExport | Export-Csv -Encoding utf8 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -NoTypeInformation
+ $arrayResourceProvidersDetailedForCSVExport = $null
+ }
+ #endregion exportCSV
+
+ if ($tfCount -lt $HtmlTableRowsLimit) {
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubResourceProvidersDetailed)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resource Providers Detailed
+
+
Output of $tfCount lines would exceed the html rows limit of $HtmlTableRowsLimit (html file potentially would become unresponsive). Work with the CSV file $($csvFilename).csv | Note: the CSV file will only exist if you did NOT use parameter -NoCsvExport
+
You can adjust the html row limit by using parameter -HtmlTableRowsLimit
+
Check the parameters documentation Azure Governance Visualizer docs
+
+"@)
+ }
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$resourceProvidersAllCount Resource Providers
+"@)
+ }
+ $endsumRPDetailed = Get-Date
+ Write-Host " RP detailed processing duration: $((New-TimeSpan -Start $startsumRPDetailed -End $endsumRPDetailed).TotalMinutes) minutes ($((New-TimeSpan -Start $startsumRPDetailed -End $endsumRPDetailed).TotalSeconds) seconds)"
+ }
+ }
+ #endregion SUMMARYSubResourceProvidersDetailed
+
+ #region SUMMARYSubFeatures
+ Write-Host ' processing TenantSummary Subscriptions Features'
+ $startSubFeatures = Get-Date
+ $subFeaturesAllCount = $arrayFeaturesAll.count
+ if ($subFeaturesAllCount -gt 0) {
+
+ #region exportCSV
+ if (-not $NoCsvExport) {
+ $csvFilename = "$($filename)_SubscriptionsFeatures"
+ Write-Host " Exporting SubscriptionsFeatures CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'"
+ ($arrayFeaturesAll | Select-Object -ExcludeProperty mgPathArray | Sort-Object -Property feature, subscriptionId) | Export-Csv -Encoding utf8 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -NoTypeInformation
+ }
+ #endregion exportCSV
+
+ $subFeaturesGroupedByFeature = $arrayFeaturesAll | Group-Object -Property feature
+ $tfCount = ($subFeaturesGroupedByFeature | Measure-Object).Count
+ $htmlTableId = 'TenantSummary_SubFeatures'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tfCount enabled Subscriptions Features
+
+
Set up preview features in Azure subscription docs
+
Download CSV
semicolon |
comma
+
+
+
+Feature
+Subscriptions
+
+
+
+"@)
+
+
+ $cnter = 0
+ $startResProvDetailed = Get-Date
+ $htmlSUMMARYSubFeatures = $null
+ $htmlSUMMARYSubFeatures = foreach ($feature in $subFeaturesGroupedByFeature | Sort-Object -Property name) {
+ @"
+
+$($feature.name)
+$($feature.Count)
+
+"@
+ }
+
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubFeatures)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No enabled Subscriptions Features docs
+'@)
+ }
+ $endSubFeatures = Get-Date
+ Write-Host " Subscriptions Features processing duration: $((New-TimeSpan -Start $startSubFeatures -End $endSubFeatures).TotalMinutes) minutes ($((New-TimeSpan -Start $startSubFeatures -End $endSubFeatures).TotalSeconds) seconds)"
+ #endregion SUMMARYSubFeatures
+
+ #region SUMMARYSubResourceLocks
+ Write-Host ' processing TenantSummary Subscriptions Resource Locks'
+ $tfCount = 6
+ $startResourceLocks = Get-Date
+
+ if (($htResourceLocks.keys | Measure-Object).Count -gt 0) {
+ $htmlTableId = 'TenantSummary_ResourceLocks'
+
+ $subscriptionLocksCannotDeleteCount = ($htResourceLocks.Keys.where( { $htResourceLocks.($_).SubscriptionLocksCannotDeleteCount -gt 0 } )).Count
+ $subscriptionLocksReadOnlyCount = ($htResourceLocks.Keys.where( { $htResourceLocks.($_).SubscriptionLocksReadOnlyCount -gt 0 } )).Count
+
+ $resourceGroupsLocksCannotDeleteCount = ($htResourceLocks.Keys.where( { $htResourceLocks.($_).ResourceGroupsLocksCannotDeleteCount -gt 0 } )).Count
+ $resourceGroupsLocksReadOnlyCount = ($htResourceLocks.Keys.where({ $htResourceLocks.($_).ResourceGroupsLocksReadOnlyCount -gt 0 } )).Count
+
+ $resourcesLocksCannotDeleteCount = ($htResourceLocks.Keys.where( { $htResourceLocks.($_).ResourcesLocksCannotDeleteCount -gt 0 } )).Count
+ $resourcesLocksReadOnlyCount = ($htResourceLocks.Keys.where( { $htResourceLocks.($_).ResourcesLocksReadOnlyCount -gt 0 } )).Count
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resource Locks
+
+
Considerations before applying locks docs
+
Note: Detailed information on Resource Locks is provided in the *_ResourceLocks.csv
+
+
+
+Lock scope
+Lock type
+presence
+
+
+
+Subscription CannotDelete $($subscriptionLocksCannotDeleteCount) of $totalSubCount Subscriptions
+Subscription ReadOnly $($subscriptionLocksReadOnlyCount) of $totalSubCount Subscriptions
+ResourceGroup CannotDelete $($resourceGroupsLocksCannotDeleteCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourceGroupsLocksCannotDeleteCount | Measure-Object -Sum).Sum))
+ResourceGroup ReadOnly $($resourceGroupsLocksReadOnlyCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourceGroupsLocksReadOnlyCount | Measure-Object -Sum).Sum))
+Resource CannotDelete $($resourcesLocksCannotDeleteCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourcesLocksCannotDeleteCount | Measure-Object -Sum).Sum))
+Resource ReadOnly $($resourcesLocksReadOnlyCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourcesLocksReadOnlyCount | Measure-Object -Sum).Sum))
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Resource Locks at all docs
+'@)
+ }
+ $endResourceLocks = Get-Date
+ Write-Host " ResourceLocks processing duration: $((New-TimeSpan -Start $startResourceLocks -End $endResourceLocks).TotalMinutes) minutes ($((New-TimeSpan -Start $startResourceLocks -End $endResourceLocks).TotalSeconds) seconds)"
+ #endregion SUMMARYSubResourceLocks
+
+ #SUMMARYSubDefenderPlansSubscriptionsSkipped
+ if ($arrayDefenderPlansSubscriptionsSkipped.Count -gt 0) {
+ #region SUMMARYSubDefenderPlansSubscriptionsSkipped
+ Write-Host ' processing TenantSummary Subscriptions Microsoft Defender for Cloud plans SubscriptionsSkipped'
+
+ $tfCount = $defenderPlansGroupedByPlanCount
+ $startDefenderPlans = Get-Date
+
+ $htmlTableId = 'TenantSummary_DefenderPlansSubscriptionsSkipped'
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
Microsoft Defender for Cloud plans - Subscriptions skipped
+
+
Register Resource Provider 'Microsoft.Security' docs
+
Microsoft Defender for Cloud's enhanced security features docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription Name
+Subscription Id
+Subscription QuotaId
+Subscription MG path
+reason
+
+
+
+"@)
+
+ foreach ($subscription in $arrayDefenderPlansSubscriptionsSkipped | Sort-Object -Property subscriptionName) {
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($subscription.subscriptionName)
+ $($subscription.subscriptionId)
+ $($subscription.subscriptionQuotaId)
+ $($subscription.subscriptionMgPath)
+ $($subscription.reason)
+
+"@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+
+ $endDefenderPlans = Get-Date
+ Write-Host " Microsoft Defender for Cloud plans SubscriptionsSkipped processing duration: $((New-TimeSpan -Start $startDefenderPlans -End $endDefenderPlans).TotalMinutes) minutes ($((New-TimeSpan -Start $startDefenderPlans -End $endDefenderPlans).TotalSeconds) seconds)"
+ #endregion SUMMARYSubDefenderPlansSubscriptionsSkipped
+ }
+
+ #region SUMMARYSubDefenderPlansByPlan
+ Write-Host ' processing TenantSummary Subscriptions Microsoft Defender for Cloud plans by plan'
+
+ $tfCount = $defenderPlansGroupedByPlanCount
+ $startDefenderPlans = Get-Date
+
+ if ($defenderPlansGroupedByPlanCount -gt 0) {
+ $htmlTableId = 'TenantSummary_DefenderPlans'
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
Microsoft Defender for Cloud plans (by plan)
+
+"@)
+
+ if ($defenderPlanDeprecatedContainerRegistry) {
+ [void]$htmlTenantSummary.AppendLine(@'
+
Using deprecated plan 'Container registries' docs
+'@)
+ }
+ if ($defenderPlanDeprecatedKubernetesService) {
+ [void]$htmlTenantSummary.AppendLine(@'
+
Using deprecated plan 'Kubernetes' docs
+'@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
Microsoft Defender for Cloud's enhanced security features docs
+
Download CSV
semicolon |
comma
+
+
+
+Plan/Tier
+Subscription Count
+
+
+
+"@)
+
+ foreach ($defenderCapabilityAndTier in $defenderPlansGroupedByPlan | Sort-Object -Property Name) {
+ if ($defenderCapabilityAndTier.Name -eq 'ContainerRegistry, Standard' -or $defenderCapabilityAndTier.Name -eq 'KubernetesService, Standard') {
+ $thisDefenderPlan = " $($defenderCapabilityAndTier.Name)"
+ }
+ else {
+ $thisDefenderPlan = $defenderCapabilityAndTier.Name
+ }
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($thisDefenderPlan)
+ $($defenderCapabilityAndTier.Count)
+
+"@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Microsoft Defender for Cloud plans at all
+'@)
+ }
+ $endDefenderPlans = Get-Date
+ Write-Host " Microsoft Defender for Cloud plans by plan processing duration: $((New-TimeSpan -Start $startDefenderPlans -End $endDefenderPlans).TotalMinutes) minutes ($((New-TimeSpan -Start $startDefenderPlans -End $endDefenderPlans).TotalSeconds) seconds)"
+ #endregion SUMMARYSubDefenderPlansByPlan
+
+ #region SUMMARYSubDefenderPlansBySubscription
+ Write-Host ' processing TenantSummary Subscriptions Microsoft Defender for Cloud plans by Subscription'
+ $tfCount = $subsDefenderPlansCount
+ $startDefenderPlans = Get-Date
+
+ if (($arrayDefenderPlans).Count -gt 0) {
+ $htmlTableId = 'TenantSummary_DefenderPlansBySubscription'
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
Microsoft Defender for Cloud plans (by Subscription)
+
+"@)
+
+ if ($defenderPlanDeprecatedContainerRegistry) {
+ [void]$htmlTenantSummary.AppendLine(@'
+
Using deprecated plan 'Container registries' docs
+'@)
+ }
+ if ($defenderPlanDeprecatedKubernetesService) {
+ [void]$htmlTenantSummary.AppendLine(@'
+
Using deprecated plan 'Kubernetes' docs
+'@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
Microsoft Defender for Cloud's enhanced security features docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Subscription MG path
+"@)
+
+ foreach ($defenderCapability in $defenderCapabilities) {
+ if (($defenderPlanDeprecatedContainerRegistry -and $defenderCapability -eq 'ContainerRegistry') -or ($defenderPlanDeprecatedKubernetesService -and $defenderCapability -eq 'KubernetesService')) {
+ $thisDefenderCapability = " $($defenderCapability)"
+ }
+ else {
+ $thisDefenderCapability = $defenderCapability
+ }
+ [void]$htmlTenantSummary.AppendLine(@"
+ $($thisDefenderCapability)
+"@)
+
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+
+'@)
+
+ foreach ($sub in $defenderPlansGroupedBySub) {
+ $nameSplit = $sub.Name.split(', ')
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($nameSplit[0])
+ $($nameSplit[1])
+ $($nameSplit[2])
+
+"@)
+
+ foreach ($plan in $sub.Group | Sort-Object -Property defenderPlan) {
+ [void]$htmlTenantSummary.AppendLine(@"
+ $($plan.defenderPlanTier)
+"@)
+ }
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ }
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Microsoft Defender for Cloud plans at all
+'@)
+ }
+ $endDefenderPlans = Get-Date
+ Write-Host " Microsoft Defender for Cloud plans by Subscription processing duration: $((New-TimeSpan -Start $startDefenderPlans -End $endDefenderPlans).TotalMinutes) minutes ($((New-TimeSpan -Start $startDefenderPlans -End $endDefenderPlans).TotalSeconds) seconds)"
+ #endregion SUMMARYSubDefenderPlansBySubscription
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #region SUMMARYSubUserAssignedIdentities4Resources
+ Write-Host ' processing TenantSummary Subscriptions UserAssigned Managed Identities assigned to Resources'
+ $arrayUserAssignedIdentities4ResourcesCount = $arrayUserAssignedIdentities4Resources.Count
+ $tfCount = $arrayUserAssignedIdentities4ResourcesCount
+ $startUserAssignedIdentities4Resources = Get-Date
+
+ if ($arrayUserAssignedIdentities4ResourcesCount -gt 0) {
+
+ $script:htUserAssignedIdentitiesAssignedResources = @{}
+ $script:htResourcesAssignedUserAssignedIdentities = @{}
+ foreach ($entry in $arrayUserAssignedIdentities4Resources) {
+ #UserAssignedIdentities
+ if (-not $htUserAssignedIdentitiesAssignedResources.($entry.miPrincipalId)) {
+ $script:htUserAssignedIdentitiesAssignedResources.($entry.miPrincipalId) = @{}
+ $script:htUserAssignedIdentitiesAssignedResources.($entry.miPrincipalId).ResourcesCount = 1
+ }
+ else {
+ $script:htUserAssignedIdentitiesAssignedResources.($entry.miPrincipalId).ResourcesCount++
+ }
+ #Resources
+ if (-not $htResourcesAssignedUserAssignedIdentities.(($entry.resourceId).tolower())) {
+ $script:htResourcesAssignedUserAssignedIdentities.(($entry.resourceId).tolower()) = @{}
+ $script:htResourcesAssignedUserAssignedIdentities.(($entry.resourceId).tolower()).UserAssignedIdentitiesCount = 1
+ }
+ else {
+ $script:htResourcesAssignedUserAssignedIdentities.(($entry.resourceId).tolower()).UserAssignedIdentitiesCount++
+ }
+ }
+
+ $htmlTableId = 'TenantSummary_UserAssignedIdentities4Resources'
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
UserAssigned Managed Identities assigned to Resources / vice versa
+
+
Managed identity 'user-assigned' vs 'system-assigned' docs
+
Download CSV
semicolon |
comma
+
+
+
+MI Name
+MI MgPath
+MI Subscription Name
+MI Subscription Id
+MI ResourceGroup
+MI ResourceId
+MI AAD SP objectId
+MI AAD SP applicationId
+MI count Res assignments
+MI used cross subscription
+Res Name
+Res Type
+Res MgPath
+Res Subscription Name
+Res Subscription Id
+Res ResourceGroup
+Res Id
+Res count assigned MIs
+"@)
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+
+'@)
+
+ $userAssignedIdentities4Resources4CSVExport = [System.Collections.ArrayList]@()
+ foreach ($miResEntry in $arrayUserAssignedIdentities4Resources | Sort-Object -Property miResourceId, resourceId) {
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($miResEntry.miResourceName)
+ $($miResEntry.miMgPath)
+ $($miResEntry.miSubscriptionName)
+ $($miResEntry.miSubscriptionId)
+ $($miResEntry.miResourceGroupName)
+ $($miResEntry.miResourceId)
+ $($miResEntry.miPrincipalId)
+ $($miResEntry.miClientId)
+ $($htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount)
+ $($miResEntry.miCrossSubscription)
+ $($miResEntry.resourceName)
+ $($miResEntry.resourceType)
+ $($miResEntry.resourceMgPath)
+ $($miResEntry.resourceSubscriptionName)
+ $($miResEntry.resourceSubscriptionId)
+ $($miResEntry.resourceResourceGroupName)
+ $($miResEntry.resourceId)
+ $($htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount)
+
+"@)
+
+ if (-not $NoCsvExport) {
+ $null = $userAssignedIdentities4Resources4CSVExport.Add([PSCustomObject]@{
+ MIName = $miResEntry.miResourceName
+ MIMgPath = $miResEntry.miMgPath
+ MISubscriptionName = $miResEntry.miSubscriptionName
+ MISubscriptionId = $miResEntry.miSubscriptionId
+ MIResourceGroup = $miResEntry.miResourceGroupName
+ MIResourceId = $miResEntry.miResourceId
+ MIAADSPObjectId = $miResEntry.miPrincipalId
+ MIAADSPApplicationId = $miResEntry.miClientId
+ MICountResAssignments = $htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount
+ MICrossSubscription = $miResEntry.miCrossSubscription
+ ResName = $miResEntry.resourceName
+ ResType = $miResEntry.resourceType
+ ResMgPath = $miResEntry.resourceMgPath
+ ResSubscriptionName = $miResEntry.resourceSubscriptionName
+ ResSubscriptionId = $miResEntry.resourceSubscriptionId
+ ResResourceGroup = $miResEntry.resourceResourceGroupName
+ ResId = $miResEntry.resourceId
+ ResCountAssignedMIs = $htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount
+ })
+ }
+
+ }
+
+ if (-not $NoCsvExport) {
+ Write-Host " Exporting UserAssignedIdentities4Resources CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_UserAssignedIdentities4Resources.csv'"
+ $userAssignedIdentities4Resources4CSVExport | Sort-Object -Property MIResourceId, ResId | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_UserAssignedIdentities4Resources.csv" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No UserAssigned Managed Identities assigned to Resources / vice versa - at all
+'@)
+ }
+ $endUserAssignedIdentities4Resources = Get-Date
+ Write-Host " UserAssigned Managed Identities assigned to Resources processing duration: $((New-TimeSpan -Start $startUserAssignedIdentities4Resources -End $endUserAssignedIdentities4Resources).TotalMinutes) minutes ($((New-TimeSpan -Start $startUserAssignedIdentities4Resources -End $endUserAssignedIdentities4Resources).TotalSeconds) seconds)"
+ #endregion SUMMARYSubUserAssignedIdentities4Resources
+
+ #region SUMMARYPSRule
+ if ($azAPICallConf['htParameters'].DoPSRule -eq $true) {
+ $startPSRule = Get-Date
+ Write-Host ' processing TenantSummary PSRule'
+ $arrayPSRuleCount = $arrayPsRule.Count
+
+ if ($arrayPSRuleCount -gt 0) {
+
+ if (-not $NoCsvExport) {
+ $PSRuleCSVPath = "$($outputPath)$($DirectorySeparatorChar)$($fileName)_PSRule.csv"
+ Write-Host " Exporting 'PSRule for Azure' CSV '$PSRuleCSVPath'"
+ $arrayPsRule | Sort-Object -Property resourceId, pillar, category, severity, rule, recommendation | Export-Csv -Path $PSRuleCSVPath -Delimiter "$csvDelimiter" -NoTypeInformation
+
+ if ($azAPICallConf['htParameters'].onGitHubActions -eq $true) {
+ $exportCSVPSRuleFileSize = (Get-Item -Path $PSRuleCSVPath).length / 1MB
+ if ($exportCSVPSRuleFileSize -gt 100) {
+ Write-Host " The exported 'PSRule for Azure' CSV '$PSRuleCSVPath' exceeds the GitHub file limit of 100MB"
+ Write-Host ' more info: https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github#file-size-limits'
+ Write-Host ' ! ---> Hint: Consider using additional parameter -PSRuleFailedOnly / results will only include failed resources'
+ Write-Host " Re-Exporting 'PSRule for Azure' CSV '$PSRuleCSVPath' excluding column 'description'"
+ $arrayPsRule | Select-Object -ExcludeProperty description | Sort-Object -Property resourceId, pillar, category, severity, rule, recommendation | Export-Csv -Path "$PSRuleCSVPath" -Delimiter "$csvDelimiter" -NoTypeInformation
+
+ $exportCSVPSRuleFileSize = (Get-Item -Path $PSRuleCSVPath).length / 1MB
+ if ($exportCSVPSRuleFileSize -gt 100) {
+ Write-Host " The exported 'PSRule for Azure' CSV '$PSRuleCSVPath' still exceeds the GitHub file limit of 100MB"
+ Write-Host " Re-Exporting 'PSRule for Azure' CSV '$PSRuleCSVPath' excluding column 'description', 'recommendation'"
+ $arrayPsRule | Select-Object -ExcludeProperty description, recommendation | Sort-Object -Property resourceId, pillar, category, severity, rule | Export-Csv -Path "$PSRuleCSVPath" -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ $exportCSVPSRuleFileSize = (Get-Item -Path $PSRuleCSVPath).length / 1MB
+ if ($exportCSVPSRuleFileSize -gt 100) {
+ Write-Host " The exported 'PSRule for Azure' CSV '$PSRuleCSVPath' still exceeds the GitHub file limit of 100MB"
+ Write-Host " Deleting 'PSRule for Azure' CSV '$PSRuleCSVPath' in order to prevent the workflow from failing at push to repo"
+ Remove-Item -Path $PSRuleCSVPath
+ }
+ }
+ else {
+ Write-Host " Info: The exported 'PSRule for Azure' CSV '$PSRuleCSVPath' does not exceed the GitHub file limit of 100MB"
+ }
+ }
+ }
+
+ $grpPSRuleAll = $arrayPsRule | Group-Object -Property resourceType, pillar, category, severity, rule, result
+ $tfCount = $grpPSRuleAll.Name.Count
+
+ $htmlTableId = 'TenantSummary_PSRule'
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tfCount PSRule for Azure results
+
+
Learn about PSRule for Azure
+
Download CSV
semicolon |
comma
+
+
+
+Resource Type
+Resource Count
+Subscription Count
+Pillar
+Category
+Severity
+Rule
+Recommendation
+lnk
+State
+
+
+
+"@)
+
+ foreach ($result in $grpPSRuleAll | Sort-Object -Property Name) {
+ $resultNameSplit = $result.Name.split(', ')
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($resultNameSplit[0])
+ $($result.Group.Count)
+ $(($result.Group.subscriptionId | Sort-Object -Unique).Count)
+ $($resultNameSplit[1])
+ $($resultNameSplit[2])
+ $($resultNameSplit[3])
+ $(($result.Group[0].rule))
+ $(($result.Group[0].recommendation))
+
+ $($resultNameSplit[5])
+
+"@)
+
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No PSRule for Azure results
+'@)
+ }
+ $endPSRule = Get-Date
+ Write-Host " PSRule for Azure processing duration: $((New-TimeSpan -Start $startPSRule -End $endPSRule).TotalMinutes) minutes ($((New-TimeSpan -Start $startPSRule -End $endPSRule).TotalSeconds) seconds)"
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
PSRule for Azure -
integration paused - PSRule for Azure
+'@)
+ }
+ #endregion SUMMARYPSRule
+ }
+
+ #region SUMMARYStorageAccountAnalysis
+ if ($azAPICallConf['htParameters'].NoStorageAccountAccessAnalysis -eq $false) {
+ $startStorageAccountAnalysis = Get-Date
+ Write-Host ' processing TenantSummary Storage Account Access Analysis'
+
+ $arrayStorageAccountAnalysisResultsCount = $arrayStorageAccountAnalysisResults.Count
+ if ($arrayStorageAccountAnalysisResultsCount -gt 0) {
+
+ if (-not $NoCsvExport) {
+ $storageAccountAccessAnalysisCSVPath = "$($outputPath)$($DirectorySeparatorChar)$($fileName)_StorageAccountAccessAnalysis.csv"
+ Write-Host " Exporting 'Storage Account Access Analysis' CSV '$storageAccountAccessAnalysisCSVPath'"
+ $arrayStorageAccountAnalysisResults | Sort-Object -Property StorageAccount | Export-Csv -Path $storageAccountAccessAnalysisCSVPath -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ $saAnonymousAccessCount = ($arrayStorageAccountAnalysisResults.where({ $_.containersAnonymousContainerCount -gt 0 -or $_.containersAnonymousBlobCount -gt 0 })).Count
+ $saStaticWebsitesEnabledCount = ($arrayStorageAccountAnalysisResults.where({ $_.staticWebsitesState -eq $true })).Count
+
+ $htmlTableId = 'TenantSummary_StorageAccountAccessAnalysis'
+ $tfCount = $arrayStorageAccountAnalysisResultsCount
+
+ if ($DoAzureConsumption -eq $true) {
+ $costDays = " ($($AzureConsumptionPeriod)d)"
+ }
+ else {
+ $costDays = " (-DoAzureConsumption = $DoAzureConsumption )"
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+ $tfCount Storage Accounts Access Analysis results - Anonymous Access Container/Blob: $saAnonymousAccessCount, Static Website enabled: $saStaticWebsitesEnabledCount
+
+
Check this article by Elli Shlomo (MVP) Azure Blob Container Threats & Attacks
+
If you enabled the parameters StorageAccountAccessAnalysisSubscriptionTags or StorageAccountAccessAnalysisStorageAccountTags these are integrated in the CSV output *_StorageAccountAccessAnalysis.csv
+
Download CSV
semicolon |
comma
+
+
+
+StorageAccount
+Kind
+SkuName
+SkuTier
+Location
+Subscription
+Subscription MGPath
+ResourceGroup
+Allow Blob Public Access
+Public Network Access
+NetworkAcls defaultAction
+StaticWebsites State
+StaticWebsites Response
+Containers CanBeListed
+Containers Count
+Containers Anonymous Container Count
+Containers Anonymous Blob Count
+IpRules Count
+IpRules IPAddress List
+VirtualNetwork Rules Count
+ResourceAccess Rules Count
+ResourceAccess Rules
+Bypass
+Supports Https Traffic Only
+Minimum Tls Version
+Allow SharedKey Access
+Require Infrastructure Encryption
+Allowed Copy Scope
+Allow Cross Tenant Replication
+DNS Endpoint Type
+Used Capacity (GB)
+Cost$costDays
+Currency
+Cost categories
+
+
+
+"@)
+
+ foreach ($result in $arrayStorageAccountAnalysisResults | Sort-Object -Property storageAccount) {
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($result.storageAccount)
+ $($result.kind)
+ $($result.skuName)
+ $($result.skuTier)
+ $($result.location)
+ $($result.SubscriptionName)
+ $($result.subscriptionMGPath)
+ $($result.resourceGroup)
+ $($result.allowBlobPublicAccess)
+ $($result.publicNetworkAccess)
+ $($result.networkAclsdefaultAction)
+ $($result.staticWebsitesState)
+ $($result.staticWebsitesResponse)
+ $($result.containersCanBeListed)
+ $($result.containersCount)
+ $($result.containersAnonymousContainerCount)
+ $($result.containersAnonymousBlobCount)
+ $($result.ipRulesCount)
+ $($result.ipRulesIPAddressList)
+ $($result.virtualNetworkRulesCount)
+ $($result.resourceAccessRulesCount)
+ $($result.resourceAccessRules)
+ $($result.bypass)
+ $($result.supportsHttpsTrafficOnly)
+ $($result.minimumTlsVersion)
+ $($result.allowSharedKeyAccess)
+ $($result.requireInfrastructureEncryption)
+ $($result.allowedCopyScope)
+ $($result.allowCrossTenantReplication)
+ $($result.dnsEndpointType)
+ $($result.usedCapacity)
+ $($result.cost)
+ $($result.curreny)
+ $($result.metercategory)
+
+"@)
+
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+ No Storage Accounts found
+'@)
+ }
+ $endStorageAccountAnalysis = Get-Date
+ Write-Host " Storage Account Analysis processing duration: $((New-TimeSpan -Start $startStorageAccountAnalysis -End $endStorageAccountAnalysis).TotalMinutes) minutes ($((New-TimeSpan -Start $startStorageAccountAnalysis -End $endStorageAccountAnalysis).TotalSeconds) seconds)"
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+ Storage Account Access Analysis disabled - parameter -NoStorageAccountAccessAnalysis = $($azAPICallConf['htParameters'].NoStorageAccountAccessAnalysis)
+"@)
+ }
+ #endregion SUMMARYStorageAccountAnalysis
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummarySubscriptionsResourceDefenderPSRule
+
+ #region tenantSummaryNetwork
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+'@)
+
+ #region SUMMARYVNets
+ if ($azAPICallConf['htParameters'].NoNetwork -eq $false) {
+ $startVNets = Get-Date
+ Write-Host ' processing TenantSummary VNets'
+ $Vnets = $arrayVirtualNetworks | Sort-Object -Property SubscriptionName, VNet, VNetId -Unique | Select-Object SubscriptionName, Subscription, MGPath, VNet, VNetResourceGroup, Location, AddressSpaceAddressPrefixes, DhcpoptionsDnsservers, SubnetsCount, SubnetsWithNSGCount, SubnetsWithRouteTableCount, SubnetsWithDelegationsCount, PrivateEndpointsCount, SubnetsWithPrivateEndPointsCount, ConnectedDevices, SubnetsWithConnectedDevicesCount, DdosProtection, PeeringsCount
+ $VNetsCount = $Vnets.Count
+
+ if (-not $NoCsvExport) {
+ $virtualNetworksCSVPath = "$($outputPath)$($DirectorySeparatorChar)$($fileName)_VirtualNetworks.csv"
+ Write-Host " Exporting VirtaulNetworks CSV '$virtualNetworksCSVPath'"
+ $Vnets | Export-Csv -Path $virtualNetworksCSVPath -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ if ($VNetsCount -gt 0) {
+
+ $htmlTableId = 'TenantSummary_VNets'
+ $tfCount = $VNetsCount
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tfCount Virtual Networks
+
+
Download CSV
semicolon |
comma
+
+
+
+Subscription Name
+Subscription
+MGPath
+VNet
+VNet Resource Group
+Location
+Address Prefixes
+DNS Servers
+Subnets
+Subnets with NSG
+Subnets with RouteTable
+Subnets with Delegation
+Private Endpoints
+Subnets with Private Endpoints
+Connected device
+Subnets with connected device
+DDoS
+Peerings Count
+
+
+
+"@)
+
+ foreach ($result in $Vnets) {
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($result.SubscriptionName)
+ $($result.Subscription)
+ $($result.MGPath)
+ $($result.VNet)
+ $($result.VNetResourceGroup)
+ $($result.Location)
+ $($result.AddressSpaceAddressPrefixes)
+ $($result.DhcpoptionsDnsservers)
+ $($result.SubnetsCount)
+ $($result.SubnetsWithNSGCount)
+ $($result.SubnetsWithRouteTableCount)
+ $($result.SubnetsWithDelegationsCount)
+ $($result.PrivateEndpointsCount)
+ $($result.SubnetsWithPrivateEndPointsCount)
+ $($result.ConnectedDevices)
+ $($result.SubnetsWithConnectedDevicesCount)
+ $($result.DdosProtection)
+ $($result.PeeringsCount)
+
+"@)
+
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Virtual Networks
+'@)
+ }
+ $endVNets = Get-Date
+ Write-Host " VNets processing duration: $((New-TimeSpan -Start $startVNets -End $endVNets).TotalMinutes) minutes ($((New-TimeSpan -Start $startVNets -End $endVNets).TotalSeconds) seconds)"
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Virtual Networks - Network Analysis disabled - parameter -NoNetwork = $($azAPICallConf['htParameters'].NoNetwork)
+"@)
+ }
+ #endregion SUMMARYVNets
+
+ #region SUMMARYSubnets
+ if ($azAPICallConf['htParameters'].NoNetwork -eq $false) {
+ $startSubnets = Get-Date
+ Write-Host ' processing TenantSummary Subnets'
+ $subnets = $arraySubnets | Sort-Object -Property SubscriptionName, VNet, VNetId, SubnetName
+ $subnetsCount = $subnets.Count
+
+ if (-not $NoCsvExport) {
+ $subnetsCSVPath = "$($outputPath)$($DirectorySeparatorChar)$($fileName)_VirtualNetworkSubnets.csv"
+ Write-Host " Exporting Subnets CSV '$subnetsCSVPath'"
+ $subnets | Export-Csv -Path $subnetsCSVPath -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ if ($subnetsCount -gt 0) {
+
+ $subnetIPAddressUsageCriticalCount = ($subnets.where({ $_.SubnetIPAddressUsageCritical -eq $true })).Count
+ $criticalUsageText = ''
+ if ($subnetIPAddressUsageCriticalCount -gt 0) {
+ $criticalUsageText = " ($subnetIPAddressUsageCriticalCount > $($NetworkSubnetIPAddressUsageCriticalPercentage)% IP addresses used)"
+ }
+
+ $htmlTableId = 'TenantSummary_Subnets'
+ $tfCount = $subnetsCount
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tfCount Subnets$($criticalUsageText)
+
+
Download CSV
semicolon |
comma
+
+
+
+Subscription Name
+Subscription
+MGPath
+VNet
+VNet Resource Group
+Location
+Name
+Id
+Subnet
+Prefix
+Mask
+Range
+Connected devices
+Free IP addresses
+Used IP addresses %
+Private Endpoint Network Policies
+Private Link Service Network Policies
+Service Endpoints count
+Service Endpoints
+Delegation
+NSG
+Route Table
+Nat Gateway
+Private Endpoints
+
+
+
+"@)
+
+ foreach ($result in $subnets) {
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($result.SubscriptionName)
+ $($result.Subscription)
+ $($result.MGPath)
+ $($result.VNet)
+ $($result.VNetResourceGroup)
+ $($result.Location)
+ $($result.SubnetName)
+ $($result.SubnetId)
+ $($result.SubnetNet)
+ $($result.SubnetPrefix)
+ $($result.Subnetmask)
+ $($result.Range)
+ $($result.ConnectedDevices)
+ $($result.AvailableIPAddresses)
+ $($result.UsedIPAddressesPercent)
+ $($result.PrivateEndpointNetworkPolicies)
+ $($result.PrivateLinkServiceNetworkPolicies)
+ $($result.ServiceEndpointsCount)
+ $($result.ServiceEndpoints)
+ $($result.Delegation)
+ $($result.NetworkSecurityGroup)
+ $($result.RouteTable)
+ $($result.NatGateway)
+ $($result.PrivateEndpoints)
+
+"@)
+
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Subnets
+'@)
+ }
+ $endSubnets = Get-Date
+ Write-Host " Subnets processing duration: $((New-TimeSpan -Start $startSubnets -End $endSubnets).TotalMinutes) minutes ($((New-TimeSpan -Start $startSubnets -End $endSubnets).TotalSeconds) seconds)"
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Subnets - Network Analysis disabled - parameter -NoNetwork = $($azAPICallConf['htParameters'].NoNetwork)
+"@)
+ }
+ #endregion SUMMARYSubnets
+
+ #region SUMMARYVNetPeerings
+ if ($azAPICallConf['htParameters'].NoNetwork -eq $false) {
+ $startVNetPeerings = Get-Date
+ Write-Host ' processing TenantSummary VNet Peerings'
+ $vnetPeerings = $arrayVirtualNetworks.where({ $_.PeeringsCount -gt 0 }) | Sort-Object -Property SubscriptionName, VNet, VNetId
+ $VNetsPeeringsCount = $vnetPeerings.Count
+
+ if (-not $NoCsvExport) {
+ $virtualNetworkPeeringsCSVPath = "$($outputPath)$($DirectorySeparatorChar)$($fileName)_VirtualNetworkPeerings.csv"
+ Write-Host " Exporting VirtaulNetworks CSV '$virtualNetworkPeeringsCSVPath'"
+ $vnetPeerings | Export-Csv -Path $virtualNetworkPeeringsCSVPath -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ if ($VNetsPeeringsCount -gt 0) {
+ $vnetPeeringsGroupedByPeeringState = $vnetPeerings | Group-Object -Property PeeringState
+ $arrayPeeringState = foreach ($peeringState in $vnetPeeringsGroupedByPeeringState) {
+ "$($peeringState.Name): $($peeringState.Count)"
+ }
+
+ $xTenantPeeringsCount = $vnetPeerings.where({ $_.PeeringXTenant -eq 'true' }).Count
+
+ $htmlTableId = 'TenantSummary_VNetPeerings'
+ $tfCount = $VNetsPeeringsCount
+ [void]$htmlTenantSummary.AppendLine(@"
+
$VNetsPeeringsCount Virtual Network Peerings - ($($arrayPeeringState -join "$CSVDelimiterOpposite ")) (Cross Tenant: $($xTenantPeeringsCount))
+
+
Download CSV
semicolon |
comma
+
+
+
+Subscription Name
+Subscription
+MGPath
+VNet
+VNet Resource Group
+Location
+Address Prefixes
+DNS Servers
+Subnets
+Subnets with NSG
+Subnets with RouteTable
+Subnets with Delegation
+Private Endpoints
+Subnets with Private Endpoints
+Connected device
+Subnets with connected device
+DDoS
+Peerings Count
+Peering Cross Tenant
+Peering Name
+Peering State
+Peering Sync Level
+Allow Virtual Network Access
+Allow Forwarded Traffic
+Allow Gateway Transit
+Use Remote Gateways
+Do Not Verify Remote Gateways
+Peer Complete Vnets
+Route Service Vips
+
+Remote Peerings Count
+Remote Peering Name
+Remote Peering State
+Remote Peering Sync Level
+Remote Allow Virtual Network Access
+Remote Allow Forwarded Traffic
+Remote Allow Gateway Transit
+Remote Use Remote Gateways
+Remote Do Not Verify Remote Gateways
+Remote Peer Complete Vnets
+Remote Route Service Vips
+
+Remote Subscription Name
+Remote Subscription
+Remote MGPath
+Remote VNet
+Remote VNet State
+Remote VNet Resource Group
+Remote Location
+Remote Address Space Address Prefixes
+Remote Virtual Network AddressSpace Address Prefixes
+
+Remote DNS Servers
+Remote Subnets
+Remote Subnets with NSG
+Remote Subnets with RouteTable
+Remote Subnets with Delegation
+Remote Private Endpoints
+Remote Subnets with Private Endpoints
+Remote Connected devices
+Remote Subnets with connected devices
+Remote DDoS
+
+
+
+
+"@)
+
+ foreach ($result in $vnetPeerings) {
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($result.SubscriptionName)
+ $($result.Subscription)
+ $($result.MGPath)
+ $($result.VNet)
+ $($result.VNetResourceGroup)
+ $($result.Location)
+ $($result.AddressSpaceAddressPrefixes)
+ $($result.DhcpoptionsDnsservers)
+ $($result.SubnetsCount)
+ $($result.SubnetsWithNSGCount)
+ $($result.SubnetsWithRouteTableCount)
+ $($result.SubnetsWithDelegationsCount)
+ $($result.PrivateEndpointsCount)
+ $($result.SubnetsWithPrivateEndPointsCount)
+ $($result.ConnectedDevices)
+ $($result.SubnetsWithConnectedDevicesCount)
+ $($result.DdosProtection)
+ $($result.PeeringsCount)
+ $($result.PeeringXTenant)
+ $($result.PeeringName)
+ $($result.PeeringState)
+ $($result.PeeringSyncLevel)
+ $($result.AllowVirtualNetworkAccess)
+ $($result.AllowForwardedTraffic)
+ $($result.AllowGatewayTransit)
+ $($result.UseRemoteGateways)
+ $($result.DoNotVerifyRemoteGateways)
+ $($result.PeerCompleteVnets)
+ $($result.RouteServiceVips)
+
+ $($result.RemotePeeringsCount)
+ $($result.RemotePeeringName)
+ $($result.RemotePeeringState)
+ $($result.RemotePeeringSyncLevel)
+ $($result.RemoteAllowVirtualNetworkAccess)
+ $($result.RemoteAllowForwardedTraffic)
+ $($result.RemoteAllowGatewayTransit)
+ $($result.RemoteUseRemoteGateways)
+ $($result.RemoteDoNotVerifyRemoteGateways)
+ $($result.RemotePeerCompleteVnets)
+ $($result.RemoteRouteServiceVips)
+
+ $($result.RemoteSubscriptionName)
+ $($result.RemoteSubscription)
+ $($result.RemoteMGPath)
+ $($result.RemoteVNet)
+ $($result.RemoteVNetState)
+ $($result.RemoteVNetResourceGroup)
+ $($result.RemoteVNetLocation)
+ $($result.RemoteAddressSpaceAddressPrefixes)
+ $($result.RemoteVirtualNetworkAddressSpaceAddressPrefixes)
+ $($result.RemoteDhcpoptionsDnsservers)
+ $($result.RemoteSubnetsCount)
+ $($result.RemoteSubnetsWithNSGCount)
+ $($result.RemoteSubnetsWithRouteTable)
+ $($result.RemoteSubnetsWithDelegations)
+ $($result.RemotePrivateEndPoints)
+ $($result.RemoteSubnetsWithPrivateEndPoints)
+ $($result.RemoteConnectedDevices)
+ $($result.RemoteSubnetsWithConnectedDevices)
+ $($result.RemoteDdosProtection)
+
+"@)
+
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Virtual Network Peerings
+'@)
+ }
+ $endVNetPeerings = Get-Date
+ Write-Host " VNet Peerings processing duration: $((New-TimeSpan -Start $startVNetPeerings -End $endVNetPeerings).TotalMinutes) minutes ($((New-TimeSpan -Start $startVNetPeerings -End $endVNetPeerings).TotalSeconds) seconds)"
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Virtual Network Peerings - Network Analysis disabled - parameter -NoNetwork = $($azAPICallConf['htParameters'].NoNetwork)
+"@)
+ }
+ #endregion SUMMARYVNetPeerings
+
+ #region SUMMARYPrivateEndpoints
+ if ($azAPICallConf['htParameters'].NoNetwork -eq $false) {
+ $startPrivateEndpoints = Get-Date
+ Write-Host ' processing TenantSummary PrivateEndpoints'
+ $privateEndPoints = $arrayPrivateEndpointsEnriched | Sort-Object -Property PESubscriptionName, PEName
+ $privateEndPointsCount = $privateEndPoints.Count
+
+ if (-not $NoCsvExport) {
+ $peCSVPath = "$($outputPath)$($DirectorySeparatorChar)$($fileName)_PrivateEndpoints.csv"
+ Write-Host " Exporting PrivateEndpoints CSV '$peCSVPath'"
+ $privateEndPoints | Export-Csv -Path $peCSVPath -Delimiter "$csvDelimiter" -NoTypeInformation
+ }
+
+ if ($privateEndPointsCount -gt 0) {
+
+ $crossSubPECount = ($privateEndPoints.where({ $_.crossSubscriptionPE -eq $true })).Count
+ $crossSubPEText = ''
+ if ($crossSubPECount -gt 0) {
+ $crossSubPEText = " ($crossSubPECount cross Subscription)"
+ }
+ $crossTenantPECount = ($privateEndPoints.where({ $_.crossTenantPE -eq $true })).Count
+ $crossTenantPEText = ''
+ if ($crossTenantPECount -gt 0) {
+ $crossTenantPEText = " ($crossTenantPECount cross Tenant)"
+ }
+
+ $htmlTableId = 'TenantSummary_PrivateEndpoints'
+ $tfCount = $privateEndPointsCount
+ [void]$htmlTenantSummary.AppendLine(@"
+
$tfCount Private Endpoints$($crossSubPEText)$($crossTenantPEText)
+
+
Download CSV
semicolon |
comma
+
+
+
+
+PE Name
+PE Id
+PE Location
+PE Resource Group
+PE Subscription Name
+PE Subscription
+PE MGPath
+PE Type
+PE State
+Cross Subscription PE
+Cross Tenant PE
+
+Resource
+Resource Type
+Resource Id
+Target Subresource
+NIC Name
+FQDN
+IP addresses
+Resource Resource Group
+Resource Subscription Name
+Resource Subscription Id
+Resource MGPath
+Resource Cross Tenant
+
+Subnet
+Subnet Id
+VNet
+VNet Id
+VNet Location
+VNet Resource Group
+Subnet Subscription Name
+Subnet Subscription Id
+Subnet MGPath
+
+
+
+"@)
+
+ foreach ($result in $privateEndPoints) {
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+ $($result.PEName)
+ $($result.PEId)
+ $($result.PELocation)
+ $($result.PEResourceGroup)
+ $($result.PESubscriptionName)
+ $($result.PESubscription)
+ $($result.PEMGPath)
+ $($result.PEConnectionType)
+ $($result.PEConnectionState)
+ $($result.CrossSubscriptionPE)
+ $($result.CrossTenantPE)
+
+ $($result.Resource)
+ $($result.ResourceType)
+ $($result.ResourceId)
+ $($result.TargetSubresource)
+ $($result.NICName)
+ $($result.FQDN)
+ $($result.ipAddresses)
+ $($result.ResourceResourceGroup)
+ $($result.ResourceSubscriptionName)
+ $($result.ResourceSubscriptionId)
+ $($result.ResourceMGPath)
+ $($result.ResourceCrossTenant)
+
+ $($result.Subnet)
+ $($result.SubnetId)
+ $($result.SubnetVNet)
+ $($result.SubnetVNetId)
+ $($result.SubnetVNetLocation)
+ $($result.SubnetVNetResourceGroup)
+ $($result.SubnetSubscriptionName)
+ $($result.SubnetSubscription)
+ $($result.SubnetMGPath)
+
+"@)
+
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Private Endpoints
+'@)
+ }
+ $endPrivateEndpoints = Get-Date
+ Write-Host " PrivateEndpoints processing duration: $((New-TimeSpan -Start $startPrivateEndpoints -End $endPrivateEndpoints).TotalMinutes) minutes ($((New-TimeSpan -Start $startPrivateEndpoints -End $endPrivateEndpoints).TotalSeconds) seconds)"
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Private Endpoints - Network Analysis disabled - parameter -NoNetwork = $($azAPICallConf['htParameters'].NoNetwork)
+"@)
+ }
+ #endregion SUMMARYPrivateEndpoints
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryNetwork
+
+ showMemoryUsage
+
+ #region tenantSummaryDiagnostics
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+'@)
+
+ [void]$htmlTenantSummary.AppendLine( @'
+
Management Groups
+'@)
+
+ #region SUMMARYDiagnosticsManagementGroups
+ Write-Host ' processing TenantSummary Diagnostics Management Groups'
+
+ #hasDiag
+ if ($diagnosticSettingsMgCount -gt 0) {
+ $tfCount = $diagnosticSettingsMgCount
+ $htmlTableId = 'TenantSummary_DiagnosticsManagementGroups'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$diagnosticSettingsMgManagementGroupsCount ($mgsDiagnosticsApplicableCount) Management Groups configured for Diagnostic settings ($diagnosticSettingsMgCount settings)
+
+
Management Group Diagnostic Settings - Create Or Update - REST API docs
+
Download CSV
semicolon |
comma
+
+
+
+Management Group Name
+Management Group Id
+Diagnostic setting
+Inheritance
+Inherited from
+Target
+TargetId
+"@)
+
+ foreach ($logCategory in $diagnosticSettingsMgCategories) {
+ [void]$htmlTenantSummary.AppendLine(@"
+$logCategory
+"@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+
+'@)
+ $htmlSUMMARYDiagnosticsManagementGroups = $null
+ $htmlSUMMARYDiagnosticsManagementGroups = foreach ($entry in $diagnosticSettingsMg | Sort-Object -Property ScopeMgPath, DiagnosticsInheritedFrom, DiagnosticSettingName, DiagnosticTargetType, DiagnosticTargetId) {
+
+ @"
+
+$($entry.ScopeName -replace '<', '<' -replace '>', '>')
+$($entry.ScopeId)
+$($entry.DiagnosticSettingName)
+$($entry.DiagnosticsInheritedOrnot)
+$($entry.DiagnosticsInheritedFrom)
+$($entry.DiagnosticTargetType)
+$($entry.DiagnosticTargetId)
+"@
+ foreach ($logCategory in $diagnosticSettingsMgCategories) {
+ if ($entry.DiagnosticCategoriesHt.($logCategory)) {
+ @"
+ $($entry.DiagnosticCategoriesHt.($logCategory))
+"@
+ }
+ else {
+ @'
+ n/a
+'@
+ }
+ }
+ @'
+
+'@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYDiagnosticsManagementGroups)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Management Groups configured for Diagnostic settings docs
+'@)
+ }
+
+ #hasNoDiag
+ if ($arrayMgsWithoutDiagnosticsCount -gt 0) {
+ $tfCount = $arrayMgsWithoutDiagnosticsCount
+ $htmlTableId = 'TenantSummary_NoDiagnosticsManagementGroups'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$arrayMgsWithoutDiagnosticsCount Management Groups NOT configured for Diagnostic settings docs
+
+
Management Group Diagnostic Settings - Create Or Update - REST API docs
+
Download CSV
semicolon |
comma
+
+
+
+Management Group Name
+Management Group Id
+Management Group path
+
+
+
+"@)
+ $htmlSUMMARYNoDiagnosticsManagementGroups = $null
+ $htmlSUMMARYNoDiagnosticsManagementGroups = foreach ($entry in $arrayMgsWithoutDiagnostics | Sort-Object -Property ScopeMgPath) {
+
+ @"
+
+$($entry.ScopeName -replace '<', '<' -replace '>', '>')
+$($entry.ScopeId)
+$($entry.ScopeMgPath)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNoDiagnosticsManagementGroups)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
All Management Groups are configured for Diagnostic settings docs
+'@)
+ }
+ #endregion SUMMARYDiagnosticsManagementGroups
+
+ #region subscriptions
+ [void]$htmlTenantSummary.AppendLine( @'
+
Subscriptions
+'@)
+
+ #region SUMMARYDiagnosticsSubscriptions
+ Write-Host ' processing TenantSummary Diagnostics Subscriptions'
+
+ #hasDiag
+ if ($diagnosticSettingsSubCount -gt 0) {
+ $tfCount = $diagnosticSettingsSubCount
+ $htmlTableId = 'TenantSummary_DiagnosticsSubscriptions'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$diagnosticSettingsSubSubscriptionsCount Subscriptions configured for Diagnostic settings ($diagnosticSettingsSubCount settings)
+
+
Create diagnostic setting docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Path
+Diagnostic setting
+Target
+TargetId
+"@)
+
+ foreach ($logCategory in $diagnosticSettingsSubCategories) {
+ [void]$htmlTenantSummary.AppendLine(@"
+$logCategory
+"@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+
+'@)
+ $htmlSUMMARYDiagnosticsSubscriptions = $null
+ $htmlSUMMARYDiagnosticsSubscriptions = foreach ($entry in $diagnosticSettingsSub | Sort-Object -Property ScopeName, DiagnosticTargetType, DiagnosticSettingName) {
+
+ @"
+
+$($entry.ScopeName -replace '<', '<' -replace '>', '>')
+$($entry.ScopeId)
+ $($entry.ScopeMgPath)
+$($entry.DiagnosticSettingName)
+$($entry.DiagnosticTargetType)
+$($entry.DiagnosticTargetId)
+"@
+ foreach ($logCategory in $diagnosticSettingsSubCategories) {
+ if ($entry.DiagnosticCategoriesHt.($logCategory)) {
+ @"
+ $($entry.DiagnosticCategoriesHt.($logCategory))
+"@
+ }
+ else {
+ @'
+ n/a
+'@
+ }
+ }
+ @'
+
+'@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYDiagnosticsSubscriptions)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Subscriptions configured for Diagnostic settings docs
+'@)
+ }
+
+ #hasNoDiag
+ if ($diagnosticSettingsSubNoDiagCount -gt 0) {
+ $tfCount = $diagnosticSettingsSubNoDiagCount
+ $htmlTableId = 'TenantSummary_NoDiagnosticsSubscriptions'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$diagnosticSettingsSubNoDiagCount Subscriptions NOT configured for Diagnostic settings
+
+
Create diagnostic setting docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+Subscription Id
+Subscription Mg path
+
+
+
+"@)
+ $htmlSUMMARYNoDiagnosticsSubscriptions = $null
+ $htmlSUMMARYNoDiagnosticsSubscriptions = foreach ($entry in $diagnosticSettingsSubNoDiag | Sort-Object -Property ScopeMgPath) {
+
+ @"
+
+$($entry.ScopeName -replace '<', '<' -replace '>', '>')
+$($entry.ScopeId)
+$($entry.ScopeMgPath)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNoDiagnosticsSubscriptions)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
All Subscriptions are configured for Diagnostic settings docs
+'@)
+ }
+ #endregion SUMMARYDiagnosticsSubscriptions
+
+ #endregion subscriptions
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #region resources
+ [void]$htmlTenantSummary.AppendLine( @'
+
Resources
+'@)
+
+ #region SUMMARYResourcesDiagnosticsCapable
+ Write-Host ' processing TenantSummary Diagnostics Resources Diagnostics Capable (1st party only)'
+ $resourceTypesDiagnosticsArraySorted = $resourceTypesDiagnosticsArray | Sort-Object -Property ResourceType, ResourceCount, Metrics, Logs, LogCategories
+ $resourceTypesDiagnosticsArraySortedCount = ($resourceTypesDiagnosticsArraySorted | Measure-Object).count
+ $resourceTypesDiagnosticsMetricsTrueCount = ($resourceTypesDiagnosticsArray.where( { $_.Metrics -eq $True }) | Measure-Object).count
+ $resourceTypesDiagnosticsLogsTrueCount = ($resourceTypesDiagnosticsArray.where( { $_.Logs -eq $True }) | Measure-Object).count
+ $resourceTypesDiagnosticsMetricsLogsTrueCount = ($resourceTypesDiagnosticsArray.where( { $_.Metrics -eq $True -or $_.Logs -eq $True }) | Measure-Object).count
+ if ($resourceTypesDiagnosticsArraySortedCount -gt 0) {
+ $tfCount = $resourceTypesDiagnosticsArraySortedCount
+ $htmlTableId = 'TenantSummary_ResourcesDiagnosticsCapable'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resources (1st party) Diagnostics capable $resourceTypesDiagnosticsMetricsLogsTrueCount/$resourceTypesDiagnosticsArraySortedCount ResourceTypes ($resourceTypesDiagnosticsMetricsTrueCount Metrics, $resourceTypesDiagnosticsLogsTrueCount Logs)
+
+
Create Custom Policies for Azure ResourceTypes that support Diagnostics Logs and Metrics Create-AzDiagPolicy
+
Supported categories for Azure Resource Logs docs
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Resource Count
+Diagnostics capable
+Metrics
+Logs
+LogCategories
+
+
+
+"@)
+ $htmlSUMMARYResourcesDiagnosticsCapable = $null
+ $htmlSUMMARYResourcesDiagnosticsCapable = foreach ($resourceType in $resourceTypesDiagnosticsArraySorted) {
+ if ($resourceType.Metrics -eq $true -or $resourceType.Logs -eq $true) {
+ $diagnosticsCapable = $true
+ }
+ else {
+ if ($resourceType.Metrics -eq 'n/a - resourcesMeanwhileDeleted' -or $resourceType.Logs -eq 'n/a - resourcesMeanwhileDeleted') {
+ $diagnosticsCapable = 'n/a'
+ }
+ else {
+ $diagnosticsCapable = $false
+ }
+ }
+ @"
+
+$($resourceType.ResourceType)
+$($resourceType.ResourceCount)
+$diagnosticsCapable
+$($resourceType.Metrics)
+$($resourceType.Logs)
+$($resourceType.LogCategories -join "$CsvDelimiterOpposite ")
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResourcesDiagnosticsCapable)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Resources (1st party) Diagnostics capable
+'@)
+ }
+ #endregion SUMMARYResourcesDiagnosticsCapable
+
+ #region SUMMARYDiagnosticsPolicyLifecycle
+ if (-not $NoResourceDiagnosticsPolicyLifecycle) {
+ Write-Host ' processing TenantSummary Diagnostics Resource Diagnostics Policy Lifecycle'
+ $startsumDiagLifecycle = Get-Date
+
+ if ($tenantCustomPoliciesCount -gt 0) {
+ $policiesThatDefineDiagnostics = $tenantCustomPolicies.where( { $_.Type -eq 'custom' -and $_.Json.properties.policyrule.then.details.type -eq 'Microsoft.Insights/diagnosticSettings' -and $_.Json.properties.policyrule.then.details.deployment.properties.template.resources.type -match '/providers/diagnosticSettings' } )
+
+ $policiesThatDefineDiagnosticsCount = ($policiesThatDefineDiagnostics).count
+ if ($policiesThatDefineDiagnosticsCount -gt 0) {
+
+ $diagnosticsPolicyAnalysis = @()
+ $diagnosticsPolicyAnalysis = [System.Collections.ArrayList]@()
+ foreach ($policy in $policiesThatDefineDiagnostics) {
+
+ if (
+ (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.workspaceId -or
+ (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.eventHubAuthorizationRuleId -or
+ (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.storageAccountId
+ ) {
+ if ( (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.workspaceId) {
+ $diagnosticsDestination = 'LA'
+ }
+ if ( (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.eventHubAuthorizationRuleId) {
+ $diagnosticsDestination = 'EH'
+ }
+ if ( (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.storageAccountId) {
+ $diagnosticsDestination = 'SA'
+ }
+
+ if ( (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.logs ) {
+
+ $resourceType = ( (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).type -replace '/providers/diagnosticSettings')
+
+ $resourceTypeCountFromResourceTypesSummarizedArray = ($resourceTypesSummarizedArray.where( { $_.ResourceType -eq $resourceType })).ResourceCount
+ if ($resourceTypeCountFromResourceTypesSummarizedArray) {
+ $resourceCount = $resourceTypeCountFromResourceTypesSummarizedArray
+ }
+ else {
+ $resourceCount = '0'
+ }
+ $supportedLogs = $resourceTypesDiagnosticsArray | Where-Object { $_.ResourceType -eq ( (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).type -replace '/providers/diagnosticSettings') }
+
+ $diagnosticsLogCategoriesSupported = $supportedLogs.LogCategories
+ if (($supportedLogs | Measure-Object).count -gt 0) {
+ $logsSupported = 'yes'
+ }
+ else {
+ $logsSupported = 'no'
+ }
+
+ $roleDefinitionIdsArray = [System.Collections.ArrayList]@()
+ foreach ($roleDefinitionId in ($policy).Json.properties.policyrule.then.details.roleDefinitionIds) {
+ if (($htCacheDefinitionsRole).($roleDefinitionId -replace '.*/')) {
+ $null = $roleDefinitionIdsArray.Add("
$(($htCacheDefinitionsRole).($roleDefinitionId -replace '.*/').Name) ($($roleDefinitionId -replace '.*/'))")
+ }
+ else {
+ Write-Host " DiagnosticsLifeCycle: unknown RoleDefinition '$roleDefinitionId'"
+ $null = $roleDefinitionIdsArray.Add("unknown RoleDefinition: '$roleDefinitionId'")
+ }
+ }
+
+ $policyHasPolicyAssignments = $policyBaseQuery | Where-Object { $_.PolicyDefinitionId -eq $policy.Id } | Sort-Object -Property PolicyDefinitionId, PolicyAssignmentId -Unique
+ $policyHasPolicyAssignmentCount = ($policyHasPolicyAssignments | Measure-Object).count
+ if ($policyHasPolicyAssignmentCount -gt 0) {
+ $policyAssignmentsArray = @()
+ $policyAssignmentsArray += foreach ($policyAssignment in $policyHasPolicyAssignments) {
+ "$($policyAssignment.PolicyAssignmentId) (
$($policyAssignment.PolicyAssignmentDisplayName) )"
+ }
+ $policyAssignmentsCollCount = ($policyAssignmentsArray).count
+ $policyAssignmentsColl = $policyAssignmentsCollCount
+ }
+ else {
+ $policyAssignmentsColl = 0
+ }
+
+ #PolicyUsedinPolicySet
+ $policySetAssignmentsColl = 0
+ $policySetAssignmentsArray = @()
+ $policyUsedinPolicySets = 'n/a'
+
+ $usedInPolicySetArray = [System.Collections.ArrayList]@()
+ foreach ($customPolicySet in $tenantCustomPolicySets) {
+ if ($customPolicySet.Type -eq 'Custom') {
+ $hlpCustomPolicySet = ($customPolicySet)
+ if (($hlpCustomPolicySet.PolicySetPolicyIds) -contains ($policy.Id)) {
+ $null = $usedInPolicySetArray.Add("$($hlpCustomPolicySet.Id) (
$($hlpCustomPolicySet.DisplayName) )")
+
+ #PolicySetHasAssignments
+ $policySetAssignments = ($htCacheAssignmentsPolicy).Values.where( { $_.Assignment.properties.policyDefinitionId -eq ($hlpCustomPolicySet.Id) } )
+ $policySetAssignmentsCount = ($policySetAssignments).count
+ if ($policySetAssignmentsCount -gt 0) {
+ $policySetAssignmentsArray += foreach ($policySetAssignment in $policySetAssignments) {
+ "$(($policySetAssignment.Assignment.id).Tolower()) (
$($policySetAssignment.Assignment.properties.displayName) )"
+ }
+ $policySetAssignmentsCollCount = ($policySetAssignmentsArray).Count
+ $policySetAssignmentsColl = "$policySetAssignmentsCollCount [$($policySetAssignmentsArray -join "$CsvDelimiterOpposite ")]"
+ }
+
+ }
+ }
+ }
+
+ if (($usedInPolicySetArray | Measure-Object).count -gt 0) {
+ $policyUsedinPolicySets = "$(($usedInPolicySetArray | Measure-Object).count) [$($usedInPolicySetArray -join "$CsvDelimiterOpposite ")]"
+ }
+ else {
+ $policyUsedinPolicySets = "$(($usedInPolicySetArray | Measure-Object).count)"
+ }
+
+ if ($recommendation -eq 'review the policy and add the missing categories as required') {
+ if ($policyAssignmentsColl -gt 0 -or $policySetAssignmentsColl -gt 0) {
+ $priority = '1-High'
+ }
+ else {
+ $priority = '3-MediumLow'
+ }
+ }
+ else {
+ $priority = '4-Low'
+ }
+
+ $diagnosticsLogCategoriesCoveredByPolicy = (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.logs
+ if (($diagnosticsLogCategoriesCoveredByPolicy.category | Measure-Object).count -gt 0) {
+
+ if (($supportedLogs | Measure-Object).count -gt 0) {
+ $actionItems = @()
+ $actionItems += foreach ($supportedLogCategory in $supportedLogs.LogCategories) {
+ if ($diagnosticsLogCategoriesCoveredByPolicy.category -notcontains ($supportedLogCategory)) {
+ $supportedLogCategory
+ }
+ }
+ if (($actionItems | Measure-Object).count -gt 0) {
+ $diagnosticsLogCategoriesNotCoveredByPolicy = $actionItems
+ $recommendation = 'review the policy and add the missing categories as required'
+ }
+ else {
+ $diagnosticsLogCategoriesNotCoveredByPolicy = 'all OK'
+ $recommendation = 'no recommendation'
+ }
+ }
+ else {
+ $status = 'Azure Governance Visualizer did not detect the resourceType'
+ $diagnosticsLogCategoriesSupported = 'n/a'
+ $diagnosticsLogCategoriesNotCoveredByPolicy = 'n/a'
+ $recommendation = 'no recommendation as this resourceType seems not existing'
+ $logsSupported = 'unknown'
+ }
+
+ $null = $diagnosticsPolicyAnalysis.Add([PSCustomObject]@{
+ Priority = $priority
+ PolicyId = ($policy).Id
+ PolicyCategory = ($policy).Category
+ PolicyName = ($policy).DisplayName
+ PolicyDeploysRoles = $roleDefinitionIdsArray -join "$CsvDelimiterOpposite "
+ PolicyForResourceTypeExists = $true
+ ResourceType = $resourceType
+ ResourceTypeCount = $resourceCount
+ Status = $status
+ LogsSupported = $logsSupported
+ LogCategoriesInPolicy = ($diagnosticsLogCategoriesCoveredByPolicy.category | Sort-Object) -join "$CsvDelimiterOpposite "
+ LogCategoriesSupported = ($diagnosticsLogCategoriesSupported | Sort-Object) -join "$CsvDelimiterOpposite "
+ LogCategoriesDelta = ($diagnosticsLogCategoriesNotCoveredByPolicy | Sort-Object) -join "$CsvDelimiterOpposite "
+ Recommendation = $recommendation
+ DiagnosticsTargetType = $diagnosticsDestination
+ PolicyAssignments = $policyAssignmentsColl
+ PolicyUsedInPolicySet = $policyUsedinPolicySets
+ PolicySetAssignments = $policySetAssignmentsColl
+ })
+
+ }
+ else {
+ $status = 'no categories defined'
+ $priority = '5-Low'
+ $recommendation = 'Review the policy - the definition has key for categories, but there are none categories defined'
+ $null = $diagnosticsPolicyAnalysis.Add([PSCustomObject]@{
+ Priority = $priority
+ PolicyId = ($policy).Id
+ PolicyCategory = ($policy).Category
+ PolicyName = ($policy).DisplayName
+ PolicyDeploysRoles = $roleDefinitionIdsArray -join "$CsvDelimiterOpposite "
+ PolicyForResourceTypeExists = $true
+ ResourceType = $resourceType
+ ResourceTypeCount = $resourceCount
+ Status = $status
+ LogsSupported = $logsSupported
+ LogCategoriesInPolicy = 'none'
+ LogCategoriesSupported = ($diagnosticsLogCategoriesSupported | Sort-Object) -join "$CsvDelimiterOpposite "
+ LogCategoriesDelta = ($diagnosticsLogCategoriesSupported | Sort-Object) -join "$CsvDelimiterOpposite "
+ Recommendation = $recommendation
+ DiagnosticsTargetType = $diagnosticsDestination
+ PolicyAssignments = $policyAssignmentsColl
+ PolicyUsedInPolicySet = $policyUsedinPolicySets
+ PolicySetAssignments = $policySetAssignmentsColl
+ })
+ }
+ }
+ else {
+ if (-not (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match '/providers/diagnosticSettings' }).properties.metrics ) {
+ Write-Host " DiagnosticsLifeCycle check?!: $($policy.DisplayName) ($($policy.Id)) - something unexpected, no Logs and no Metrics defined"
+ }
+ }
+ }
+ else {
+ Write-Host " DiagnosticsLifeCycle check?!: $($policy.DisplayName) ($($policy.Id)) - something unexpected - not EH, LA, SA"
+ }
+ }
+ #where no Policy exists
+ $diagnosticsPolicyAnalysisCount = ($diagnosticsPolicyAnalysis).count
+ if ($diagnosticsPolicyAnalysisCount -gt 0) {
+ foreach ($resourceTypeDiagnosticsCapable in $resourceTypesDiagnosticsArray | Where-Object { $_.Logs -eq $true }) {
+ if (($diagnosticsPolicyAnalysis.ResourceType).ToLower() -notcontains ( ($resourceTypeDiagnosticsCapable.ResourceType).ToLower() )) {
+ $supportedLogs = ($resourceTypesDiagnosticsArray | Where-Object { $_.ResourceType -eq $resourceTypeDiagnosticsCapable.ResourceType }).LogCategories
+ $logsSupported = 'yes'
+ $resourceTypeCountFromResourceTypesSummarizedArray = ($resourceTypesSummarizedArray | Where-Object { $_.ResourceType -eq $resourceTypeDiagnosticsCapable.ResourceType }).ResourceCount
+ if ($resourceTypeCountFromResourceTypesSummarizedArray) {
+ $resourceCount = $resourceTypeCountFromResourceTypesSummarizedArray
+ }
+ else {
+ $resourceCount = '0'
+ }
+ $recommendation = "Create diagnostics policy for this ResourceType. To verify GA check
docs "
+ $null = $diagnosticsPolicyAnalysis.Add([PSCustomObject]@{
+ Priority = '2-Medium'
+ PolicyId = 'n/a'
+ PolicyCategory = 'n/a'
+ PolicyName = 'n/a'
+ PolicyDeploysRoles = 'n/a'
+ ResourceType = $resourceTypeDiagnosticsCapable.ResourceType
+ ResourceTypeCount = $resourceCount
+ Status = 'n/a'
+ LogsSupported = $logsSupported
+ LogCategoriesInPolicy = 'n/a'
+ LogCategoriesSupported = $supportedLogs -join "$CsvDelimiterOpposite "
+ LogCategoriesDelta = 'n/a'
+ Recommendation = $recommendation
+ DiagnosticsTargetType = 'n/a'
+ PolicyForResourceTypeExists = $false
+ PolicyAssignments = 'n/a'
+ PolicyUsedInPolicySet = 'n/a'
+ PolicySetAssignments = 'n/a'
+ })
+ }
+ }
+ }
+
+ $diagnosticsPolicyAnalysisCount = ($diagnosticsPolicyAnalysis).count
+
+ if ($diagnosticsPolicyAnalysisCount -gt 0) {
+ $tfCount = $diagnosticsPolicyAnalysisCount
+
+ $htmlTableId = 'TenantSummary_DiagnosticsLifecycle'
+ [void]$htmlTenantSummary.AppendLine(@"
+
ResourceDiagnostics for Logs - Policy Lifecycle recommendations
+
+
Create Custom Policies for Azure ResourceTypes that support Diagnostics Logs and Metrics Create-AzDiagPolicy
+
Supported categories for Azure Resource Logs docs
+
+
+
+Priority
+Recommendation
+ResourceType
+Resource Count
+Diagnostics capable (logs)
+Policy Id
+Policy DisplayName
+Role definitions
+Target
+Log Categories not covered by Policy
+Policy assignments
+Policy used in PolicySet
+PolicySet assignments
+
+
+
+"@)
+
+ foreach ($diagnosticsFinding in $diagnosticsPolicyAnalysis | Sort-Object -Property @{Expression = { $_.Priority } }, @{Expression = { $_.Recommendation } }, @{Expression = { $_.ResourceType } }, @{Expression = { $_.PolicyName } }, @{Expression = { $_.PolicyId } }) {
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+ $($diagnosticsFinding.Priority)
+
+
+ $($diagnosticsFinding.Recommendation)
+
+
+ $($diagnosticsFinding.ResourceType)
+
+
+ $($diagnosticsFinding.ResourceTypeCount)
+
+
+ $($diagnosticsFinding.LogsSupported)
+
+
+ $($diagnosticsFinding.PolicyId)
+
+
+ $($diagnosticsFinding.PolicyName)
+
+
+ $($diagnosticsFinding.PolicyDeploysRoles)
+
+
+ $($diagnosticsFinding.DiagnosticsTargetType)
+
+
+ $($diagnosticsFinding.LogCategoriesDelta)
+
+
+ $($diagnosticsFinding.PolicyAssignments)
+
+
+ $($diagnosticsFinding.PolicyUsedInPolicySet)
+
+
+ $($diagnosticsFinding.PolicySetAssignments)
+
+
+"@)
+ }
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No ResourceDiagnostics Policy Lifecycle recommendations
+'@)
+ }
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No ResourceDiagnostics Policy Lifecycle recommendations
+'@)
+ }
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No ResourceDiagnostics Policy Lifecycle recommendations
+'@)
+ }
+ $endsumDiagLifecycle = Get-Date
+ Write-Host " Resource Diagnostics Policy Lifecycle processing duration: $((New-TimeSpan -Start $startsumDiagLifecycle -End $endsumDiagLifecycle).TotalSeconds) seconds"
+ }
+ #endregion SUMMARYDiagnosticsPolicyLifecycle
+
+ #endregion resources
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+
+ #endregion tenantSummaryDiagnostics
+
+ showMemoryUsage
+
+ #region tenantSummaryLimits
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+"@)
+
+ #region tenantSummaryLimitsTenant
+ [void]$htmlTenantSummary.AppendLine( @'
+
Tenant
+'@)
+
+ #policySets
+ if ($tenantCustompolicySetsCount -gt (($LimitPOLICYPolicySetDefinitionsScopedTenant * $LimitCriticalPercentage) / 100)) {
+ [void]$htmlTenantSummary.AppendLine(@"
+
PolicySet definitions: $tenantCustompolicySetsCount/$LimitPOLICYPolicySetDefinitionsScopedTenant docs
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
PolicySet definitions: $tenantCustompolicySetsCount/$LimitPOLICYPolicySetDefinitionsScopedTenant docs
+"@)
+ }
+
+ #CustomRoleDefinitions
+ if ($tenantCustomRolesCount -gt (($LimitRBACCustomRoleDefinitionsTenant * $LimitCriticalPercentage) / 100)) {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Custom Role definitions: $tenantCustomRolesCount/$LimitRBACCustomRoleDefinitionsTenant docs
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Custom Role definitions: $tenantCustomRolesCount/$LimitRBACCustomRoleDefinitionsTenant docs
+"@)
+ }
+
+ #endregion tenantSummaryLimitsTenant
+
+ #region tenantSummaryLimitsManagementGroups
+ [void]$htmlTenantSummary.AppendLine( @'
+
Management Groups
+'@)
+
+ #region SUMMARYMgsapproachingLimitsPolicyAssignments
+ Write-Host ' processing TenantSummary ManagementGroups Limit PolicyAssignments'
+ $mgsApproachingLimitPolicyAssignments = (($policyBaseQueryManagementGroups.where( { [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.PolicyAndPolicySetAssignmentAtScopeCount -gt 0 -and (($_.PolicyAndPolicySetAssignmentAtScopeCount -gt ($LimitPOLICYPolicyAssignmentsManagementGroup * ($LimitCriticalPercentage / 100)))) })) | Select-Object MgId, MgName, PolicyAssignmentAtScopeCount, PolicySetAssignmentAtScopeCount, PolicyAndPolicySetAssignmentAtScopeCount, PolicyAssignmentLimit -Unique)
+ if (($mgsApproachingLimitPolicyAssignments | Measure-Object).count -gt 0) {
+ $tfCount = ($mgsApproachingLimitPolicyAssignments | Measure-Object).count
+ $htmlTableId = 'TenantSummary_MgsapproachingLimitsPolicyAssignments'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($mgsApproachingLimitPolicyAssignments | Measure-Object).count) Management Groups approaching Limit ($LimitPOLICYPolicyAssignmentsManagementGroup) for PolicyAssignment
+
+
Azure Policy Limits docs
+
Download CSV
semicolon |
comma
+
+
+
+Management Group Name
+Management Group Id
+Limit
+
+
+
+"@)
+ $htmlSUMMARYMgsapproachingLimitsPolicyAssignments = $null
+ $htmlSUMMARYMgsapproachingLimitsPolicyAssignments = foreach ($mgApproachingLimitPolicyAssignments in $mgsApproachingLimitPolicyAssignments) {
+ @"
+
+$($mgApproachingLimitPolicyAssignments.MgName -replace '<', '<' -replace '>', '>')
+$($mgApproachingLimitPolicyAssignments.MgId)
+$(($mgApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount/$LimitPOLICYPolicyAssignmentsManagementGroup).tostring('P')) ($($mgApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount)/$($LimitPOLICYPolicyAssignmentsManagementGroup)) ($($mgApproachingLimitPolicyAssignments.PolicyAssignmentAtScopeCount) Policy assignments, $($mgApproachingLimitPolicyAssignments.PolicySetAssignmentAtScopeCount) PolicySet assignments)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicyAssignments)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($mgsApproachingLimitPolicyAssignments | Measure-Object).count) Management Groups approaching Limit ($LimitPOLICYPolicyAssignmentsManagementGroup) for PolicyAssignment docs
+"@)
+ }
+ #endregion SUMMARYMgsapproachingLimitsPolicyAssignments
+
+ #region SUMMARYMgsapproachingLimitsPolicyScope
+ Write-Host ' processing TenantSummary ManagementGroups Limit PolicyScope'
+ $mgsApproachingLimitPolicyScope = (($policyBaseQueryManagementGroups.where( { [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.PolicyDefinitionsScopedCount -gt 0 -and (($_.PolicyDefinitionsScopedCount -gt ($LimitPOLICYPolicyDefinitionsScopedManagementGroup * ($LimitCriticalPercentage / 100)))) })) | Select-Object MgId, MgName, PolicyDefinitionsScopedCount, PolicyDefinitionsScopedLimit -Unique)
+ if (($mgsApproachingLimitPolicyScope | Measure-Object).count -gt 0) {
+ $tfCount = ($mgsApproachingLimitPolicyScope | Measure-Object).count
+ $htmlTableId = 'TenantSummary_MgsapproachingLimitsPolicyScope'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($mgsApproachingLimitPolicyScope | Measure-Object).count) Management Groups approaching Limit ($LimitPOLICYPolicyDefinitionsScopedManagementGroup) for Policy Scope
+
+
Azure Policy Limits docs
+
Download CSV
semicolon |
comma
+
+
+
+Management Group Name
+Management Group Id
+Limit
+
+
+
+"@)
+ $htmlSUMMARYMgsapproachingLimitsPolicyScope = $null
+ $htmlSUMMARYMgsapproachingLimitsPolicyScope = foreach ($mgApproachingLimitPolicyScope in $mgsApproachingLimitPolicyScope) {
+ @"
+
+$($mgApproachingLimitPolicyScope.MgName -replace '<', '<' -replace '>', '>')
+$($mgApproachingLimitPolicyScope.MgId)
+$(($mgApproachingLimitPolicyScope.PolicyDefinitionsScopedCount/$LimitPOLICYPolicyDefinitionsScopedManagementGroup).tostring('P')) $($mgApproachingLimitPolicyScope.PolicyDefinitionsScopedCount)/$($LimitPOLICYPolicyDefinitionsScopedManagementGroup)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicyScope)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($mgsApproachingLimitPolicyScope.count) Management Groups approaching Limit ($LimitPOLICYPolicyDefinitionsScopedManagementGroup) for Policy Scope docs
+"@)
+ }
+ #endregion SUMMARYMgsapproachingLimitsPolicyScope
+
+ #region SUMMARYMgsapproachingLimitsPolicySetScope
+ Write-Host ' processing TenantSummary ManagementGroups Limit PolicySetScope'
+ $mgsApproachingLimitPolicySetScope = (($policyBaseQueryManagementGroups.where( { [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.PolicySetDefinitionsScopedCount -gt 0 -and (($_.PolicySetDefinitionsScopedCount -gt ($LimitPOLICYPolicySetDefinitionsScopedManagementGroup * ($LimitCriticalPercentage / 100)))) })) | Select-Object MgId, MgName, PolicySetDefinitionsScopedCount, PolicySetDefinitionsScopedLimit -Unique)
+ if ($mgsApproachingLimitPolicySetScope.count -gt 0) {
+ $tfCount = ($mgsApproachingLimitPolicySetScope | Measure-Object).count
+ $htmlTableId = 'TenantSummary_MgsapproachingLimitsPolicySetScope'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($mgsApproachingLimitPolicySetScope | Measure-Object).count) Management Groups approaching Limit ($LimitPOLICYPolicySetDefinitionsScopedManagementGroup) for PolicySet Scope
+
+
Azure Policy Limits docs
+
Download CSV
semicolon |
comma
+
+
+
+Management Group Name
+Management Group Id
+Limit
+
+
+
+"@)
+ $htmlSUMMARYMgsapproachingLimitsPolicySetScope = $null
+ $htmlSUMMARYMgsapproachingLimitsPolicySetScope = foreach ($mgApproachingLimitPolicySetScope in $mgsApproachingLimitPolicySetScope) {
+ @"
+
+$($mgApproachingLimitPolicySetScope.MgName -replace '<', '<' -replace '>', '>')
+$($mgApproachingLimitPolicySetScope.MgId)
+$(($mgApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount/$LimitPOLICYPolicySetDefinitionsScopedManagementGroup).tostring('P')) ($($mgApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount)/$($LimitPOLICYPolicySetDefinitionsScopedManagementGroup))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicySetScope)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($mgsApproachingLimitPolicySetScope | Measure-Object).count) Management Groups approaching Limit ($LimitPOLICYPolicySetDefinitionsScopedManagementGroup) for PolicySet Scope docs
+"@)
+ }
+ #endregion SUMMARYMgsapproachingLimitsPolicySetScope
+
+ #region SUMMARYMgsapproachingLimitsRoleAssignment
+ Write-Host ' processing TenantSummary ManagementGroups Limit RoleAssignments'
+ $mgsApproachingRoleAssignmentLimit = $rbacBaseQuery.where( { [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.RoleAssignmentsCount -gt ($LimitRBACRoleAssignmentsManagementGroup * $LimitCriticalPercentage / 100) }) | Sort-Object -Property MgId -Unique | Select-Object -Property MgId, MgName, RoleAssignmentsCount, RoleAssignmentsLimit
+
+ if (($mgsApproachingRoleAssignmentLimit).count -gt 0) {
+ $tfCount = ($mgsApproachingRoleAssignmentLimit).count
+ $htmlTableId = 'TenantSummary_MgsapproachingLimitsRoleAssignment'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($mgsApproachingRoleAssignmentLimit | Measure-Object).count) Management Groups approaching Limit ($LimitRBACRoleAssignmentsManagementGroup) for RoleAssignment
+
+
Azure RBAC Limits docs
+
Download CSV
semicolon |
comma
+
+
+
+Management Group Name
+Management Group Id
+Limit
+
+
+
+"@)
+ $htmlSUMMARYMgsapproachingLimitsRoleAssignment = $null
+ $htmlSUMMARYMgsapproachingLimitsRoleAssignment = foreach ($mgApproachingRoleAssignmentLimit in $mgsApproachingRoleAssignmentLimit) {
+ @"
+
+$($mgApproachingRoleAssignmentLimit.MgName -replace '<', '<' -replace '>', '>')
+$($mgApproachingRoleAssignmentLimit.MgId)
+$(($mgApproachingRoleAssignmentLimit.RoleAssignmentsCount/$LimitRBACRoleAssignmentsManagementGroup).tostring('P')) ($($mgApproachingRoleAssignmentLimit.RoleAssignmentsCount)/$($LimitRBACRoleAssignmentsManagementGroup))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsRoleAssignment)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($mgApproachingRoleAssignmentLimit | Measure-Object).count) Management Groups approaching Limit ($LimitRBACRoleAssignmentsManagementGroup) for RoleAssignment docs
+"@)
+ }
+ #endregion SUMMARYMgsapproachingLimitsRoleAssignment
+
+ #endregion tenantSummaryLimitsManagementGroups
+
+ #region tenantSummaryLimitsSubscriptions
+ [void]$htmlTenantSummary.AppendLine( @'
+
Subscriptions
+'@)
+
+ #region SUMMARYSubsapproachingLimitsResourceGroups
+ Write-Host ' processing TenantSummary Subscriptions Limit Resource Groups'
+ $subscriptionsApproachingLimitFromResourceGroupsAll = $resourceGroupsAll.where( { $_.count_ -gt ($LimitResourceGroups * ($LimitCriticalPercentage / 100)) })
+ if (($subscriptionsApproachingLimitFromResourceGroupsAll | Measure-Object).count -gt 0) {
+ $tfCount = ($subscriptionsApproachingLimitFromResourceGroupsAll | Measure-Object).count
+ $htmlTableId = 'TenantSummary_SubsapproachingLimitsResourceGroups'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingLimitFromResourceGroupsAll | Measure-Object).count) Subscriptions approaching Limit ($LimitResourceGroups) for ResourceGroups
+
+
Azure Subscription Resource Group Limit docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Limit
+
+
+
+"@)
+ $htmlSUMMARYSubsapproachingLimitsResourceGroups = $null
+ $htmlSUMMARYSubsapproachingLimitsResourceGroups = foreach ($subscriptionApproachingLimitFromResourceGroupsAll in $subscriptionsApproachingLimitFromResourceGroupsAll) {
+ $subscriptionData = $htSubDetails.($subscriptionApproachingLimitFromResourceGroupsAll.subscriptionId).details
+ @"
+
+$($subscriptionData.subscription -replace '<', '<' -replace '>', '>')
+$($subscriptionData.subscriptionId)
+$(($subscriptionApproachingLimitFromResourceGroupsAll.count_/$LimitResourceGroups).tostring('P')) ($($subscriptionApproachingLimitFromResourceGroupsAll.count_)/$($LimitResourceGroups))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsResourceGroups)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingLimitFromResourceGroupsAll | Measure-Object).count) Subscriptions approaching Limit ($LimitResourceGroups) for ResourceGroups docs
+"@)
+ }
+ #endregion SUMMARYSubsapproachingLimitsResourceGroups
+
+ #region SUMMARYSubsapproachingLimitsSubscriptionTags
+ Write-Host ' processing TenantSummary Subscriptions Limit Subscription Tags'
+ $subscriptionsApproachingLimitTags = ($optimizedTableForPathQueryMgAndSub.where( { (($_.SubscriptionTagsCount -gt ($LimitTagsSubscription * ($LimitCriticalPercentage / 100)))) }))
+ if (($subscriptionsApproachingLimitTags | Measure-Object).count -gt 0) {
+ $tfCount = ($subscriptionsApproachingLimitTags | Measure-Object).count
+ $htmlTableId = 'TenantSummary_SubsapproachingLimitsSubscriptionTags'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingLimitTags | Measure-Object).count) Subscriptions approaching Limit ($LimitTagsSubscription) for Tags
+
+
Azure Subscription Tag Limit docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Limit
+
+
+
+"@)
+ $htmlSUMMARYSubsapproachingLimitsSubscriptionTags = $null
+ $htmlSUMMARYSubsapproachingLimitsSubscriptionTags = foreach ($subscriptionApproachingLimitTags in $subscriptionsApproachingLimitTags) {
+ @"
+
+$($subscriptionApproachingLimitTags.subscription -replace '<', '<' -replace '>', '>')
+$($subscriptionApproachingLimitTags.subscriptionId)
+$(($subscriptionApproachingLimitTags.SubscriptionTagsCount/$LimitTagsSubscription).tostring('P')) ($($subscriptionApproachingLimitTags.SubscriptionTagsCount)/$($LimitTagsSubscription))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsSubscriptionTags)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($subscriptionsApproachingLimitTags.count) Subscriptions approaching Limit ($LimitTagsSubscription) for Tags docs
+"@)
+ }
+ #endregion SUMMARYSubsapproachingLimitsSubscriptionTags
+
+ #region SUMMARYSubsapproachingLimitsPolicyAssignments
+ Write-Host ' processing TenantSummary Subscriptions Limit PolicyAssignments'
+ $subscriptionsApproachingLimitPolicyAssignments = (($policyBaseQuerySubscriptions.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.PolicyAndPolicySetAssignmentAtScopeCount -gt 0 -and (($_.PolicyAndPolicySetAssignmentAtScopeCount -gt ($_.PolicyAssignmentLimit * ($LimitCriticalPercentage / 100)))) })) | Select-Object MgId, Subscription, SubscriptionId, PolicyAssignmentAtScopeCount, PolicySetAssignmentAtScopeCount, PolicyAndPolicySetAssignmentAtScopeCount, PolicyAssignmentLimit -Unique)
+ if ($subscriptionsApproachingLimitPolicyAssignments.count -gt 0) {
+ $tfCount = ($subscriptionsApproachingLimitPolicyAssignments | Measure-Object).count
+ $htmlTableId = 'TenantSummary_SubsapproachingLimitsPolicyAssignments'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingLimitPolicyAssignments | Measure-Object).count) Subscriptions approaching Limit ($LimitPOLICYPolicyAssignmentsSubscription) for PolicyAssignment
+
+
Azure Policy Limits docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Limit
+
+
+
+"@)
+ $htmlSUMMARYSubsapproachingLimitsPolicyAssignments = $null
+ $htmlSUMMARYSubsapproachingLimitsPolicyAssignments = foreach ($subscriptionApproachingLimitPolicyAssignments in $subscriptionsApproachingLimitPolicyAssignments) {
+ @"
+
+$($subscriptionApproachingLimitPolicyAssignments.subscription -replace '<', '<' -replace '>', '>')
+$($subscriptionApproachingLimitPolicyAssignments.subscriptionId)
+$(($subscriptionApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount/$subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentLimit).tostring('P')) ($($subscriptionApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount)/$($subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentLimit)) ($($subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentAtScopeCount) Policy assignments, $($subscriptionApproachingLimitPolicyAssignments.PolicySetAssignmentAtScopeCount) PolicySet assignments)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicyAssignments)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingLimitPolicyAssignments | Measure-Object).count) Subscriptions approaching Limit ($LimitPOLICYPolicyAssignmentsSubscription) for PolicyAssignment docs
+"@)
+ }
+ #endregion SUMMARYSubsapproachingLimitsPolicyAssignments
+
+ #region SUMMARYSubsapproachingLimitsPolicyScope
+ Write-Host ' processing TenantSummary Subscriptions Limit PolicyScope'
+ $subscriptionsApproachingLimitPolicyScope = (($policyBaseQuerySubscriptions.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.PolicyDefinitionsScopedCount -gt 0 -and (($_.PolicyDefinitionsScopedCount -gt ($_.PolicyDefinitionsScopedLimit * ($LimitCriticalPercentage / 100)))) })) | Select-Object MgId, Subscription, SubscriptionId, PolicyDefinitionsScopedCount, PolicyDefinitionsScopedLimit -Unique)
+ if (($subscriptionsApproachingLimitPolicyScope | Measure-Object).count -gt 0) {
+ $tfCount = ($subscriptionsApproachingLimitPolicyScope | Measure-Object).count
+ $htmlTableId = 'TenantSummary_SubsapproachingLimitsPolicyScope'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingLimitPolicyScope | Measure-Object).count) Subscriptions approaching Limit ($LimitPOLICYPolicyDefinitionsScopedSubscription) for Policy Scope
+
+
Azure Policy Limits docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Limit
+
+
+
+"@)
+ $htmlSUMMARYSubsapproachingLimitsPolicyScope = $null
+ $htmlSUMMARYSubsapproachingLimitsPolicyScope = foreach ($subscriptionApproachingLimitPolicyScope in $subscriptionsApproachingLimitPolicyScope) {
+ @"
+
+$($subscriptionApproachingLimitPolicyScope.subscription -replace '<', '<' -replace '>', '>')
+$($subscriptionApproachingLimitPolicyScope.subscriptionId)
+$(($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedCount/$subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedLimit).tostring('P')) ($($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedCount)/$($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedLimit))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicyScope)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($subscriptionsApproachingLimitPolicyScope.count) Subscriptions approaching Limit ($LimitPOLICYPolicyDefinitionsScopedSubscription) for Policy Scope docs
+"@)
+ }
+ #endregion SUMMARYSubsapproachingLimitsPolicyScope
+
+ #region SUMMARYSubsapproachingLimitsPolicySetScope
+ Write-Host ' processing TenantSummary Subscriptions Limit PolicySetScope'
+ $subscriptionsApproachingLimitPolicySetScope = (($policyBaseQuerySubscriptions.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.PolicySetDefinitionsScopedCount -gt 0 -and (($_.PolicySetDefinitionsScopedCount -gt ($_.PolicySetDefinitionsScopedLimit * ($LimitCriticalPercentage / 100)))) })) | Select-Object MgId, Subscription, SubscriptionId, PolicySetDefinitionsScopedCount, PolicySetDefinitionsScopedLimit -Unique)
+ if ($subscriptionsApproachingLimitPolicySetScope.count -gt 0) {
+ $tfCount = ($subscriptionsApproachingLimitPolicySetScope | Measure-Object).count
+ $htmlTableId = 'TenantSummary_SubsapproachingLimitsPolicySetScope'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingLimitPolicyScope | Measure-Object).count) Subscriptions approaching Limit ($LimitPOLICYPolicySetDefinitionsScopedSubscription) for PolicySet Scope
+
+
Azure Policy Limits docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Limit
+
+
+
+"@)
+ $htmlSUMMARYSubsapproachingLimitsPolicySetScope = $null
+ $htmlSUMMARYSubsapproachingLimitsPolicySetScope = foreach ($subscriptionApproachingLimitPolicySetScope in $subscriptionsApproachingLimitPolicySetScope) {
+ @"
+
+$($subscriptionApproachingLimitPolicySetScope.subscription -replace '<', '<' -replace '>', '>')
+$($subscriptionApproachingLimitPolicySetScope.subscriptionId)
+$(($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount/$subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedLimit).tostring('P')) ($($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount)/$($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedLimit))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicySetScope)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingLimitPolicyScope | Measure-Object).count) Subscriptions approaching Limit ($LimitPOLICYPolicySetDefinitionsScopedSubscription) for PolicySet Scope docs
+"@)
+ }
+ #endregion SUMMARYSubsapproachingLimitsPolicySetScope
+
+ #region SUMMARYSubsapproachingLimitsRoleAssignment
+ Write-Host ' processing TenantSummary Subscriptions Limit RoleAssignments'
+ $subscriptionsApproachingRoleAssignmentLimit = $rbacBaseQuery.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) -and $_.RoleAssignmentsCount -gt ($_.RoleAssignmentsLimit * $LimitCriticalPercentage / 100) }) | Sort-Object -Property SubscriptionId -Unique | Select-Object -Property MgId, SubscriptionId, Subscription, RoleAssignmentsCount, RoleAssignmentsLimit
+
+ $availableSubscriptionsRoleAssignmentLimits = ($htSubscriptionsRoleAssignmentLimit.values | Sort-Object -Unique) -join ' | '
+
+ if (($subscriptionsApproachingRoleAssignmentLimit).count -gt 0) {
+ $tfCount = ($subscriptionsApproachingRoleAssignmentLimit).count
+ $htmlTableId = 'TenantSummary_SubsapproachingLimitsRoleAssignment'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingRoleAssignmentLimit | Measure-Object).count) Subscriptions approaching Limit ($($availableSubscriptionsRoleAssignmentLimits)) for RoleAssignment
+
+
Azure RBAC Limits docs
+
Download CSV
semicolon |
comma
+
+
+
+Subscription
+SubscriptionId
+Limit
+
+
+
+"@)
+ $htmlSUMMARYSubsapproachingLimitsRoleAssignment = $null
+ $htmlSUMMARYSubsapproachingLimitsRoleAssignment = foreach ($subscriptionApproachingRoleAssignmentLimit in $subscriptionsApproachingRoleAssignmentLimit) {
+ @"
+
+$($subscriptionApproachingRoleAssignmentLimit.subscription -replace '<', '<' -replace '>', '>')
+$($subscriptionApproachingRoleAssignmentLimit.subscriptionId)
+$(($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsCount/$subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsLimit).tostring('P')) ($($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsCount)/$($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsLimit))
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsRoleAssignment)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$(($subscriptionsApproachingRoleAssignmentLimit | Measure-Object).count) Subscriptions approaching Limit ($availableSubscriptionsRoleAssignmentLimits) for RoleAssignment docs
+"@)
+ }
+ #endregion SUMMARYSubsapproachingLimitsRoleAssignment
+
+ #endregion tenantSummaryLimitsSubscriptions
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryLimits
+
+ showMemoryUsage
+
+ #region tenantSummaryAAD
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+
Check out AzADServicePrincipalInsights GitHub
+
Demystifying Service Principals - Managed Identities devBlogs
+
John Savill - Azure AD App Registrations, Enterprise Apps and Service Principals YouTube
+'@)
+
+ #region AADSPNotFound
+ Write-Host ' processing TenantSummary AAD ServicePrincipals - not found'
+
+ if ($servicePrincipalRequestResourceNotFoundCount -gt 0) {
+ $tfCount = $servicePrincipalRequestResourceNotFoundCount
+ $htmlTableId = 'TenantSummary_AADSPNotFound'
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($servicePrincipalRequestResourceNotFoundCount) AAD ServicePrincipals 'Request_ResourceNotFound'
+
+
+
+
+Service Principal Object Id
+
+
+
+"@)
+ $htmlSUMMARYAADSPNotFound = $null
+ $htmlSUMMARYAADSPNotFound = foreach ($serviceprincipal in $arrayServicePrincipalRequestResourceNotFound | Sort-Object) {
+
+ @"
+
+$($serviceprincipal)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPNotFound)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No ServicePrincipals where the API returned 'Request_ResourceNotFound'
+'@)
+ }
+ #endregion AADSPNotFound
+
+ #region AADAppNotFound
+ Write-Host ' processing TenantSummary AAD Applications - not found'
+
+ if ($applicationRequestResourceNotFoundCount -gt 0) {
+ $tfCount = $applicationRequestResourceNotFoundCount
+ $htmlTableId = 'TenantSummary_AADAppNotFound'
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($applicationRequestResourceNotFoundCount) AAD Applications 'Request_ResourceNotFound'
+
+
+
+
+Application (Client) Id
+
+
+
+"@)
+ $htmlSUMMARYAADAppNotFound = $null
+ $htmlSUMMARYAADAppNotFound = foreach ($app in $arrayApplicationRequestResourceNotFound | Sort-Object) {
+
+ @"
+
+$($app)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADAppNotFound)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No Applications where the API returned 'Request_ResourceNotFound'
+'@)
+ }
+ #endregion AADAppNotFound
+
+ #region AADSPManagedIdentity
+ $startAADSPManagedIdentityLoop = Get-Date
+ Write-Host ' processing TenantSummary AAD SP Managed Identities'
+
+ if ($servicePrincipalsOfTypeManagedIdentityCount -gt 0) {
+ $tfCount = $servicePrincipalsOfTypeManagedIdentityCount
+ $htmlTableId = 'TenantSummary_AADSPManagedIdentities'
+
+ if ($htOrphanedSPMI.keys.Count -gt 0) {
+ $orphanedSPMIPresent = $true
+ }
+
+ $abbr = "
"
+ $abbrOrphanedSPMI = "
"
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($servicePrincipalsOfTypeManagedIdentityCount) AAD ServicePrincipals type=ManagedIdentity
+
+
Download CSV
semicolon |
comma
+
+
+
+ApplicationId
+DisplayName
+SP ObjectId
+Type
+Usage
+Usage info
+Policy assignment details
+Role assignments
+Assigned to resources$($abbr)
+Orphaned$($abbrOrphanedSPMI)
+
+
+
+"@)
+ $htmlSUMMARYAADSPManagedIdentities = $null
+ $htmlSUMMARYAADSPManagedIdentities = foreach ($serviceprincipalMI in $servicePrincipalsOfTypeManagedIdentity | Sort-Object) {
+
+ $serviceprincipalMIDetailed = $htServicePrincipals.($serviceprincipalMI)
+ $miRoleAssignments = 'n/a'
+ $miType = 'unknown'
+ $userMiAssignedToResourcesCount = ''
+ foreach ($altName in $serviceprincipalMIDetailed.alternativeNames) {
+ if ($altName -like 'isExplicit=*') {
+ $splitAltName = $altName.split('=')
+ if ($splitAltName[1] -eq 'true') {
+ $miType = 'User assigned'
+ if ($htUserAssignedIdentitiesAssignedResources.($serviceprincipalMI)) {
+ $userMiAssignedToResourcesCount = $htUserAssignedIdentitiesAssignedResources.($serviceprincipalMI).ResourcesCount
+ }
+ }
+ if ($splitAltName[1] -eq 'false') {
+ $miType = 'System assigned'
+ }
+ }
+ else {
+ $s1 = $altName -replace '.*/providers/'; $rm = $s1 -replace '.*/'; $resourceType = $s1 -replace "/$($rm)"
+ $miAlternativeName = $altname
+ $miResourceType = $resourceType
+ }
+ }
+
+ if ($miResourceType -eq 'Microsoft.Authorization/policyAssignments') {
+ $policyAssignmentId = $miAlternativeName.ToLower()
+ if ($policyAssignmentId -like '/providers/Microsoft.Management/managementGroups/*') {
+ if (-not ($htCacheAssignmentsPolicy).($policyAssignmentId)) {
+ $assignmentInfo = 'n/a'
+ }
+ else {
+ $assignmentInfo = ($htCacheAssignmentsPolicy).($policyAssignmentId).Assignment
+ }
+ }
+ else {
+ #sub
+ if (((($policyAssignmentId).Split('/') | Measure-Object).Count - 1) -eq 6) {
+ if (-not ($htCacheAssignmentsPolicy).($policyAssignmentId)) {
+ $assignmentInfo = 'n/a'
+ }
+ else {
+ $assignmentInfo = ($htCacheAssignmentsPolicy).($policyAssignmentId).Assignment
+ }
+ }
+ else {
+ #rg
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) {
+ if (-not ($htCacheAssignmentsPolicyOnResourceGroupsAndResources).($policyAssignmentId)) {
+ $assignmentInfo = 'n/a'
+ }
+ else {
+ $assignmentInfo = ($htCacheAssignmentsPolicyOnResourceGroupsAndResources).($policyAssignmentId)
+ }
+ }
+ else {
+ if (-not ($htCacheAssignmentsPolicy).($policyAssignmentId)) {
+ $assignmentInfo = 'n/a'
+ }
+ else {
+ $assignmentInfo = ($htCacheAssignmentsPolicy).($policyAssignmentId).Assignment
+ }
+ }
+ }
+ }
+
+ if ($assignmentinfo -ne 'n/a') {
+
+ if ($assignmentinfo.id -like '/subscriptions/*/resourcegroups/*') {
+
+ if ($assignmentInfo.properties.policyDefinitionId -like '*/providers/Microsoft.Authorization/policyDefinitions/*') {
+ $policyAssignmentsPolicyVariant = 'Policy'
+ $policyAssignmentsPolicyVariant4ht = 'policy'
+ }
+
+ if ($assignmentInfo.properties.policyDefinitionId -like '*/providers/Microsoft.Authorization/policySetDefinitions/*') {
+ $policyAssignmentsPolicyVariant = 'PolicySet'
+ $policyAssignmentsPolicyVariant4ht = 'policySet'
+ }
+
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) {
+ $policyAssignmentsPolicyDefinitionId = ($assignmentInfo.properties.policyDefinitionId).ToLower()
+ $policyAssignmentspolicyDefinitionIdGuid = $policyAssignmentsPolicyDefinitionId -replace '.*/'
+
+ if ($policyAssignmentsPolicyVariant4ht -eq 'policy') {
+ if (($htCacheDefinitionsPolicy).($policyAssignmentsPolicyDefinitionId)) {
+ $definitionInfo = ($htCacheDefinitionsPolicy).($policyAssignmentsPolicyDefinitionId)
+ }
+ else {
+ $definitionInfo = 'unknown'
+ }
+ }
+ if ($policyAssignmentsPolicyVariant4ht -eq 'policySet') {
+ if (($htCacheDefinitionsPolicySet).($policyAssignmentsPolicyDefinitionId)) {
+ $definitionInfo = ($htCacheDefinitionsPolicySet).($policyAssignmentsPolicyDefinitionId)
+ }
+ else {
+ $definitionInfo = 'unknown'
+ }
+ }
+
+ }
+ else {
+ $policyAssignmentsPolicyDefinitionId = ($assignmentInfo.properties.policyDefinitionId).ToLower()
+ $policyAssignmentspolicyDefinitionIdGuid = $policyAssignmentsPolicyDefinitionId -replace '.*/'
+
+ if ($policyAssignmentsPolicyVariant4ht -eq 'policy') {
+ if (($htCacheDefinitionsPolicy).($policyAssignmentsPolicyDefinitionId)) {
+ $definitionInfo = ($htCacheDefinitionsPolicy).($policyAssignmentsPolicyDefinitionId)
+ }
+ else {
+ $definitionInfo = 'unknown'
+ }
+ }
+ if ($policyAssignmentsPolicyVariant4ht -eq 'policySet') {
+ if (($htCacheDefinitionsPolicySet).($policyAssignmentsPolicyDefinitionId)) {
+ $definitionInfo = ($htCacheDefinitionsPolicySet).($policyAssignmentsPolicyDefinitionId)
+ }
+ else {
+ $definitionInfo = 'unknown'
+ }
+ }
+ }
+ }
+ else {
+ if ($assignmentInfo.properties.policyDefinitionId -like '*/providers/Microsoft.Authorization/policyDefinitions/*') {
+ $policyAssignmentsPolicyVariant = 'Policy'
+ $policyAssignmentsPolicyVariant4ht = 'policy'
+ }
+ if ($assignmentInfo.properties.policyDefinitionId -like '*/providers/Microsoft.Authorization/policySetDefinitions/*') {
+ $policyAssignmentsPolicyVariant = 'PolicySet'
+ $policyAssignmentsPolicyVariant4ht = 'policySet'
+ }
+
+ $policyAssignmentsPolicyDefinitionId = ($assignmentInfo.properties.policyDefinitionId).Tolower()
+ $policyAssignmentspolicyDefinitionIdGuid = $policyAssignmentsPolicyDefinitionId -replace '.*/'
+
+ if ($policyAssignmentsPolicyVariant4ht -eq 'policy') {
+ if (($htCacheDefinitionsPolicy).($policyAssignmentsPolicyDefinitionId)) {
+ $definitionInfo = ($htCacheDefinitionsPolicy).($policyAssignmentsPolicyDefinitionId)
+ }
+ else {
+ $definitionInfo = 'unknown'
+ }
+ }
+ if ($policyAssignmentsPolicyVariant4ht -eq 'policySet') {
+ if (($htCacheDefinitionsPolicySet).($policyAssignmentsPolicyDefinitionId)) {
+ $definitionInfo = ($htCacheDefinitionsPolicySet).($policyAssignmentsPolicyDefinitionId)
+ }
+ else {
+ $definitionInfo = 'unknown'
+ }
+ }
+ }
+
+ if ($definitionInfo -eq 'unknown') {
+ $policyAssignmentMoreInfo = "unknown definition ($($policyAssignmentsPolicyDefinitionId))"
+ }
+ else {
+ if ($definitionInfo.type -eq 'BuiltIn') {
+ $policyAssignmentMoreInfo = "$($definitionInfo.Type) $($policyAssignmentsPolicyVariant): $($definitionInfo.LinkToAzAdvertizer) ($policyAssignmentspolicyDefinitionIdGuid)"
+ }
+ else {
+ $policyAssignmentMoreInfo = "$($definitionInfo.Type) $($policyAssignmentsPolicyVariant): $($definitionInfo.DisplayName -replace '<', '<' -replace '>', '>') ($($policyAssignmentsPolicyDefinitionId))"
+ }
+ }
+ }
+ else {
+ $policyAssignmentMoreInfo = 'n/a'
+ }
+
+ }
+ else {
+ $policyAssignmentMoreInfo = 'n/a'
+ }
+
+ if ($htRoleAssignmentsForServicePrincipals.($serviceprincipalMI)) {
+
+ $arrayMiRoleAssignments = @()
+ $helperMiRoleAssignments = $htRoleAssignmentsForServicePrincipals.($serviceprincipalMI).RoleAssignments
+
+ foreach ($roleAssignment in $helperMiRoleAssignments) {
+ if ($roleAssignment.RoleIsCustom -eq 'False') {
+ $arrayMiRoleAssignments += "$(($htCacheDefinitionsRole).($roleAssignment.roleDefinitionId).LinkToAzAdvertizer) ($($roleAssignment.roleassignmentId))"
+ }
+ else {
+ $arrayMiRoleAssignments += "$($roleAssignment.roleDefinitionName -replace '<', '<' -replace '>', '>') ; $($roleAssignment.roleDefinitionId) ($($roleAssignment.roleassignmentId))"
+ }
+ }
+ $miRoleAssignments = "$(($arrayMiRoleAssignments).Count) ($($arrayMiRoleAssignments -join ', '))"
+ }
+
+ $orphanedMI = ''
+ if ($miResourceType -eq 'Microsoft.Authorization/policyAssignments') {
+ $orphanedMI = 'false'
+ if ($htOrphanedSPMI.($serviceprincipalMI)) {
+ $orphanedMI = 'true'
+ }
+ }
+
+ @"
+
+$($serviceprincipalMIDetailed.appId)
+$($serviceprincipalMIDetailed.displayName)
+$($serviceprincipalMI)
+$miType
+$miResourceType
+$($serviceprincipalMIDetailed.alternativeNames -join ', ')
+$($policyAssignmentMoreInfo)
+$($miRoleAssignments)
+$userMiAssignedToResourcesCount
+$orphanedMI
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPManagedIdentities)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$servicePrincipalsOfTypeManagedIdentityCount AAD ServicePrincipals type=ManagedIdentity
+"@)
+ }
+
+ $endAADSPManagedIdentityLoop = Get-Date
+ Write-Host " TenantSummary AAD SP Managed Identities processing duration: $((New-TimeSpan -Start $startAADSPManagedIdentityLoop -End $endAADSPManagedIdentityLoop).TotalMinutes) minutes ($((New-TimeSpan -Start $startAADSPManagedIdentityLoop -End $endAADSPManagedIdentityLoop).TotalSeconds) seconds)"
+ #endregion AADSPManagedIdentity
+
+ #region AADSPCredExpiry
+ if (-not $skipApplications) {
+ $startAADSPCredExpiryLoop = Get-Date
+ Write-Host ' processing TenantSummary AAD SP Apps CredExpiry'
+
+ $servicePrincipalsOfTypeApplicationCount = ($servicePrincipalsOfTypeApplication).Count
+
+ if ($servicePrincipalsOfTypeApplicationCount -gt 0) {
+ $tfCount = $servicePrincipalsOfTypeApplicationCount
+ $htmlTableId = 'TenantSummary_AADSPCredExpiry'
+
+ $servicePrincipalsOfTypeApplicationSecretsExpiring = $servicePrincipalsOfTypeApplication.where( { $htAppDetails.($_).appPasswordCredentialsGracePeriodExpiryCount -gt 0 } )
+ $servicePrincipalsOfTypeApplicationSecretsExpiringCount = ($servicePrincipalsOfTypeApplicationSecretsExpiring).Count
+ $servicePrincipalsOfTypeApplicationCertificatesExpiring = $servicePrincipalsOfTypeApplication.where( { $htAppDetails.($_).appKeyCredentialsGracePeriodExpiryCount -gt 0 } )
+ $servicePrincipalsOfTypeApplicationCertificatesExpiringCount = ($servicePrincipalsOfTypeApplicationCertificatesExpiring).Count
+ if ($servicePrincipalsOfTypeApplicationSecretsExpiringCount -gt 0 -or $servicePrincipalsOfTypeApplicationCertificatesExpiringCount -gt 0) {
+ $warningOrNot = "
"
+ }
+ else {
+ $warningOrNot = "
"
+ }
+ [void]$htmlTenantSummary.AppendLine(@"
+
$warningOrNot $($servicePrincipalsOfTypeApplicationCount) AAD ServicePrincipals type=Application | $servicePrincipalsOfTypeApplicationSecretsExpiringCount Secrets expire < $($AADServicePrincipalExpiryWarningDays)d | $servicePrincipalsOfTypeApplicationCertificatesExpiringCount Certificates expire < $($AADServicePrincipalExpiryWarningDays)d
+
+
Download CSV
semicolon |
comma
+
+
+
+ApplicationId
+DisplayName
+Notes
+SP ObjectId
+App ObjectId
+Secrets
+Secrets expired
+Secrets expiry <$($AADServicePrincipalExpiryWarningDays)d
+Secrets expiry >$($AADServicePrincipalExpiryWarningDays)d & <2y
+Secrets expiry >2y
+Certs
+Certs expired
+Certs expiry <$($AADServicePrincipalExpiryWarningDays)d
+Certs expiry >$($AADServicePrincipalExpiryWarningDays)d & <2y
+Certs expiry >2y
+
+
+
+"@)
+ $htmlSUMMARYAADSPCredExpiry = $null
+ $htmlSUMMARYAADSPCredExpiry = foreach ($serviceprincipalApp in $servicePrincipalsOfTypeApplication | Sort-Object) {
+ @"
+
+$($htAppDetails.$serviceprincipalApp.spGraphDetails.appId)
+$($htAppDetails.$serviceprincipalApp.spGraphDetails.displayName)
+$($htAppDetails.$serviceprincipalApp.spGraphDetails.notes)
+$($htAppDetails.$serviceprincipalApp.spGraphDetails.Id)
+$($htAppDetails.$serviceprincipalApp.appGraphDetails.Id)
+"@
+ if ($htAppDetails.$serviceprincipalApp.appPasswordCredentialsCount) {
+ @"
+$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsCount)
+$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiredCount)
+$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsGracePeriodExpiryCount)
+$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiryOKCount)
+$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiryOKMoreThan2YearsCount)
+"@
+ }
+ else {
+ @'
+0
+0
+0
+0
+0
+'@
+ }
+
+ if ($htAppDetails.$serviceprincipalApp.appKeyCredentialsCount) {
+ @"
+$($htAppDetails.$serviceprincipalApp.appKeyCredentialsCount)
+$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiredCount)
+$($htAppDetails.$serviceprincipalApp.appKeyCredentialsGracePeriodExpiryCount)
+$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiryOKCount)
+$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiryOKMoreThan2YearsCount)
+"@
+ }
+ else {
+ @'
+0
+0
+0
+0
+0
+'@
+ }
+
+ @'
+
+'@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPCredExpiry)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$servicePrincipalsOfTypeApplicationCount AAD ServicePrincipals type=Application
+"@)
+ }
+
+ $endAADSPCredExpiryLoop = Get-Date
+ Write-Host " TenantSummary AAD SP Apps CredExpiry processing duration: $((New-TimeSpan -Start $startAADSPCredExpiryLoop -End $endAADSPCredExpiryLoop).TotalMinutes) minutes ($((New-TimeSpan -Start $startAADSPCredExpiryLoop -End $endAADSPCredExpiryLoop).TotalSeconds) seconds)"
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No information on AAD ServicePrincipals type=Application as Guest account does not have enough permissions
+'@)
+ }
+ #endregion AADSPCredExpiry
+
+ #region AADSPExternalSP
+ Write-Host ' processing TenantSummary AAD External ServicePrincipals'
+ $startAADSPExternalSP = Get-Date
+
+ $htRoleAssignmentsForServicePrincipalsRgRes = @{}
+ $roleAssignmentsForServicePrincipalsRgRes = (((($htCacheAssignmentsRBACOnResourceGroupsAndResources).values).where( { $_.ObjectType -eq 'ServicePrincipal' })) | Sort-Object -Property RoleAssignmentId -Unique)
+ foreach ($spWithRoleAssignment in $roleAssignmentsForServicePrincipalsRgRes | Group-Object -Property ObjectId) {
+ if (-not $htRoleAssignmentsForServicePrincipalsRgRes.($spWithRoleAssignment.Name)) {
+ $htRoleAssignmentsForServicePrincipalsRgRes.($spWithRoleAssignment.Name) = @{}
+ $htRoleAssignmentsForServicePrincipalsRgRes.($spWithRoleAssignment.Name).RoleAssignments = $spWithRoleAssignment.group
+ }
+ }
+
+ $appsWithOtherOrgId = $htServicePrincipals.Keys.where( { $htServicePrincipals.($_).servicePrincipalType -eq 'Application' -and $htServicePrincipals.($_).appOwnerOrganizationId -ne $azAPICallConf['checkContext'].Tenant.Id } )
+ $appsWithOtherOrgIdCount = ($appsWithOtherOrgId).Count
+
+ if ($appsWithOtherOrgIdCount -gt 0) {
+ $tfCount = $appsWithOtherOrgIdCount
+ $htmlTableId = 'TenantSummary_AADSPExternal'
+
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ $abbr = "
"
+ }
+ else {
+ $abbr = ''
+ }
+ [void]$htmlTenantSummary.AppendLine(@"
+
$($appsWithOtherOrgIdCount) External (appOwnerOrganizationId) AAD ServicePrincipals type=Application
+
+
Download CSV
semicolon |
comma
+
+
+
+ApplicationId
+DisplayName
+SP ObjectId
+OrganizationId
+Role assignments$($abbr)
+
+
+
+"@)
+ $htmlSUMMARYAADSPExternal = $null
+ $htmlSUMMARYAADSPExternal = foreach ($serviceprincipalApp in $appsWithOtherOrgId | Sort-Object) {
+ $arrayRoleAssignments4ExternalApp = [System.Collections.ArrayList]@()
+ $roleAssignmentsMgSub = $htRoleAssignmentsForServicePrincipals.($serviceprincipalApp).RoleAssignments
+ $roleAssignmentsMgSubCount = ($roleAssignmentsMgSub).Count
+ $roleAssignments4ExternalApp = 'n/a'
+ if ($roleAssignmentsMgSubCount -gt 0) {
+ $roleAssignments4ExternalApp = $roleAssignmentsMgSubCount
+ }
+ $roleAssignmentsRgRes = $htRoleAssignmentsForServicePrincipalsRgRes.($serviceprincipalApp).RoleAssignments
+ $roleAssignmentsRgResCount = ($roleAssignmentsRgRes).Count
+ if ($roleAssignmentsRgResCount -gt 0) {
+ foreach ($roleAssignmentRgRes in $roleAssignmentsRgRes) {
+ $null = $arrayRoleAssignments4ExternalApp.Add([PSCustomObject]@{
+ roleAssignmentId = $roleAssignmentRgRes.RoleAssignmentId
+ })
+ }
+ $roleAssignments4ExternalApp = "$roleAssignmentsRgResCount ($($arrayRoleAssignments4ExternalApp.roleAssignmentId -join ', '))"
+ }
+
+ @"
+
+$($htServicePrincipals.($serviceprincipalApp).appId)
+$($htServicePrincipals.($serviceprincipalApp).displayName)
+$($htServicePrincipals.($serviceprincipalApp).id)
+$($htServicePrincipals.($serviceprincipalApp).appOwnerOrganizationId)
+$roleAssignments4ExternalApp
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPExternal)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$appsWithOtherOrgIdCount External (appOwnerOrganizationId) AAD ServicePrincipals type=Application
+"@)
+ }
+
+ $endAADSPExternalSP = Get-Date
+ Write-Host " TenantSummary AAD External ServicePrincipals processing duration: $((New-TimeSpan -Start $startAADSPExternalSP -End $endAADSPExternalSP).TotalMinutes) minutes ($((New-TimeSpan -Start $startAADSPExternalSP -End $endAADSPExternalSP).TotalSeconds) seconds)"
+ #endregion AADSPExternalSP
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryAAD
+
+ showMemoryUsage
+
+ #region tenantSummaryConsumption
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+
Customize your Azure environment optimizations (Cost, Reliability & more) with Azure Optimization Engine (AOE)
+'@)
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ $startConsumption = Get-Date
+ Write-Host ' processing TenantSummary Consumption'
+
+ if (($arrayConsumptionData | Measure-Object).Count -gt 0) {
+ $tfCount = ($arrayConsumptionData | Measure-Object).Count
+ $htmlTableId = 'TenantSummary_Consumption'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Total cost $($arrayTotalCostSummary -join "$CsvDelimiterOpposite ") last $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)
+
+
Download CSV
semicolon |
comma
+
+
+
+ChargeType
+ResourceType
+Category
+ResourceCount
+Cost ($($AzureConsumptionPeriod)d)
+Currency
+Subscriptions
+
+
+
+"@)
+ $htmlSUMMARYConsumption = $null
+ $htmlSUMMARYConsumption = foreach ($consumptionLine in $arrayConsumptionData) {
+ @"
+
+$($consumptionLine.ConsumedServiceChargeType)
+$($consumptionLine.ResourceType)
+$($consumptionLine.ConsumedServiceCategory)
+$($consumptionLine.ConsumedServiceInstanceCount)
+$($consumptionLine.ConsumedServiceCost)
+$($consumptionLine.ConsumedServiceCurrency)
+$($consumptionLine.ConsumedServiceSubscriptions)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYConsumption)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No information on Consumption
+'@)
+ }
+
+ $endConsumption = Get-Date
+ Write-Host " TenantSummary Consumption processing duration: $((New-TimeSpan -Start $startConsumption -End $endConsumption).TotalMinutes) minutes ($((New-TimeSpan -Start $startConsumption -End $endConsumption).TotalSeconds) seconds)"
+
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@'
+
No information on Consumption as switch parameter -DoAzureConsumption was not applied
+'@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryConsumption
+
+ showMemoryUsage
+
+ #region tenantSummaryChangeTracking
+ Write-Host ' processing TenantSummary ChangeTracking'
+ $startChangeTracking = Get-Date
+ $xdaysAgo = (Get-Date).AddDays(-$ChangeTrackingDays)
+
+ #region ctpolicydata
+ Write-Host ' processing Policy'
+ $customPolicyCreatedOrUpdated = ($customPoliciesDetailed.where( { (-not [string]::IsNullOrEmpty($_.CreatedOn) -and [datetime]$_.CreatedOn -gt $xdaysAgo) -or (-not [string]::IsNullOrEmpty($_.UpdatedOn) -and [datetime]$_.UpdatedOn -gt $xdaysAgo) }))
+ $customPolicyCreatedOrUpdatedCount = $customPolicyCreatedOrUpdated.Count
+ $customPolicyCreatedMgSub = ($customPolicyCreatedOrUpdated.where( { -not [string]::IsNullOrEmpty($_.CreatedOn) -and [datetime]$_.CreatedOn -gt $xdaysAgo }))
+ $customPolicyCreatedMg = ($customPolicyCreatedMgSub.where( { $_.Scope -eq 'Mg' }))
+ $customPolicyCreatedMgCount = ($customPolicyCreatedMg).count
+ $customPolicyCreatedSub = ($customPolicyCreatedMgSub.where( { $_.Scope -eq 'Sub' }))
+ $customPolicyCreatedSubCount = ($customPolicyCreatedSub).count
+
+ $customPolicyUpdatedMgSub = ($customPolicyCreatedOrUpdated.where( { -not [string]::IsNullOrEmpty($_.UpdatedOn) -and [datetime]$_.UpdatedOn -gt $xdaysAgo }))
+ $customPolicyUpdatedMg = ($customPolicyUpdatedMgSub.where( { $_.Scope -eq 'Mg' }))
+ $customPolicyUpdatedMgCount = ($customPolicyUpdatedMg).count
+ $customPolicyUpdatedSub = ($customPolicyUpdatedMgSub.where( { $_.Scope -eq 'Sub' }))
+ $customPolicyUpdatedSubCount = ($customPolicyUpdatedSub).count
+ #endregion ctpolicydata
+
+ #region ctpolicySetdata
+ Write-Host ' processing PolicySet'
+ $customPolicySetCreatedOrUpdated = ($customPolicySetsDetailed.where( { (-not [string]::IsNullOrEmpty($_.CreatedOn) -and [datetime]$_.CreatedOn -gt $xdaysAgo) -or (-not [string]::IsNullOrEmpty($_.UpdatedOn) -and [datetime]$_.UpdatedOn -gt $xdaysAgo) }))
+ $customPolicySetCreatedOrUpdatedCount = $customPolicySetCreatedOrUpdated.Count
+
+ $customPolicySetCreatedMgSub = ($customPolicySetCreatedOrUpdated.where( { -not [string]::IsNullOrEmpty($_.CreatedOn) -and [datetime]$_.CreatedOn -gt $xdaysAgo }))
+ $customPolicySetCreatedMg = ($customPolicySetCreatedMgSub.where( { $_.Scope -eq 'Mg' }))
+ $customPolicySetCreatedMgCount = ($customPolicySetCreatedMg).count
+ $customPolicySetCreatedSub = ($customPolicySetCreatedMgSub.where( { $_.Scope -eq 'Sub' }))
+ $customPolicySetCreatedSubCount = ($customPolicySetCreatedSub).count
+
+ $customPolicySetUpdatedMgSub = ($customPolicySetCreatedOrUpdated.where( { -not [string]::IsNullOrEmpty($_.UpdatedOn) -and [datetime]$_.UpdatedOn -gt $xdaysAgo }))
+ $customPolicySetUpdatedMg = ($customPolicySetUpdatedMgSub.where( { $_.Scope -eq 'Mg' }))
+ $customPolicySetUpdatedMgCount = ($customPolicySetUpdatedMg).count
+ $customPolicySetUpdatedSub = ($customPolicySetUpdatedMgSub.where( { $_.Scope -eq 'Sub' }))
+ $customPolicySetUpdatedSubCount = ($customPolicySetUpdatedSub).count
+ #endregion ctpolicySetdata
+
+ #region ctpolicyAssignmentsData
+ Write-Host ' processing Policy assignment'
+ $policyAssignmentsCreatedOrUpdated = (($arrayPolicyAssignmentsEnriched.where( { $_.Inheritance -notlike 'inherited*' } )).where( { (-not [string]::IsNullOrEmpty($_.CreatedOn) -and [datetime]$_.CreatedOn -gt $xdaysAgo) -or (-not [string]::IsNullOrEmpty($_.UpdatedOn) -and [datetime]$_.UpdatedOn -gt $xdaysAgo) }))
+ $policyAssignmentsCreatedOrUpdatedCount = $policyAssignmentsCreatedOrUpdated.Count
+
+ $policyAssignmentsCreatedMgSub = $policyAssignmentsCreatedOrUpdated.where( { (-not [string]::IsNullOrEmpty($_.CreatedOn) -and [datetime]$_.CreatedOn -gt $xdaysAgo) })
+ $policyAssignmentsCreatedMg = ($policyAssignmentsCreatedMgSub.where( { $_.mgOrSubOrRG -eq 'Mg' }))
+ $policyAssignmentsCreatedMgCount = ($policyAssignmentsCreatedMg).count
+ $policyAssignmentsCreatedSub = ($policyAssignmentsCreatedMgSub.where( { $_.mgOrSubOrRG -eq 'Sub' }))
+ $policyAssignmentsCreatedSubCount = ($policyAssignmentsCreatedSub).count
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) {
+ $policyAssignmentsCreatedRg = ($policyAssignmentsUpdatedMgSub.where( { $_.mgOrSubOrRG -eq 'RG' }))
+ $policyAssignmentsCreatedRgCount = ($policyAssignmentsCreatedRg).count
+ }
+
+ $policyAssignmentsUpdatedMgSub = $policyAssignmentsCreatedOrUpdated.where( { (-not [string]::IsNullOrEmpty($_.UpdatedOn) -and [datetime]$_.UpdatedOn -gt $xdaysAgo) })
+ $policyAssignmentsUpdatedMg = ($policyAssignmentsUpdatedMgSub.where( { $_.mgOrSubOrRG -eq 'Mg' }))
+ $policyAssignmentsUpdatedMgCount = ($policyAssignmentsUpdatedMg).count
+ $policyAssignmentsUpdatedSub = ($policyAssignmentsUpdatedMgSub.where( { $_.mgOrSubOrRG -eq 'Sub' }))
+ $policyAssignmentsUpdatedSubCount = ($policyAssignmentsUpdatedSub).count
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) {
+ $policyAssignmentsUpdatedRg = ($policyAssignmentsUpdatedMgSub.where( { $_.mgOrSubOrRG -eq 'RG' }))
+ $policyAssignmentsUpdatedRgCount = ($policyAssignmentsUpdatedRg).count
+ }
+
+
+ if ($customPolicyCreatedOrUpdatedCount -gt 0 -or $customPolicySetCreatedOrUpdatedCount -gt 0 -or $policyAssignmentsCreatedOrUpdatedCount -gt 0) {
+ $ctContenIndicatorPolicy = 'ctContenPolicyTrue'
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) {
+ $policyAssignmentSummaryCt = "(Mg: C:$($policyAssignmentsCreatedMgCount), U:$($policyAssignmentsUpdatedMgCount); Sub: C:$($policyAssignmentsCreatedSubCount), U:$($policyAssignmentsUpdatedSubCount)); RG: C:$($policyAssignmentsCreatedRgCount), U:$($policyAssignmentsUpdatedRgCount)"
+ }
+ else {
+ $policyAssignmentSummaryCt = "(Mg: C:$($policyAssignmentsCreatedMgCount), U:$($policyAssignmentsUpdatedMgCount); Sub: C:$($policyAssignmentsCreatedSubCount), U:$($policyAssignmentsUpdatedSubCount))"
+ }
+
+ }
+ else {
+ $ctContenIndicatorPolicy = 'ctContenPolicyFalse'
+ $policyAssignmentSummaryCt = ''
+ }
+ #endregion ctpolicyAssignmentsData
+
+ ##RBAC
+ #region ctRbacData
+ #rbac defs
+ Write-Host ' processing RBAC'
+ $customRoleDefinitionsCreatedOrUpdated = $tenantCustomRoles.where( { $_.IsCustom -eq $true -and $_.Json.properties.createdOn -gt $xdaysAgo -or $_.Json.properties.updatedOn -gt $xdaysAgo })
+ $customRoleDefinitionsCreatedOrUpdatedCount = $customRoleDefinitionsCreatedOrUpdated.Count
+
+ #rbac defs created
+ Write-Host ' processing RBAC Role definition created'
+ $customRoleDefinitionsCreated = $customRoleDefinitionsCreatedOrUpdated.where( { $_.Json.properties.createdOn -gt $xdaysAgo })
+ $customRoleDefinitionsCreatedCount = $customRoleDefinitionsCreated.Count
+
+ #rbac defs updated
+ Write-Host ' processing RBAC Role definition updated'
+ $customRoleDefinitionsUpdated = $customRoleDefinitionsCreatedOrUpdated.where( { $_.Json.properties.updatedOn -ne $_.Json.properties.createdOn -and $_.Json.properties.updatedOn -gt $xdaysAgo })
+ $customRoleDefinitionsUpdatedCount = $customRoleDefinitionsUpdated.Count
+ #endregion ctRbacData
+
+ #region ctrbacassignments
+ #rbac roleassignments
+ Write-Host ' processing RBAC Role assignments'
+ $roleAssignmentsCreated = ($rbacAll | Sort-Object -Property RoleAssignmentId, ObjectId -Unique).where( { -not [string]::IsNullOrEmpty($_.CreatedOn) -and [datetime]$_.CreatedOn -gt $xdaysAgo })
+ $roleAssignmentsCreatedUnique = ($roleAssignmentsCreated | Sort-Object -Property RoleAssignmentId -Unique)
+ $roleAssignmentsCreatedCount = ($roleAssignmentsCreated | Sort-Object -Property RoleAssignmentId -Unique).Count
+ $roleAssignmentsCreatedImpactedIdentitiesCount = $roleAssignmentsCreated.Count
+
+ #rbac assignments createdMg
+ $roleAssignmentsCreatedMg = $roleAssignmentsCreatedUnique.where( { $_.ScopeTenOrMgOrSubOrRGOrRes -eq 'MG' -or $_.ScopeTenOrMgOrSubOrRGOrRes -eq 'Ten' })
+ $roleAssignmentsCreatedMgCount = $roleAssignmentsCreatedMg.Count
+ #rbac assignments createdSub
+ $roleAssignmentsCreatedSub = $roleAssignmentsCreatedUnique.where( { $_.ScopeTenOrMgOrSubOrRGOrRes -eq 'Sub' })
+ $roleAssignmentsCreatedSubCount = $roleAssignmentsCreatedSub.Count
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ $roleAssignmentsCreatedSubRg = $roleAssignmentsCreatedUnique.where( { $_.ScopeTenOrMgOrSubOrRGOrRes -eq 'RG' })
+ $roleAssignmentsCreatedSubRgCount = $roleAssignmentsCreatedSubRg.Count
+ $roleAssignmentsCreatedSubRgRes = $roleAssignmentsCreatedUnique.where( { $_.ScopeTenOrMgOrSubOrRGOrRes -eq 'Res' })
+ $roleAssignmentsCreatedSubRgResCount = $roleAssignmentsCreatedSubRgRes.Count
+ }
+
+ if ($customRoleDefinitionsCreatedOrUpdatedCount -gt 0 -or $roleAssignmentsCreatedCount -gt 0) {
+ $ctContenIndicatorRBAC = 'ctContenRBACTrue'
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ $rbacAssignmentSummaryCt = "(Mg: $roleAssignmentsCreatedMgCount; Sub: $roleAssignmentsCreatedSubCount; RG: $roleAssignmentsCreatedSubRgCount; Res: $roleAssignmentsCreatedSubRgResCount)"
+ }
+ else {
+ $rbacAssignmentSummaryCt = "(Mg: $roleAssignmentsCreatedMgCount; Sub: $roleAssignmentsCreatedSubCount)"
+ }
+ }
+ else {
+ $ctContenIndicatorRBAC = 'ctContenRBACFalse'
+ $rbacAssignmentSummaryCt = ''
+ }
+ #endregion ctrbacassignments
+
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #region ctresources
+ Write-Host ' processing Resources'
+ $resourcesCreatedOrChanged = $resourcesIdsAll.where( { $_.createdTime -gt $xdaysAgo -or $_.changedTime -gt $xdaysAgo })
+ $resourcesCreatedOrChangedCount = $resourcesCreatedOrChanged.Count
+
+ $resourcesCreatedAndChanged = $resourcesIdsAll.where( { $_.createdTime -gt $xdaysAgo -and $_.changedTime -gt $xdaysAgo })
+ $resourcesCreatedAndChangedCount = $resourcesCreatedAndChanged.Count
+
+ $resourcesCreated = $resourcesCreatedOrChanged.where( { $_.createdTime -gt $xdaysAgo })
+ $resourcesCreatedCount = $resourcesCreated.Count
+ $resourcesChanged = $resourcesCreatedOrChanged.where( { $_.changedTime -gt $xdaysAgo })
+ $resourcesChangedCount = $resourcesChanged.Count
+
+ if ($resourcesCreatedOrChangedCount -gt 0) {
+ $ctContenIndicatorResources = 'ctContenResourcesTrue'
+ $resourcesCreatedOrChangedGrouped = $resourcesCreatedOrChanged | Group-Object -Property type
+ $resourcesCreatedOrChangedGroupedCount = ($resourcesCreatedOrChangedGrouped | Measure-Object).Count
+ }
+ else {
+ $ctContenIndicatorResources = 'ctContenResourcesFalse'
+ }
+ #endregion ctresources
+ }
+
+
+
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+"@)
+
+ #region ctpolicy
+ [void]$htmlTenantSummary.AppendLine(@"
+
Policy
+
+"@)
+
+ #region ChangeTrackingCustomPolicy
+ if ($customPolicyCreatedOrUpdatedCount -gt 0) {
+ $tfCount = $customPolicyCreatedOrUpdatedCount
+ $htmlTableId = 'TenantSummary_ChangeTrackingCustomPolicy'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$customPolicyCreatedOrUpdatedCount Created/Updated custom Policy definitions (Mg: C:$($customPolicyCreatedMgCount), U:$($customPolicyUpdatedMgCount); Sub: C:$($customPolicyCreatedSubCount), U:$($customPolicyUpdatedSubCount))
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+Scope Id
+Policy DisplayName
+PolicyId
+Category
+Effect
+Role definitions
+Unique assignments
+Used in PolicySets
+Created/Updated
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYChangeTrackingCustomPolicy = $null
+ $htmlSUMMARYChangeTrackingCustomPolicy = foreach ($entry in $customPolicyCreatedOrUpdated | Sort-Object -Property CreatedOn, UpdatedOn -Descending) {
+ $createdOnGt = $false
+ if ($entry.CreatedOn -ne '') {
+ $createdOn = ($entry.CreatedOn)
+ if ([datetime]($entry.CreatedOn) -gt $xdaysAgo) {
+ $createdOnGt = $true
+ }
+ }
+ else {
+ $createdOn = ''
+ }
+
+ $updatedOnGt = $false
+ if ($entry.updatedOn -ne '') {
+ $updatedOn = ($entry.UpdatedOn)
+ if ([datetime]($entry.UpdatedOn) -gt $xdaysAgo) {
+ $updatedOnGt = $true
+ }
+ $updatedOnGt = $true
+ }
+ else {
+ $updatedOn = ''
+ }
+
+ $createOnUpdatedOn = $null
+ if ($createdOnGt) {
+ $createOnUpdatedOn = 'Created'
+ }
+ if ($updatedOnGt) {
+ $createOnUpdatedOn = 'Updated'
+ }
+ if ($createdOnGt -and $updatedOnGt) {
+ $createOnUpdatedOn = 'Created&Updated'
+ }
+
+ if ($entry.UsedInPolicySetsCount -gt 0) {
+ $customPolicyUsedInPolicySets = "$($entry.UsedInPolicySetsCount) ($($entry.UsedInPolicySets))"
+ }
+ else {
+ $customPolicyUsedInPolicySets = $($entry.UsedInPolicySetsCount)
+ }
+
+ @"
+
+$($entry.Scope)
+$($entry.ScopeId)
+$($entry.PolicyDisplayName -replace '<', '<' -replace '>', '>')
+$($entry.PolicyDefinitionId -replace '<', '<' -replace '>', '>')
+$($entry.PolicyCategory -replace '<', '<' -replace '>', '>')
+$($entry.PolicyEffect)
+$($entry.RoleDefinitions)
+$($entry.UniqueAssignments -replace '<', '<' -replace '>', '>')
+$($customPolicyUsedInPolicySets)
+$createOnUpdatedOn
+$($entry.CreatedOn)
+$($entry.CreatedBy)
+$($entry.UpdatedOn)
+$($entry.UpdatedBy)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomPolicy)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$customPolicyCreatedOrUpdatedCount Created/Updated custom Policy definitions
+"@)
+ }
+ #endregion ChangeTrackingCustomPolicy
+
+ #region ChangeTrackingCustomPolicySet
+ if ($customPolicySetCreatedOrUpdatedCount -gt 0) {
+ $tfCount = $customPolicySetCreatedOrUpdatedCount
+ $htmlTableId = 'TenantSummary_ChangeTrackingCustomPolicySet'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$customPolicySetCreatedOrUpdatedCount Created/Updated custom PolicySet definitions (Mg: C:$($customPolicySetCreatedMgCount), U:$($customPolicySetUpdatedMgCount); Sub: C:$($customPolicySetCreatedSubCount), U:$($customPolicySetUpdatedSubCount))
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+ScopeId
+PolicySet DisplayName
+PolicySetId
+Category
+Unique assignments
+Policies used in PolicySet
+Created/Updated
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYChangeTrackingCustomPolicySet = $null
+ $htmlSUMMARYChangeTrackingCustomPolicySet = foreach ($entry in $customPolicySetCreatedOrUpdated | Sort-Object -Property CreatedOn, UpdatedOn -Descending) {
+ $createdOnGt = $false
+ if ($entry.CreatedOn -ne '') {
+ $createdOn = ($entry.CreatedOn)
+ if ([datetime]($entry.CreatedOn) -gt $xdaysAgo) {
+ $createdOnGt = $true
+ }
+ }
+ else {
+ $createdOn = ''
+ }
+
+ $updatedOnGt = $false
+ if ($entry.updatedOn -ne '') {
+ $updatedOn = ($entry.UpdatedOn)
+ if ([datetime]($entry.UpdatedOn) -gt $xdaysAgo) {
+ $updatedOnGt = $true
+ }
+ $updatedOnGt = $true
+ }
+ else {
+ $updatedOn = ''
+ }
+
+ $createOnUpdatedOn = $null
+ if ($createdOnGt) {
+ $createOnUpdatedOn = 'Created'
+ }
+ if ($updatedOnGt) {
+ $createOnUpdatedOn = 'Updated'
+ }
+ if ($createdOnGt -and $updatedOnGt) {
+ $createOnUpdatedOn = 'Created&Updated'
+ }
+
+ @"
+
+$($entry.Scope)
+$($entry.ScopeId)
+$($entry.PolicySetDisplayName -replace '<', '<' -replace '>', '>')
+$($entry.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')
+$($entry.PolicySetCategory -replace '<', '<' -replace '>', '>')
+$($entry.UniqueAssignments -replace '<', '<' -replace '>', '>')
+$($entry.PoliciesUsed)
+$createOnUpdatedOn
+$($entry.CreatedOn)
+$($entry.CreatedBy)
+$($entry.UpdatedOn)
+$($entry.UpdatedBy)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomPolicySet)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$customPolicySetCreatedOrUpdatedCount Created/Updated custom PolicySet definitions
+"@)
+ }
+ #endregion ChangeTrackingCustomPolicySet
+
+ #region ChangeTrackingPolicyAssignments
+ if ($policyAssignmentsCreatedOrUpdatedCount -gt 0) {
+ $tfCount = $policyAssignmentsCreatedOrUpdatedCount
+ $htmlTableId = 'TenantSummary_ChangeTrackingPolicyAssignments'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$policyAssignmentsCreatedOrUpdatedCount Created/Updated Policy assignments ($policyAssignmentSummaryCt)
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+Management Group Id
+Management Group Name
+SubscriptionId
+Subscription Name
+Inheritance
+ScopeExcluded
+Exemption applies
+Policy/Set DisplayName
+Policy/Set Description
+Policy/SetId
+Policy/Set
+Type
+Category
+Effect
+Parameters
+Enforcement
+NonCompliance Message
+"@)
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ [void]$htmlTenantSummary.AppendLine(@'
+Policies NonCmplnt
+Policies Compliant
+Resources NonCmplnt
+Resources Compliant
+Resources Conflicting
+'@)
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@"
+Role/Assignment $noteOrNot
+Assignment DisplayName
+Assignment Description
+AssignmentId
+Created/Updated
+AssignedBy
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYChangeTrackingPolicyAssignments = $null
+ $htmlSUMMARYChangeTrackingPolicyAssignments = foreach ($policyAssignment in $policyAssignmentsCreatedOrUpdated | Sort-Object -Property CreatedOn, UpdatedOn -Descending) {
+ $createdOnGt = $false
+ if ($policyAssignment.CreatedOn -ne '') {
+ $createdOn = ($policyAssignment.CreatedOn)
+ if ([datetime]($policyAssignment.CreatedOn) -gt $xdaysAgo) {
+ $createdOnGt = $true
+ }
+ }
+ else {
+ $createdOn = ''
+ }
+
+ $updatedOnGt = $false
+ if ($policyAssignment.updatedOn -ne '') {
+ $updatedOn = ($policyAssignment.UpdatedOn)
+ if ([datetime]($policyAssignment.UpdatedOn) -gt $xdaysAgo) {
+ $updatedOnGt = $true
+ }
+ $updatedOnGt = $true
+ }
+ else {
+ $updatedOn = ''
+ }
+
+ $createOnUpdatedOn = $null
+ if ($createdOnGt) {
+ $createOnUpdatedOn = 'Created'
+ }
+ if ($updatedOnGt) {
+ $createOnUpdatedOn = 'Updated'
+ }
+ if ($createdOnGt -and $updatedOnGt) {
+ $createOnUpdatedOn = 'Created&Updated'
+ }
+
+ if ($policyAssignment.PolicyType -eq 'Custom') {
+ $policyName = ($policyAssignment.PolicyName -replace '<', '<' -replace '>', '>')
+ }
+ else {
+ $policyName = $policyAssignment.PolicyName
+ }
+
+ @"
+
+$($policyAssignment.mgOrSubOrRG)
+$($policyAssignment.MgId)
+$($policyAssignment.MgName -replace '<', '<' -replace '>', '>')
+$($policyAssignment.SubscriptionId)
+$($policyAssignment.SubscriptionName)
+$($policyAssignment.Inheritance)
+$($policyAssignment.ExcludedScope)
+$($policyAssignment.ExemptionScope)
+$($policyName)
+$($policyAssignment.PolicyDescription -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyId -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyVariant)
+$($policyAssignment.PolicyType)
+$($policyAssignment.PolicyCategory -replace '<', '<' -replace '>', '>')
+$($policyAssignment.Effect)
+$($policyAssignment.PolicyAssignmentParameters)
+$($policyAssignment.PolicyAssignmentEnforcementMode)
+$($policyAssignment.PolicyAssignmentNonComplianceMessages)
+"@
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ @"
+$($policyAssignment.NonCompliantPolicies)
+$($policyAssignment.CompliantPolicies)
+$($policyAssignment.NonCompliantResources)
+$($policyAssignment.CompliantResources)
+$($policyAssignment.ConflictingResources)
+"@
+ }
+
+ @"
+$($policyAssignment.RelatedRoleAssignments)
+$($policyAssignment.PolicyAssignmentDisplayName -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyAssignmentDescription -replace '<', '<' -replace '>', '>')
+$($policyAssignment.PolicyAssignmentId -replace '<', '<' -replace '>', '>')
+$createOnUpdatedOn
+$($policyAssignment.AssignedBy)
+$($policyAssignment.CreatedOn)
+$($policyAssignment.CreatedBy)
+$($policyAssignment.UpdatedOn)
+$($policyAssignment.UpdatedBy)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingPolicyAssignments)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$policyAssignmentsCreatedOrUpdatedCount Created/Updated Policy assignments
+"@)
+ }
+ #endregion ChangeTrackingPolicyAssignments
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+
+ #endregion ctpolicy
+
+ #region ctrbac
+ [void]$htmlTenantSummary.AppendLine(@"
+
RBAC
+
+"@)
+
+ #region ChangeTrackingCustomRoles
+ if ($customRoleDefinitionsCreatedOrUpdatedCount -gt 0) {
+ $tfCount = $customRoleDefinitionsCreatedOrUpdatedCount
+ $htmlTableId = 'TenantSummary_ChangeTrackingCustomRoles'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$customRoleDefinitionsCreatedOrUpdatedCount Created/Updated custom Role definitions (Created: $customRoleDefinitionsCreatedCount; Updated: $customRoleDefinitionsUpdatedCount)
+
+
Download CSV
semicolon |
comma
+
+
+
+Role Name
+RoleId
+Assignable Scopes
+Data
+Created/Updated
+CreatedOn
+CreatedBy
+UpdatedOn
+UpdatedBy
+
+
+
+"@)
+ $htmlSUMMARYChangeTrackingCustomRoles = $null
+ $htmlSUMMARYChangeTrackingCustomRoles = foreach ($entry in $customRoleDefinitionsCreatedOrUpdated | Sort-Object @{Expression = { $_.Json.properties.createdOn } }, @{Expression = { $_.Json.properties.updatedOn } } -Descending) {
+ $createdBy = $entry.Json.properties.createdBy
+ if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) {
+ $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details
+ }
+
+ $createdOn = $entry.Json.properties.createdOn
+ $createdOnFormated = $createdOn
+ $createdOnUpdatedOn = 'Created'
+
+ $updatedOn = $entry.Json.properties.updatedOn
+ if ($updatedOn -eq $createdOn) {
+ $updatedOnFormated = ''
+ $updatedByRemoveNoiseOrNot = ''
+ }
+ else {
+ if ($createdOn -gt $xdaysAgo) {
+ $createdOnUpdatedOn = 'Created&Updated'
+ }
+ else {
+ $createdOnUpdatedOn = 'Updated'
+ }
+ $updatedOnFormated = $updatedOn
+ $updatedByRemoveNoiseOrNot = $entry.Json.properties.updatedBy
+ if ($htIdentitiesWithRoleAssignmentsUnique.($updatedByRemoveNoiseOrNot)) {
+ $updatedByRemoveNoiseOrNot = $htIdentitiesWithRoleAssignmentsUnique.($updatedByRemoveNoiseOrNot).details
+ }
+ }
+
+ @"
+
+$($entry.Name -replace '<', '<' -replace '>', '>')
+$($entry.Id)
+$(($entry.AssignableScopes | Measure-Object).count) ($($entry.AssignableScopes -join "$CsvDelimiterOpposite "))
+$($roleManageData)
+$createdOnUpdatedOn
+$createdOnFormated
+$createdBy
+$updatedOnFormated
+$updatedByRemoveNoiseOrNot
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomRoles)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$customRoleDefinitionsCreatedOrUpdatedCount Created/Updated custom Role definitions
+"@)
+ }
+ #endregion ChangeTrackingCustomRoles
+
+ #region ChangeTrackingRoleAssignments
+ if ($roleAssignmentsCreatedCount -gt 0) {
+ $tfCount = $roleAssignmentsCreatedCount
+ $htmlTableId = 'TenantSummary_ChangeTrackingRoleAssignments'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$roleAssignmentsCreatedCount Created Role assignments $rbacAssignmentSummaryCt (impacted identities: $roleAssignmentsCreatedImpactedIdentitiesCount)
+
+
Download CSV
semicolon |
comma
+
+
+
+Scope
+Role
+Role Id
+Role Type
+Data
+Identity Displayname
+Identity SignInName
+Identity ObjectId
+Identity Type
+Applicability
+Applies through membership
+Group Details
+PIM
+PIM assignment type
+PIM start
+PIM end
+Role AssignmentId
+Related Policy Assignment $noteOrNot
+CreatedOn
+CreatedBy
+
+
+
+"@)
+ $htmlSUMMARYChangeTrackingRoleAssignments = $null
+ $htmlSUMMARYChangeTrackingRoleAssignments = [System.Text.StringBuilder]::new()
+ foreach ($entry in $roleAssignmentsCreated | Sort-Object -Property CreatedOn -Descending) {
+ if ($entry.RoleType -eq 'Custom') {
+ $roleName = ($entry.Role -replace '<', '<' -replace '>', '>')
+ }
+ else {
+ $roleName = $entry.Role
+ }
+ [void]$htmlSUMMARYChangeTrackingRoleAssignments.AppendFormat(
+ @'
+
+{0}
+{1}
+{2}
+{3}
+{4}
+{5}
+{6}
+{7}
+{8}
+{9}
+{10}
+{11}
+{12}
+{13}
+{14}
+{15}
+{16}
+{17}
+{18}
+{19}
+
+'@, $entry.ScopeTenOrMgOrSubOrRGOrRes,
+ $roleName,
+ $entry.RoleId,
+ $entry.RoleType,
+ $entry.RoleDataRelated,
+ $entry.ObjectDisplayName,
+ $entry.ObjectSignInName,
+ $entry.ObjectId,
+ $entry.ObjectType,
+ $entry.AssignmentType,
+ $entry.AssignmentInheritFrom,
+ $entry.GroupMembersCount,
+ $entry.RoleAssignmentPIMRelated,
+ $entry.RoleAssignmentPIMAssignmentType,
+ $entry.RoleAssignmentPIMAssignmentSlotStart,
+ $entry.RoleAssignmentPIMAssignmentSlotEnd,
+ $entry.RoleAssignmentId,
+ #($entry.RbacRelatedPolicyAssignment -replace '<', '<' -replace '>', '>'),
+ $entry.RbacRelatedPolicyAssignment,
+ $entry.CreatedOn,
+ $entry.CreatedBy
+ )
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingRoleAssignments)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$customRoleDefinitionsCreatedOrUpdatedCount Created/Updated custom Role definitions
+"@)
+ }
+ #endregion ChangeTrackingRoleAssignments
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+
+ #endregion ctrbac
+
+ if ($azAPICallConf['htParameters'].NoResources -eq $false) {
+ #region ctresources
+ [void]$htmlTenantSummary.AppendLine(@"
+
Resources
+
+"@)
+
+ #region ChangeTrackingResources
+ if ($resourcesCreatedOrChangedCount -gt 0) {
+ $tfCount = $resourcesCreatedOrChangedGroupedCount
+ $htmlTableId = 'TenantSummary_ChangeTrackingResources'
+ [void]$htmlTenantSummary.AppendLine(@"
+
$resourcesCreatedOrChangedCount Created/Changed Resources ($resourcesCreatedOrChangedGroupedCount ResourceTypes) (Created&Changed: $resourcesCreatedAndChangedCount; Created: $resourcesCreatedCount; Changed: $resourcesChangedCount)
+
+
Download CSV
semicolon |
comma
+
+
+
+ResourceType
+Resource Count
+Created&Changed
+Created&Changed Subs
+Created
+Created Subs
+Changed
+Changed Subs
+
+
+
+"@)
+ $htmlSUMMARYChangeTrackingResources = $null
+ $htmlSUMMARYChangeTrackingResources = foreach ($entry in $resourcesCreatedOrChangedGrouped) {
+ $createdAndChanged = $entry.group.where( { $_.createdTime -gt $xdaysAgo -and $_.changedTime -gt $xdaysAgo })
+ $createdAndChangedCount = $createdAndChanged.Count
+ $createdAndChangedInSubscriptionsCount = ($createdAndChanged | Group-Object -Property subscriptionId | Measure-Object).Count
+
+ $created = $entry.group.where( { $_.createdTime -gt $xdaysAgo })
+ $createdCount = $created.Count
+ $createdInSubscriptionsCount = ($created | Group-Object -Property subscriptionId | Measure-Object).Count
+
+ $changed = $entry.group.where( { $_.changedTime -gt $xdaysAgo })
+ $changedCount = $changed.Count
+ $changedInSubscriptionsCount = ($changed | Group-Object -Property subscriptionId | Measure-Object).Count
+
+ @"
+
+$($entry.Name)
+$($entry.Count)
+$($createdAndChangedCount)
+$($createdAndChangedInSubscriptionsCount)
+$($createdCount)
+$($createdInSubscriptionsCount)
+$($changedCount)
+$($changedInSubscriptionsCount)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingResources)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
$resourcesCreatedOrChangedCount Created/Changed Resources
+"@)
+ }
+ #endregion ChangeTrackingResources
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+
+ #endregion ctresources
+ }
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+
+ $endChangeTracking = Get-Date
+ Write-Host " ChangeTracking duration: $((New-TimeSpan -Start $startChangeTracking -End $endChangeTracking).TotalMinutes) minutes ($((New-TimeSpan -Start $startChangeTracking -End $endChangeTracking).TotalSeconds) seconds)"
+ #endregion tenantSummaryChangeTracking
+
+ showMemoryUsage
+
+ #region tenantSummaryNaming
+ [void]$htmlTenantSummary.AppendLine(@'
+
+
+'@)
+
+ $startSUMMARYNaming = Get-Date
+ Write-Host ' processing TenantSummary Findings'
+
+
+ $namingPolicyCount = $htNamingValidation.Policy.values.count
+ if ($namingPolicyCount -gt 0) {
+ $tfCount = $namingPolicyCount
+ $htmlTableId = 'TenantSummary_NamingPolicy'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Policy $($namingPolicyCount) Naming findings
+
+
Download CSV
semicolon |
comma
+
+
+
+Id
+Name
+Name Invalid chars
+DisplayName
+DisplayName Invalid chars
+
+
+
+"@)
+ $htmlSUMMARYNamingPolicy = $null
+ $cnter = 0
+ $htmlSUMMARYNamingPolicy = foreach ($key in $htNamingValidation.Policy.Keys | Sort-Object) {
+ $id = $key -replace '<', '<' -replace '>', '>'
+ if ($htNamingValidation.Policy.($key).name) {
+ $name = $htNamingValidation.Policy.($key).name -replace '<', '<' -replace '>', '>'
+ $nameInvalidChars = $htNamingValidation.Policy.($key).nameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $name = ''
+ $nameInvalidChars = ''
+ }
+
+ if ($htNamingValidation.Policy.($key).displayName) {
+ $displayName = $htNamingValidation.Policy.($key).displayName -replace '<', '<' -replace '>', '>'
+ $displayNameInvalidChars = $htNamingValidation.Policy.($key).displayNameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $displayName = ''
+ $displayNameInvalidChars = ''
+ }
+
+
+ @"
+
+$($id)
+$($name)
+$($nameInvalidChars)
+$($displayName)
+$($displayNameInvalidChars)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicy)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Policy $($namingPolicyCount) Naming findings
+"@)
+ }
+
+ $namingPolicySetCount = $htNamingValidation.PolicySet.values.count
+ if ($namingPolicySetCount -gt 0) {
+ $tfCount = $namingPolicySetCount
+ $htmlTableId = 'TenantSummary_NamingPolicySet'
+ [void]$htmlTenantSummary.AppendLine(@"
+
PolicySet $($namingPolicySetCount) Naming findings
+
+
Download CSV
semicolon |
comma
+
+
+
+Id
+Name
+Name Invalid chars
+DisplayName
+DisplayName Invalid chars
+
+
+
+"@)
+ $htmlSUMMARYNamingPolicySet = $null
+ $cnter = 0
+ $htmlSUMMARYNamingPolicySet = foreach ($key in $htNamingValidation.PolicySet.Keys | Sort-Object) {
+ $id = $key -replace '<', '<' -replace '>', '>'
+ if ($htNamingValidation.PolicySet.($key).name) {
+ $name = $htNamingValidation.PolicySet.($key).name -replace '<', '<' -replace '>', '>'
+ $nameInvalidChars = $htNamingValidation.PolicySet.($key).nameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $name = ''
+ $nameInvalidChars = ''
+ }
+
+ if ($htNamingValidation.PolicySet.($key).displayName) {
+ $displayName = $htNamingValidation.PolicySet.($key).displayName -replace '<', '<' -replace '>', '>'
+ $displayNameInvalidChars = $htNamingValidation.PolicySet.($key).displayNameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $displayName = ''
+ $displayNameInvalidChars = ''
+ }
+
+
+ @"
+
+$($id)
+$($name)
+$($nameInvalidChars)
+$($displayName)
+$($displayNameInvalidChars)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicySet)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
PolicySet $($namingPolicySetCount) Naming findings
+"@)
+ }
+
+ $namingPolicyAssignmentCount = $htNamingValidation.PolicyAssignment.values.count
+ if ($namingPolicyAssignmentCount -gt 0) {
+ $tfCount = $namingPolicyAssignmentCount
+ $htmlTableId = 'TenantSummary_NamingPolicyAssignment'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Policy assignment $($namingPolicyAssignmentCount) Naming findings
+
+
Download CSV
semicolon |
comma
+
+
+
+Id
+Name
+Name Invalid chars
+DisplayName
+DisplayName Invalid chars
+
+
+
+"@)
+ $htmlSUMMARYNamingPolicyAssignment = $null
+ $cnter = 0
+ $htmlSUMMARYNamingPolicyAssignment = foreach ($key in $htNamingValidation.PolicyAssignment.Keys | Sort-Object) {
+ $id = $key -replace '<', '<' -replace '>', '>'
+ if ($htNamingValidation.PolicyAssignment.($key).name) {
+ $name = $htNamingValidation.PolicyAssignment.($key).name -replace '<', '<' -replace '>', '>'
+ $nameInvalidChars = $htNamingValidation.PolicyAssignment.($key).nameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $name = ''
+ $nameInvalidChars = ''
+ }
+
+ if ($htNamingValidation.PolicyAssignment.($key).displayName) {
+ $displayName = $htNamingValidation.PolicyAssignment.($key).displayName -replace '<', '<' -replace '>', '>'
+ $displayNameInvalidChars = $htNamingValidation.PolicyAssignment.($key).displayNameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $displayName = ''
+ $displayNameInvalidChars = ''
+ }
+
+
+ @"
+
+$($id)
+$($name)
+$($nameInvalidChars)
+$($displayName)
+$($displayNameInvalidChars)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicyAssignment)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Policy assignment $($namingPolicyAssignmentCount) Naming findings
+"@)
+ }
+
+ $namingManagementGroupCount = $htNamingValidation.ManagementGroup.values.count
+ if ($namingManagementGroupCount -gt 0) {
+ $tfCount = $namingManagementGroupCount
+ $htmlTableId = 'TenantSummary_NamingManagementGroup'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Management Group $($namingManagementGroupCount) Naming findings
+
+
Download CSV
semicolon |
comma
+
+
+
+Id
+Name
+Name Invalid chars
+
+
+
+"@)
+ $htmlSUMMARYNamingManagementGroup = $null
+ $cnter = 0
+ $htmlSUMMARYNamingManagementGroup = foreach ($key in $htNamingValidation.ManagementGroup.Keys | Sort-Object) {
+ $id = $key -replace '<', '<' -replace '>', '>'
+ if ($htNamingValidation.ManagementGroup.($key).name) {
+ $name = $htNamingValidation.ManagementGroup.($key).name -replace '<', '<' -replace '>', '>'
+ $nameInvalidChars = $htNamingValidation.ManagementGroup.($key).nameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $name = ''
+ $nameInvalidChars = ''
+ }
+
+ @"
+
+$($id)
+$($name)
+$($nameInvalidChars)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingManagementGroup)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Management Group $($namingManagementGroupCount) Naming findings
+"@)
+ }
+
+
+ $namingSubscriptionCount = $htNamingValidation.Subscription.values.count
+ if ($namingSubscriptionCount -gt 0) {
+ $tfCount = $namingSubscriptionCount
+ $htmlTableId = 'TenantSummary_NamingSubscription'
+ [void]$htmlTenantSummary.AppendLine(@"
+
Subscription $($namingSubscriptionCount) Naming findings
+
+
Download CSV
semicolon |
comma
+
+
+
+Id
+DisplayName
+DisplayName Invalid chars
+
+
+
+"@)
+ $htmlSUMMARYNamingSubscription = $null
+ $htmlSUMMARYNamingSubscription = foreach ($key in $htNamingValidation.Subscription.Keys | Sort-Object) {
+
+ if ($htNamingValidation.Subscription.($key).displayName) {
+ $displayName = $htNamingValidation.Subscription.($key).displayName -replace '<', '<' -replace '>', '>'
+ $displayNameInvalidChars = $htNamingValidation.Subscription.($key).displayNameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $displayName = ''
+ $displayNameInvalidChars = ''
+ }
+
+ @"
+
+$($key)
+$($displayName)
+$($displayNameInvalidChars)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingSubscription)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
Subscription $($namingSubscriptionCount) Naming findings
+"@)
+ }
+
+
+ $namingRoleCount = $htNamingValidation.Role.values.count
+ if ($namingRoleCount -gt 0) {
+ $tfCount = $namingRoleCount
+ $htmlTableId = 'TenantSummary_NamingRole'
+ [void]$htmlTenantSummary.AppendLine(@"
+
RBAC $($namingRoleCount) Naming findings
+
+
Download CSV
semicolon |
comma
+
+
+
+Id
+Name
+Name Invalid chars
+
+
+
+"@)
+ $htmlSUMMARYNamingRole = $null
+ $htmlSUMMARYNamingRole = foreach ($key in $htNamingValidation.Role.Keys | Sort-Object) {
+
+ if ($htNamingValidation.Role.($key).roleName) {
+ $roleName = $htNamingValidation.Role.($key).roleName -replace '<', '<' -replace '>', '>'
+ $roleNameInvalidChars = $htNamingValidation.Role.($key).roleNameInvalidChars -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $roleName = ''
+ $roleNameInvalidChars = ''
+ }
+
+ @"
+
+$($key)
+$($roleName)
+$($roleNameInvalidChars)
+
+"@
+ }
+ [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingRole)
+ [void]$htmlTenantSummary.AppendLine(@"
+
+
+
+
+
+"@)
+ }
+ else {
+ [void]$htmlTenantSummary.AppendLine(@"
+
RBAC $($namingRoleCount) Naming Findings
+"@)
+ }
+
+ $endSUMMARYNaming = Get-Date
+ Write-Host " SUMMARYMGs duration: $((New-TimeSpan -Start $startSUMMARYNaming -End $endSUMMARYNaming).TotalMinutes) minutes ($((New-TimeSpan -Start $startSUMMARYNaming -End $endSUMMARYNaming).TotalSeconds) seconds)"
+
+ [void]$htmlTenantSummary.AppendLine(@'
+
+'@)
+ #endregion tenantSummaryNaming
+
+ $script:html += $htmlTenantSummary
+ $htmlTenantSummary = $null
+ $script:html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force
+ $script:html = $null
+}
+function removeInvalidFileNameChars {
+ param(
+ [Parameter(Mandatory = $true,
+ Position = 0,
+ ValueFromPipeline = $true,
+ ValueFromPipelineByPropertyName = $true)]
+ [String]$Name
+ )
+ if ($Name -like '`[Deprecated`]:*') {
+ $Name = $Name -replace '\[Deprecated\]\:', '[Deprecated]'
+ }
+ if ($Name -like '`[Preview`]:*') {
+ $Name = $Name -replace '\[Preview\]\:', '[Preview]'
+ }
+ if ($Name -like '`[ASC Private Preview`]:*') {
+ $Name = $Name -replace '\[ASC Private Preview\]\:', '[ASC Private Preview]'
+ }
+ return ($Name -replace ':', '_' -replace '/', '_' -replace '\\', '_' -replace '<', '_' -replace '>', '_' -replace '\*', '_' -replace '\?', '_' -replace '\|', '_' -replace '"', '_')
+}
+function ResolveObjectIds {
+ [CmdletBinding()]Param(
+ [object]
+ $objectIds,
+
+ [switch]
+ $showActivity
+ )
+
+ $arrayObjectIdsToCheck = @()
+ $arrayObjectIdsToCheck = foreach ($objectToCheckIfAlreadyResolved in $objectIds) {
+ if (-not $htPrincipals.($objectToCheckIfAlreadyResolved)) {
+ $objectToCheckIfAlreadyResolved
+ }
+ else {
+ #Write-Host "$objectToCheckIfAlreadyResolved already resolved"
+ }
+ }
+
+ if ($arrayObjectIdsToCheck.Count -gt 0) {
+
+ $counterBatch = [PSCustomObject] @{ Value = 0 }
+ $batchSize = 1000
+ $ObjectBatch = $arrayObjectIdsToCheck | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) }
+ $ObjectBatchCount = ($ObjectBatch | Measure-Object).Count
+ $batchCnt = 0
+
+ foreach ($batch in $ObjectBatch) {
+ $batchCnt++
+ $objectsToProcess = '"{0}"' -f ($batch.Group.where({ testGuid $_ }) -join '","')
+ $currentTask = " Resolving ObjectIds - Batch #$batchCnt/$($ObjectBatchCount) ($(($batch.Group).Count))"
+ if ($showActivity) {
+ Write-Host $currentTask
+ }
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/directoryObjects/getByIds"
+ $method = 'POST'
+ $body = @"
+ {
+ "ids":[$($objectsToProcess)]
+ }
+"@
+ $resolveObjectIds = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask
+
+ foreach ($identity in $resolveObjectIds) {
+ if (-not $htPrincipals.($identity.id)) {
+ $arrayIdentityObject = [System.Collections.ArrayList]@()
+ if ($identity.'@odata.type' -eq '#microsoft.graph.user') {
+ if ($identity.userType -eq 'Guest') {
+ $script:htUserTypesGuest.($identity.id) = @{}
+ $script:htUserTypesGuest.($identity.id).userType = 'Guest'
+ }
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'User'
+ userType = $identity.userType
+ id = $identity.id
+ displayName = $identity.displayName
+ signInName = $identity.userPrincipalName
+ })
+ }
+ if ($identity.'@odata.type' -eq '#microsoft.graph.group') {
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'Group'
+ id = $identity.id
+ displayName = $identity.displayName
+ })
+ }
+ if ($identity.'@odata.type' -eq '#microsoft.graph.servicePrincipal') {
+ if ($identity.servicePrincipalType -eq 'Application') {
+ if ($identity.appOwnerOrganizationId -eq $azAPICallConf['checkContext'].Tenant.Id) {
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'ServicePrincipal'
+ spTypeConcatinated = 'SP APP INT'
+ servicePrincipalType = $identity.servicePrincipalType
+ id = $identity.id
+ appid = $identity.appId
+ displayName = $identity.displayName
+ appOwnerOrganizationId = $identity.appOwnerOrganizationId
+ alternativeNames = $identity.alternativeNames
+ })
+ }
+ else {
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'ServicePrincipal'
+ spTypeConcatinated = 'SP APP EXT'
+ servicePrincipalType = $identity.servicePrincipalType
+ id = $identity.id
+ appid = $identity.appId
+ displayName = $identity.displayName
+ appOwnerOrganizationId = $identity.appOwnerOrganizationId
+ alternativeNames = $identity.alternativeNames
+ })
+ }
+ }
+ elseif ($identity.servicePrincipalType -eq 'ManagedIdentity') {
+ $miType = 'unknown'
+ if ($identity.alternativeNames) {
+ foreach ($altName in $identity.alternativeNames) {
+ if ($altName -like 'isExplicit=*') {
+ $splitAltName = $altName.split('=')
+ if ($splitAltName[1] -eq 'true') {
+ $miType = 'Usr'
+ }
+ if ($splitAltName[1] -eq 'false') {
+ $miType = 'Sys'
+ }
+ }
+ }
+ }
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'ServicePrincipal'
+ spTypeConcatinated = "SP MI $miType"
+ servicePrincipalType = $identity.servicePrincipalType
+ id = $identity.id
+ appid = $identity.appId
+ displayName = $identity.displayName
+ appOwnerOrganizationId = $identity.appOwnerOrganizationId
+ alternativeNames = $identity.alternativeNames
+ })
+ }
+ else {
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'servicePrincipal'
+ spTypeConcatinated = "SP $($identity.servicePrincipalType)"
+ servicePrincipalType = $identity.servicePrincipalType
+ id = $identity.id
+ appid = $identity.appId
+ displayName = $identity.displayName
+ appOwnerOrganizationId = $identity.appOwnerOrganizationId
+ alternativeNames = $identity.alternativeNames
+ })
+ }
+ if (-not $htServicePrincipals.($identity.id)) {
+ $script:htServicePrincipals.($identity.id) = @{}
+ $script:htServicePrincipals.($identity.id) = $arrayIdentityObject
+ }
+ }
+ if (-not $htPrincipals.($identity.id)) {
+ $script:htPrincipals.($identity.id) = $arrayIdentityObject
+ }
+ }
+ }
+ if ($batch.Group.Count -ne $resolveObjectIds.Count) {
+ foreach ($objectId in $batch.Group) {
+ if ($resolveObjectIds.id -notcontains $objectId) {
+ if (-not $htPrincipals.($objectId)) {
+ $arrayIdentityObject = [System.Collections.ArrayList]@()
+ $null = $arrayIdentityObject.Add([PSCustomObject]@{
+ type = 'Unknown'
+ id = $objectId
+ })
+ $script:htPrincipals.($objectId) = $arrayIdentityObject
+ }
+ else {
+ #Write-Host "$($objectId) was already collected"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+function runInfo {
+ #region RunInfo
+ Write-Host 'Run Info:'
+ if ($HierarchyMapOnly) {
+ Write-Host ' Creating HierarchyMap only' -ForegroundColor Green
+ }
+ else {
+ $script:paramsUsed = $Null
+ $startTimeUTC = ((Get-Date).ToUniversalTime()).ToString('dd-MMM-yyyy HH:mm:ss')
+ $script:paramsUsed += "Date: $startTimeUTC (UTC); Version: $ProductVersion
"
+
+ if ($azAPICallConf['htParameters'].accountType -eq 'ServicePrincipal') {
+ $script:paramsUsed += "ExecutedBy: $($azAPICallConf['checkContext'].Account.Id) (App/ClientId) ($($azAPICallConf['htParameters'].accountType))
"
+ }
+ elseif ($azAPICallConf['htParameters'].accountType -eq 'ManagedService') {
+ $script:paramsUsed += "ExecutedBy: $($azAPICallConf['checkContext'].Account.Id) (Id) ($($azAPICallConf['htParameters'].accountType))
"
+ }
+ elseif ($azAPICallConf['htParameters'].accountType -eq 'ClientAssertion') {
+ $script:paramsUsed += "ExecutedBy: $($azAPICallConf['checkContext'].Account.Id) (App/ClientId) ($($azAPICallConf['htParameters'].accountType))
"
+ }
+ else {
+ $script:paramsUsed += "ExecutedBy: $($azAPICallConf['checkContext'].Account.Id) ($($azAPICallConf['htParameters'].accountType), $($azAPICallConf['htParameters'].userType))
"
+ }
+ #$script:paramsUsed += "ManagementGroupId: $($ManagementGroupId)
"
+ $script:paramsUsed += 'HierarchyMapOnly: false
'
+ Write-Host " Creating HierarchyMap, TenantSummary, DefinitionInsights and ScopeInsights - use parameter: '-HierarchyMapOnly' to only create the HierarchyMap" -ForegroundColor Yellow
+
+ if ($azAPICallConf['htParameters'].ManagementGroupsOnly) {
+ Write-Host " Management Groups only = $($azAPICallConf['htParameters'].ManagementGroupsOnly)" -ForegroundColor Green
+ }
+ else {
+ Write-Host " Management Groups only = $($azAPICallConf['htParameters'].ManagementGroupsOnly) - use parameter -ManagementGroupsOnly to only collect data for Management Groups" -ForegroundColor Yellow
+ }
+
+ if (($SubscriptionQuotaIdWhitelist).count -eq 1 -and $SubscriptionQuotaIdWhitelist[0] -eq 'undefined') {
+ Write-Host " Subscription Whitelist disabled - use parameter: '-SubscriptionQuotaIdWhitelist' to whitelist QuotaIds" -ForegroundColor Yellow
+ $script:paramsUsed += 'SubscriptionQuotaIdWhitelist: false
'
+ }
+ else {
+ Write-Host ' Subscription Whitelist enabled. Azure Governance Visualizer will only process Subscriptions where QuotaId startswith one of the following strings:' -ForegroundColor Green
+ foreach ($quotaIdFromSubscriptionQuotaIdWhitelist in $SubscriptionQuotaIdWhitelist) {
+ Write-Host " - $($quotaIdFromSubscriptionQuotaIdWhitelist)" -ForegroundColor Green
+ }
+ foreach ($whiteListEntry in $SubscriptionQuotaIdWhitelist) {
+ if ($whiteListEntry -eq 'undefined') {
+ Write-Host "When defining the 'SubscriptionQuotaIdWhitelist' make sure to remove the 'undefined' entry from the array :)" -ForegroundColor Red
+ Throw 'Error - Azure Governance Visualizer: check the last console output for details'
+ }
+ }
+ $script:paramsUsed += "SubscriptionQuotaIdWhitelist: $($SubscriptionQuotaIdWhitelist -join ', ')
"
+ }
+
+ if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $true) {
+ Write-Host " Microsoft Defender for Cloud Secure Score disabled (-NoMDfCSecureScore = $($azAPICallConf['htParameters'].NoMDfCSecureScore))" -ForegroundColor Green
+ $script:paramsUsed += 'NoMDfCSecureScore: true
'
+ }
+ else {
+ Write-Host " Microsoft Defender for Cloud Secure Score enabled - use parameter: '-NoMDfCSecureScore' to disable" -ForegroundColor Yellow
+ $script:paramsUsed += 'NoMDfCSecureScore: false
'
+ }
+
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $true) {
+ Write-Host " Scrub Identity information for identityType='User' enabled (-DoNotShowRoleAssignmentsUserData = $($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData))" -ForegroundColor Green
+ $script:paramsUsed += 'DoNotShowRoleAssignmentsUserData: true
'
+ }
+ else {
+ Write-Host " Scrub Identity information for identityType='User' disabled - use parameter: '-DoNotShowRoleAssignmentsUserData' to scrub information such as displayName and signInName (email) for identityType='User'" -ForegroundColor Yellow
+ $script:paramsUsed += 'DoNotShowRoleAssignmentsUserData: false
'
+ }
+
+ if ($LimitCriticalPercentage -eq 80) {
+ Write-Host " ARM Limits warning set to 80% (default) - use parameter: '-LimitCriticalPercentage' to set warning level accordingly" -ForegroundColor Yellow
+ #$script:paramsUsed += "LimitCriticalPercentage: 80% (default)
"
+ }
+ else {
+ Write-Host " ARM Limits warning set to $($LimitCriticalPercentage)% (custom)" -ForegroundColor Green
+ #$script:paramsUsed += "LimitCriticalPercentage: $($LimitCriticalPercentage)%
"
+ }
+
+ if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) {
+ Write-Host " Policy States enabled - use parameter: '-NoPolicyComplianceStates' to disable Policy States" -ForegroundColor Yellow
+ $script:paramsUsed += 'NoPolicyComplianceStates: false
'
+ }
+ else {
+ Write-Host " Policy States disabled (-NoPolicyComplianceStates = $($azAPICallConf['htParameters'].NoPolicyComplianceStates))" -ForegroundColor Green
+ $script:paramsUsed += 'NoPolicyComplianceStates: true
'
+ }
+
+ if (-not $NoResourceDiagnosticsPolicyLifecycle) {
+ Write-Host " Resource Diagnostics Policy Lifecycle recommendations enabled - use parameter: '-NoResourceDiagnosticsPolicyLifecycle' to disable Resource Diagnostics Policy Lifecycle recommendations" -ForegroundColor Yellow
+ $script:paramsUsed += 'NoResourceDiagnosticsPolicyLifecycle: false
'
+ }
+ else {
+ Write-Host " Resource Diagnostics Policy Lifecycle disabled (-NoResourceDiagnosticsPolicyLifecycle = $($NoResourceDiagnosticsPolicyLifecycle))" -ForegroundColor Green
+ $script:paramsUsed += 'NoResourceDiagnosticsPolicyLifecycle: true
'
+ }
+
+ if (-not $NoAADGroupsResolveMembers) {
+ Write-Host " AAD Groups resolve members enabled (honors parameter -DoNotShowRoleAssignmentsUserData) - use parameter: '-NoAADGroupsResolveMembers' to disable resolving AAD Group memberships" -ForegroundColor Yellow
+ $script:paramsUsed += 'NoAADGroupsResolveMembers: false
'
+ if ($AADGroupMembersLimit -eq 500) {
+ Write-Host " AADGroupMembersLimit = $AADGroupMembersLimit" -ForegroundColor Yellow
+ $script:paramsUsed += "AADGroupMembersLimit: $AADGroupMembersLimit
"
+ }
+ else {
+ Write-Host " AADGroupMembersLimit = $AADGroupMembersLimit" -ForegroundColor Green
+ $script:paramsUsed += "AADGroupMembersLimit: $AADGroupMembersLimit
"
+ }
+ }
+ else {
+ Write-Host " AAD Groups resolve members disabled (-NoAADGroupsResolveMembers = $($NoAADGroupsResolveMembers))" -ForegroundColor Green
+ $script:paramsUsed += 'NoAADGroupsResolveMembers: true
'
+ }
+
+ Write-Host " AADServicePrincipalExpiryWarningDays: $AADServicePrincipalExpiryWarningDays" -ForegroundColor Yellow
+ #$script:paramsUsed += "AADServicePrincipalExpiryWarningDays: $AADServicePrincipalExpiryWarningDays
"
+
+ if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
+ if (-not $AzureConsumptionPeriod -is [int]) {
+ Write-Host 'parameter -AzureConsumptionPeriod must be an integer'
+ Throw 'Error - Azure Governance Visualizer: check the last console output for details'
+ }
+ elseif ($AzureConsumptionPeriod -eq 0) {
+ Write-Host 'parameter -AzureConsumptionPeriod must be gt 0'
+ Throw 'Error - Azure Governance Visualizer: check the last console output for details'
+ }
+ else {
+ #$azureConsumptionStartDate = ((Get-Date).AddDays( - ($($AzureConsumptionPeriod)))).ToString("yyyy-MM-dd")
+ #$azureConsumptionEndDate = ((Get-Date).AddDays(-1)).ToString("yyyy-MM-dd")
+
+ if ($AzureConsumptionPeriod -eq 1) {
+ Write-Host " Azure Consumption reporting enabled: $AzureConsumptionPeriod days (default) ($azureConsumptionStartDate - $azureConsumptionEndDate) - use parameter: '-AzureConsumptionPeriod' to define the period (days)" -ForegroundColor Yellow
+ }
+ else {
+ Write-Host " Azure Consumption reporting enabled: $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" -ForegroundColor Green
+ }
+
+ if (-not $NoAzureConsumptionReportExportToCSV) {
+ Write-Host " Azure Consumption report export to CSV enabled - use parameter: '-NoAzureConsumptionReportExportToCSV' to disable" -ForegroundColor Yellow
+ }
+ else {
+ Write-Host " Azure Consumption report export to CSV disabled (-NoAzureConsumptionReportExportToCSV = $($NoAzureConsumptionReportExportToCSV))" -ForegroundColor Green
+ }
+ $script:paramsUsed += "DoAzureConsumption: true ($AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate))
"
+ $script:paramsUsed += "NoAzureConsumptionReportExportToCSV: $NoAzureConsumptionReportExportToCSV
"
+ }
+ }
+ else {
+ Write-Host " Azure Consumption reporting disabled (-DoAzureConsumption = $($azAPICallConf['htParameters'].DoAzureConsumption))" -ForegroundColor Green
+ $script:paramsUsed += 'DoAzureConsumption: false
'
+ }
+
+ if ($NoScopeInsights) {
+ Write-Host " ScopeInsights will not be created (-NoScopeInsights = $($NoScopeInsights))" -ForegroundColor Green
+ $script:paramsUsed += 'NoScopeInsights: true
'
+ }
+ else {
+ Write-Host " ScopeInsights will be created (-NoScopeInsights = $($NoScopeInsights)) Q: Why would you not want to show ScopeInsights? A: In larger tenants ScopeInsights may blow up the html file (up to unusable due to html file size)" -ForegroundColor Yellow
+ $script:paramsUsed += 'NoScopeInsights: false
'
+ }
+
+ if ($NoSingleSubscriptionOutput) {
+ Write-Host " No single Subscription output will not be created (-NoSingleSubscriptionOutput = $($NoSingleSubscriptionOutput))" -ForegroundColor Green
+ $script:paramsUsed += 'NoSingleSubscriptionOutput: true
'
+ }
+ else {
+ Write-Host " Single Subscription output will be created (-NoSingleSubscriptionOutput = $($NoSingleSubscriptionOutput))" -ForegroundColor Yellow
+ $script:paramsUsed += 'NoSingleSubscriptionOutput: false
'
+ }
+
+ if ($azAPICallConf['htParameters'].NoResourceProvidersDetailed -eq $true) {
+ Write-Host " ResourceProvider Detailed for TenantSummary disabled (-NoResourceProvidersDetailed = $($azAPICallConf['htParameters'].NoResourceProvidersDetailed))" -ForegroundColor Green
+ $script:paramsUsed += "NoResourceProvidersDetailed: $($azAPICallConf['htParameters'].NoResourceProvidersDetailed)
"
+ }
+ else {
+ Write-Host " ResourceProvider Detailed for TenantSummary enabled - use parameter: '-NoResourceProvidersDetailed' to disable" -ForegroundColor Yellow
+ $script:paramsUsed += "NoResourceProvidersDetailed: $($azAPICallConf['htParameters'].NoResourceProvidersDetailed)
"
+ }
+
+ if ($azAPICallConf['htParameters'].NoResourceProvidersAtAll -eq $true) {
+ Write-Host " ResourceProvider collection disabled (NoResourceProvidersAtAll = $($azAPICallConf['htParameters'].NoResourceProvidersAtAll))" -ForegroundColor Green
+ $script:paramsUsed += "NoResourceProvidersAtAll: $($azAPICallConf['htParameters'].NoResourceProvidersAtAll)
"
+ }
+ else {
+ Write-Host " ResourceProvider collection enabled - use parameter: 'NoResourceProvidersAtAll' to disable" -ForegroundColor Yellow
+ $script:paramsUsed += "NoResourceProvidersAtAll: $($azAPICallConf['htParameters'].NoResourceProvidersAtAll)
"
+ }
+
+ if ($azAPICallConf['htParameters'].LargeTenant -or $azAPICallConf['htParameters'].PolicyAtScopeOnly -or $azAPICallConf['htParameters'].RBACAtScopeOnly) {
+ if ($azAPICallConf['htParameters'].LargeTenant) {
+ Write-Host " TenantSummary Policy assignments and Role assignments will not include assignment information on scopes where assignment is inherited, ScopeInsights will not be created, ResourceProvidersDetailed will not be created (-LargeTenant = $($azAPICallConf['htParameters'].LargeTenant))" -ForegroundColor Green
+ $script:paramsUsed += "LargeTenant: $($azAPICallConf['htParameters'].LargeTenant)
"
+ $script:paramsUsed += "LargeTenant -> PolicyAtScopeOnly: $($azAPICallConf['htParameters'].PolicyAtScopeOnly)
"
+ $script:paramsUsed += "LargeTenant -> RBACAtScopeOnly: $($azAPICallConf['htParameters'].RBACAtScopeOnly)
"
+ $script:paramsUsed += "LargeTenant -> NoScopeInsights: $($NoScopeInsights)
"
+ $script:paramsUsed += "LargeTenant -> NoResourceProvidersDetailed: $($azAPICallConf['htParameters'].NoResourceProvidersDetailed)
"
+ }
+ else {
+ Write-Host " TenantSummary LargeTenant disabled (-LargeTenant = $($azAPICallConf['htParameters'].LargeTenant)) Q: Why would you not want to enable -LargeTenant? A: In larger tenants showing the inheritance on each scope may blow up the html file (up to unusable due to html file size)" -ForegroundColor Yellow
+ $script:paramsUsed += "LargeTenant: $($azAPICallConf['htParameters'].LargeTenant)
"
+
+ if ($azAPICallConf['htParameters'].PolicyAtScopeOnly) {
+ Write-Host " TenantSummary Policy assignments will not include assignment information on scopes where assignment is inherited (PolicyAtScopeOnly = $($azAPICallConf['htParameters'].PolicyAtScopeOnly))" -ForegroundColor Green
+ $script:paramsUsed += "PolicyAtScopeOnly: $($azAPICallConf['htParameters'].PolicyAtScopeOnly)
"
+ }
+ else {
+ Write-Host " TenantSummary Policy assignments will include assignment information on scopes where assignment is inherited (PolicyAtScopeOnly = $($azAPICallConf['htParameters'].PolicyAtScopeOnly))" -ForegroundColor Yellow
+ $script:paramsUsed += "PolicyAtScopeOnly: $($azAPICallConf['htParameters'].PolicyAtScopeOnly)
"
+ }
+
+ if ($azAPICallConf['htParameters'].RBACAtScopeOnly) {
+ Write-Host " TenantSummary Role assignments will not include assignment information on scopes where assignment is inherited (RBACAtScopeOnly = $($azAPICallConf['htParameters'].RBACAtScopeOnly))" -ForegroundColor Green
+ $script:paramsUsed += "RBACAtScopeOnly: $($azAPICallConf['htParameters'].RBACAtScopeOnly)
"
+ }
+ else {
+ Write-Host " TenantSummary Role assignments will include assignment information on scopes where assignment is inherited (RBACAtScopeOnly = $($azAPICallConf['htParameters'].RBACAtScopeOnly))" -ForegroundColor Yellow
+ $script:paramsUsed += "RBACAtScopeOnly: $($azAPICallConf['htParameters'].RBACAtScopeOnly)
"
+ }
+ }
+ }
+ else {
+ Write-Host " TenantSummary LargeTenant disabled (-LargeTenant = $($azAPICallConf['htParameters'].LargeTenant)) Q: Why would you not want to enable -LargeTenant? A: In larger tenants showing the inheritance on each scope may blow up the html file (up to unusable due to html file size)" -ForegroundColor Yellow
+ $script:paramsUsed += "LargeTenant: $($azAPICallConf['htParameters'].LargeTenant)
"
+
+ if ($azAPICallConf['htParameters'].PolicyAtScopeOnly) {
+ Write-Host " TenantSummary Policy assignments will not include assignment information on scopes where assignment is inherited (PolicyAtScopeOnly = $($azAPICallConf['htParameters'].PolicyAtScopeOnly))" -ForegroundColor Green
+ $script:paramsUsed += "PolicyAtScopeOnly: $($azAPICallConf['htParameters'].PolicyAtScopeOnly)
"
+ }
+ else {
+ Write-Host " TenantSummary Policy assignments will include assignment information on scopes where assignment is inherited (PolicyAtScopeOnly = $($azAPICallConf['htParameters'].PolicyAtScopeOnly))" -ForegroundColor Yellow
+ $script:paramsUsed += "PolicyAtScopeOnly: $($azAPICallConf['htParameters'].PolicyAtScopeOnly)
"
+ }
+
+ if ($azAPICallConf['htParameters'].RBACAtScopeOnly) {
+ Write-Host " TenantSummary Role assignments will not include assignment information on scopes where assignment is inherited (RBACAtScopeOnly = $($azAPICallConf['htParameters'].RBACAtScopeOnly))" -ForegroundColor Green
+ $script:paramsUsed += "RBACAtScopeOnly: $($azAPICallConf['htParameters'].RBACAtScopeOnly)
"
+ }
+ else {
+ Write-Host " TenantSummary Role assignments will include assignment information on scopes where assignment is inherited (RBACAtScopeOnly = $($azAPICallConf['htParameters'].RBACAtScopeOnly))" -ForegroundColor Yellow
+ $script:paramsUsed += "RBACAtScopeOnly: $($azAPICallConf['htParameters'].RBACAtScopeOnly)
"
+ }
+ }
+
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) {
+ Write-Host " TenantSummary Policy assignments will also include assignments on ResourceGroups (DoNotIncludeResourceGroupsOnPolicy = $($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy))" -ForegroundColor Yellow
+ $script:paramsUsed += 'DoNotIncludeResourceGroupsOnPolicy: false
'
+ }
+ else {
+ Write-Host " TenantSummary Policy assignments will not include assignments on ResourceGroups (DoNotIncludeResourceGroupsOnPolicy = $($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy))" -ForegroundColor Green
+ $script:paramsUsed += 'DoNotIncludeResourceGroupsOnPolicy: true
'
+ }
+
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ Write-Host " TenantSummary RBAC Role assignments will also include assignments on ResourceGroups and Resources (DoNotIncludeResourceGroupsAndResourcesOnRBAC = $($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC))" -ForegroundColor Yellow
+ $script:paramsUsed += 'DoNotIncludeResourceGroupsAndResourcesOnRBAC: false
'
+ }
+ else {
+ Write-Host " TenantSummary RBAC Role assignments will not include assignments on ResourceGroups and Resources (DoNotIncludeResourceGroupsAndResourcesOnRBAC = $($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC))" -ForegroundColor Green
+ $script:paramsUsed += 'DoNotIncludeResourceGroupsAndResourcesOnRBAC: true
'
+ }
+
+ if (-not $NoCsvExport) {
+ Write-Host " CSV Export enabled: enriched 'Role assignments' data, enriched 'Policy assignments' data and 'all resources' (subscriptionId, mgPath, resourceType, id, name, location, tags, createdTime, changedTime) (-NoCsvExport = $($NoCsvExport))" -ForegroundColor Yellow
+ $script:paramsUsed += 'NoCsvExport: false
'
+ }
+ else {
+ Write-Host " CSV Export disabled: enriched 'Role assignments' data, enriched 'Policy assignments' data and 'all resources' (subscriptionId, mgPath, resourceType, id, name, location, tags, createdTime, changedTime) (-NoCsvExport = $($NoCsvExport))" -ForegroundColor Green
+ $script:paramsUsed += 'NoCsvExport: true
'
+ }
+
+ if (-not $azAPICallConf['htParameters'].NoJsonExport) {
+ Write-Host " JSON Export enabled: export of ManagementGroup Hierarchy including all MG/Sub Policy/RBAC definitions, Policy/RBAC assignments and some more relevant information to JSON (-NoJsonExport = $($azAPICallConf['htParameters'].NoJsonExport))" -ForegroundColor Yellow
+ $script:paramsUsed += 'NoJsonExport: false
'
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) {
+ if (-not $JsonExportExcludeResourceGroups) {
+ Write-Host " JSON Export will also include Policy assignments on ResourceGroups (JsonExportExcludeResourceGroups = $($JsonExportExcludeResourceGroups))" -ForegroundColor Yellow
+ $script:paramsUsed += "JsonExportExcludeResourceGroups Policy: $($JsonExportExcludeResourceGroups)
"
+ }
+ else {
+ Write-Host " JSON Export will not include Policy assignments on ResourceGroups (JsonExportExcludeResourceGroups = $($JsonExportExcludeResourceGroups))" -ForegroundColor Green
+ $script:paramsUsed += "JsonExportExcludeResourceGroups Policy: $($JsonExportExcludeResourceGroups)
"
+ }
+ }
+ if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) {
+ if (-not $JsonExportExcludeResourceGroups) {
+ Write-Host " JSON Export will also include Role assignments on ResourceGroups (JsonExportExcludeResourceGroups = $($JsonExportExcludeResourceGroups))" -ForegroundColor Yellow
+ $script:paramsUsed += "JsonExportExcludeResourceGroups RBAC: $($JsonExportExcludeResourceGroups)
"
+
+ }
+ else {
+ Write-Host " JSON Export will not include Role assignments on ResourceGroups (JsonExportExcludeResourceGroups = $($JsonExportExcludeResourceGroups))" -ForegroundColor Green
+ $script:paramsUsed += "JsonExportExcludeResourceGroups RBAC: $($JsonExportExcludeResourceGroups)
"
+ }
+ if (-not $JsonExportExcludeResources) {
+ Write-Host " JSON Export will also include Role assignments on Resources (JsonExportExcludeResources = $($JsonExportExcludeResources))" -ForegroundColor Yellow
+ $script:paramsUsed += "JsonExportExcludeResources RBAC: $($JsonExportExcludeResources)
"
+ }
+ else {
+ Write-Host " JSON Export will not include Role assignments on Resources (JsonExportExcludeResources = $($JsonExportExcludeResources))" -ForegroundColor Green
+ $script:paramsUsed += "JsonExportExcludeResources RBAC: $($JsonExportExcludeResources)
"
+ }
+ }
+ }
+ else {
+ Write-Host " JSON Export disabled: export of ManagementGroup Hierarchy including all MG/Sub Policy/RBAC definitions, Policy/RBAC assignments and some more relevant information to JSON (-NoJsonExport = $($azAPICallConf['htParameters'].NoJsonExport))" -ForegroundColor Green
+ $script:paramsUsed += 'NoJsonExport: true
'
+ }
+
+ if ($ThrottleLimit -eq 10) {
+ Write-Host " ThrottleLimit = $ThrottleLimit" -ForegroundColor Yellow
+ #$script:paramsUsed += "ThrottleLimit: $ThrottleLimit
"
+ }
+ else {
+ Write-Host " ThrottleLimit = $ThrottleLimit" -ForegroundColor Green
+ #$script:paramsUsed += "ThrottleLimit: $ThrottleLimit
"
+ }
+
+
+ if ($ChangeTrackingDays -eq 14) {
+ Write-Host " ChangeTrackingDays = $ChangeTrackingDays" -ForegroundColor Yellow
+ #$script:paramsUsed += "ChangeTrackingDays: $ChangeTrackingDays
"
+ }
+ else {
+ Write-Host " ChangeTrackingDays = $ChangeTrackingDays" -ForegroundColor Green
+ #$script:paramsUsed += "ChangeTrackingDays: $ChangeTrackingDays
"
+ }
+
+ if ($azAPICallConf['htParameters'].NoResources) {
+ Write-Host " NoResources = $($azAPICallConf['htParameters'].NoResources)" -ForegroundColor Green
+ $script:paramsUsed += "NoResources: $($azAPICallConf['htParameters'].NoResources)
"
+ }
+ else {
+ Write-Host " NoResources = $($azAPICallConf['htParameters'].NoResources)" -ForegroundColor Yellow
+ $script:paramsUsed += "NoResources: $($azAPICallConf['htParameters'].NoResources)
"
+ }
+
+ if ($ShowMemoryUsage) {
+ Write-Host " ShowMemoryUsage = $($ShowMemoryUsage)" -ForegroundColor Green
+ #$script:paramsUsed += "ShowMemoryUsage: $($ShowMemoryUsage)
"
+ }
+ else {
+ Write-Host " ShowMemoryUsage = $($ShowMemoryUsage)" -ForegroundColor Yellow
+ #$script:paramsUsed += "ShowMemoryUsage: $($ShowMemoryUsage)
"
+ }
+
+ if ($CriticalMemoryUsage -ne 99) {
+ Write-Host " CriticalMemoryUsage = $($CriticalMemoryUsage)%" -ForegroundColor green
+ #$script:paramsUsed += "ShowMemoryUsage: $($ShowMemoryUsage)
"
+ }
+ else {
+ Write-Host " CriticalMemoryUsage = $($CriticalMemoryUsage)%" -ForegroundColor Yellow
+ #$script:paramsUsed += "ShowMemoryUsage: $($ShowMemoryUsage)
"
+ }
+
+ if ($azAPICallConf['htParameters'].DoPSRule) {
+ Write-Host " DoPSRule = $($azAPICallConf['htParameters'].DoPSRule)" -ForegroundColor Green
+ $script:paramsUsed += "DoPSRule: $($azAPICallConf['htParameters'].DoPSRule)
"
+
+ if ($azAPICallConf['htParameters'].PSRuleFailedOnly) {
+ Write-Host " PSRuleFailedOnly = $($azAPICallConf['htParameters'].PSRuleFailedOnly)" -ForegroundColor Green
+ $script:paramsUsed += "PSRuleFailedOnly: $($azAPICallConf['htParameters'].PSRuleFailedOnly)
"
+ }
+ else {
+ Write-Host " PSRuleFailedOnly = $($azAPICallConf['htParameters'].PSRuleFailedOnly)" -ForegroundColor Yellow
+ $script:paramsUsed += "PSRuleFailedOnly: $($azAPICallConf['htParameters'].PSRuleFailedOnly)
"
+ }
+ }
+ else {
+ Write-Host " DoPSRule = $($azAPICallConf['htParameters'].DoPSRule)" -ForegroundColor Yellow
+ $script:paramsUsed += "DoPSRule: $($azAPICallConf['htParameters'].DoPSRule)
"
+ }
+
+ if ($NoPIMEligibility) {
+ Write-Host " NoPIMEligibility = $($NoPIMEligibility)" -ForegroundColor Green
+ $script:paramsUsed += "NoPIMEligibility: $($NoPIMEligibility)
"
+ }
+ else {
+ Write-Host " NoPIMEligibility = $($NoPIMEligibility)" -ForegroundColor Yellow
+ $script:paramsUsed += "NoPIMEligibility: $($NoPIMEligibility)
"
+ }
+
+ if ($PIMEligibilityIgnoreScope) {
+ Write-Host " PIMEligibilityIgnoreScope = $($PIMEligibilityIgnoreScope)" -ForegroundColor Green
+ #$script:paramsUsed += "PIMEligibilityIgnoreScope: $($PIMEligibilityIgnoreScope)
"
+ }
+ else {
+ Write-Host " PIMEligibilityIgnoreScope = $($PIMEligibilityIgnoreScope)" -ForegroundColor Yellow
+ #$script:paramsUsed += "PIMEligibilityIgnoreScope: $($PIMEligibilityIgnoreScope)
"
+ }
+
+ if ($NoPIMEligibilityIntegrationRoleAssignmentsAll) {
+ Write-Host " NoPIMEligibilityIntegrationRoleAssignmentsAll = $($NoPIMEligibilityIntegrationRoleAssignmentsAll)" -ForegroundColor Green
+ #$script:paramsUsed += "NoPIMEligibilityIntegrationRoleAssignmentsAll: $($NoPIMEligibilityIntegrationRoleAssignmentsAll)
"
+ }
+ else {
+ Write-Host " NoPIMEligibilityIntegrationRoleAssignmentsAll = $($NoPIMEligibilityIntegrationRoleAssignmentsAll)" -ForegroundColor Yellow
+ #$script:paramsUsed += "NoPIMEligibilityIntegrationRoleAssignmentsAll: $($NoPIMEligibilityIntegrationRoleAssignmentsAll)
"
+ }
+
+ if ($NoDefinitionInsightsDedicatedHTML) {
+ Write-Host " NoDefinitionInsightsDedicatedHTML = $($NoDefinitionInsightsDedicatedHTML)" -ForegroundColor Green
+ #$script:paramsUsed += "NoDefinitionInsightsDedicatedHTML: $($NoDefinitionInsightsDedicatedHTML)
"
+ }
+ else {
+ Write-Host " NoDefinitionInsightsDedicatedHTML = $($NoDefinitionInsightsDedicatedHTML)" -ForegroundColor Yellow
+ #$script:paramsUsed += "NoDefinitionInsightsDedicatedHTML: $($NoDefinitionInsightsDedicatedHTML)
"
+ }
+
+ if ($NoALZPolicyVersionChecker) {
+ Write-Host " NoALZPolicyVersionChecker = $($NoALZPolicyVersionChecker)" -ForegroundColor Green
+ #$script:paramsUsed += "NoALZPolicyVersionChecker: $($NoALZPolicyVersionChecker)
"
+ }
+ else {
+ Write-Host " NoALZPolicyVersionChecker = $($NoALZPolicyVersionChecker)" -ForegroundColor Yellow
+ #$script:paramsUsed += "NoALZPolicyVersionChecker: $($NoALZPolicyVersionChecker)
"
+ }
+
+ if ($NoStorageAccountAccessAnalysis) {
+ Write-Host " NoStorageAccountAccessAnalysis = $($NoStorageAccountAccessAnalysis)" -ForegroundColor Green
+ #$script:paramsUsed += "NoStorageAccountAccessAnalysis: $($NoStorageAccountAccessAnalysis)
"
+ }
+ else {
+ Write-Host " NoStorageAccountAccessAnalysis = $($NoStorageAccountAccessAnalysis)" -ForegroundColor Yellow
+ #$script:paramsUsed += "NoStorageAccountAccessAnalysis: $($NoStorageAccountAccessAnalysis)
"
+ if ($StorageAccountAccessAnalysisSubscriptionTags[0] -ne 'undefined' -and $StorageAccountAccessAnalysisSubscriptionTags.Count -gt 0) {
+ Write-Host " StorageAccountAccessAnalysisSubscriptionTags: $($StorageAccountAccessAnalysisSubscriptionTags -join ', ')" -ForegroundColor Green
+ }
+ if ($StorageAccountAccessAnalysisStorageAccountTags[0] -ne 'undefined' -and $StorageAccountAccessAnalysisStorageAccountTags.Count -gt 0) {
+ Write-Host " StorageAccountAccessAnalysisStorageAccountTags: $($StorageAccountAccessAnalysisStorageAccountTags -join ', ')" -ForegroundColor Green
+ }
+ }
+
+ if ($NoNetwork) {
+ Write-Host " NoNetwork = $($NoNetwork)" -ForegroundColor Green
+ #$script:paramsUsed += "NoNetwork: $($NoNetwork)
"
+ }
+ else {
+ Write-Host " NoNetwork = $($NoNetwork)" -ForegroundColor Yellow
+ #$script:paramsUsed += "NoNetwork: $($NoNetwork)
"
+
+ if ($NetworkSubnetIPAddressUsageCriticalPercentage -ne 80) {
+ Write-Host " NetworkSubnetIPAddressUsageCriticalPercentage = $($NetworkSubnetIPAddressUsageCriticalPercentage)" -ForegroundColor Green
+ #$script:paramsUsed += "NetworkSubnetIPAddressUsageCriticalPercentage: $($NetworkSubnetIPAddressUsageCriticalPercentage)
"
+ }
+ else {
+ Write-Host " NoNetwork = $($NetworkSubnetIPAddressUsageCriticalPercentage)" -ForegroundColor Yellow
+ #$script:paramsUsed += "NetworkSubnetIPAddressUsageCriticalPercentage: $($NetworkSubnetIPAddressUsageCriticalPercentage)
"
+ }
+ }
+
+ if ($GitHubActionsOIDC) {
+ Write-Host " GitHubActionsOIDC = $($GitHubActionsOIDC)" -ForegroundColor Green
+ #$script:paramsUsed += "GitHubActionsOIDC: $($GitHubActionsOIDC)
"
+ }
+ else {
+ Write-Host " GitHubActionsOIDC = $($GitHubActionsOIDC)" -ForegroundColor Yellow
+ #$script:paramsUsed += "GitHubActionsOIDC: $($GitHubActionsOIDC)
"
+ }
+
+ }
+ #endregion RunInfo
+}
+function selectMg() {
+ Write-Host 'Please select a Management Group from the list below:'
+ $MgtGroupArray | Select-Object '#', Name, @{Name = 'displayName'; Expression = { $_.properties.displayName } }, Id | Format-Table
+ Write-Host "If you don't see your ManagementGroupID try using the parameter -ManagementGroupID" -ForegroundColor Yellow
+ if ($msg) {
+ Write-Host $msg -ForegroundColor Red
+ }
+
+ $script:SelectedMG = Read-Host "Please enter a selection from 1 to $(($MgtGroupArray).count)"
+
+ if ($SelectedMG -match '^[\d\.]+$') {
+ if ([int]$SelectedMG -lt 1 -or [int]$SelectedMG -gt ($MgtGroupArray).count) {
+ $msg = "last input '$SelectedMG' is out of range, enter a number from the selection!"
+ selectMg
+ }
+ }
+ else {
+ $msg = "last input '$SelectedMG' is not numeric, enter a number from the selection!"
+ selectMg
+ }
+}
+function setBaseVariablesMG {
+ if (($azAPICallConf['checkContext']).Tenant.Id -ne $ManagementGroupId) {
+ $script:mgSubPathTopMg = $selectedManagementGroupId.ParentName
+ $script:getMgParentId = $selectedManagementGroupId.ParentName
+ $script:getMgParentName = $selectedManagementGroupId.ParentDisplayName
+ $script:mermaidprnts = "'$(($azAPICallConf['checkContext']).Tenant.Id)',$getMgParentId"
+ }
+ else {
+ $script:hierarchyLevel = -1
+ $script:mgSubPathTopMg = "$ManagementGroupId"
+ $script:getMgParentId = "'$ManagementGroupId'"
+ $script:getMgParentName = 'Tenant Root'
+ $script:mermaidprnts = "'$getMgParentId',$getMgParentId"
+ }
+}
+function setOutput {
+ if (-not [IO.Path]::IsPathRooted($outputPath)) {
+ $outputPath = Join-Path -Path (Get-Location).Path -ChildPath $outputPath
+ }
+ $outputPath = Join-Path -Path $outputPath -ChildPath '.'
+ $script:outputPath = [IO.Path]::GetFullPath($outputPath)
+ if (-not (Test-Path $outputPath)) {
+ Write-Host "path $outputPath does not exist - please create it!" -ForegroundColor Red
+ Throw 'Error - check the last console output for details'
+ }
+ else {
+ Write-Host "Output/Files will be created in path '$outputPath'"
+ }
+
+ #fileTimestamp
+ try {
+ $script:fileTimestamp = (Get-Date -Format $FileTimeStampFormat)
+ }
+ catch {
+ Write-Host "fileTimestamp format: '$($FileTimeStampFormat)' invalid; continue with default format: 'yyyyMMdd_HHmmss'" -ForegroundColor Red
+ $FileTimeStampFormat = 'yyyyMMdd_HHmmss'
+ $script:fileTimestamp = (Get-Date -Format $FileTimeStampFormat)
+ }
+
+ $script:executionDateTimeInternationalReadable = Get-Date -Format 'dd-MMM-yyyy HH:mm:ss'
+ $script:currentTimeZone = (Get-TimeZone).Id
+}
+function setTranscript {
+ if ($ManagementGroupId) {
+ if ($onAzureDevOpsOrGitHubActions -eq $true) {
+ if ($HierarchyMapOnly -eq $true) {
+ $script:fileNameTranscript = "AzGovViz_HierarchyMapOnly_$($ManagementGroupId)_Log.txt"
+ }
+ elseif ($ManagementGroupsOnly -eq $true) {
+ $script:fileNameTranscript = "AzGovViz_ManagementGroupsOnly_$($ManagementGroupId)_Log.txt"
+ }
+ else {
+ $script:fileNameTranscript = "AzGovViz_$($ManagementGroupId)_Log.txt"
+ }
+ }
+ else {
+ if ($HierarchyMapOnly -eq $true) {
+ $script:fileNameTranscript = "AzGovViz_HierarchyMapOnly_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)_Log.txt"
+ }
+ elseif ($ManagementGroupsOnly -eq $true) {
+ $script:fileNameTranscript = "AzGovViz_ManagementGroupsOnly_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)_Log.txt"
+ }
+ else {
+ $script:fileNameTranscript = "AzGovViz_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)_Log.txt"
+ }
+ }
+ }
+ else {
+ if ($onAzureDevOpsOrGitHubActions -eq $true) {
+ if ($HierarchyMapOnly -eq $true) {
+ $script:fileNameTranscript = 'AzGovViz_HierarchyMapOnly_Log.txt'
+ }
+ elseif ($ManagementGroupsOnly -eq $true) {
+ $script:fileNameTranscript = 'AzGovViz_ManagementGroupsOnly_Log.txt'
+ }
+ else {
+ $script:fileNameTranscript = 'AzGovViz_Log.txt'
+ }
+ }
+ else {
+ if ($HierarchyMapOnly -eq $true) {
+ $script:fileNameTranscript = "AzGovViz_HierarchyMapOnly_$($ProductVersion)_$($fileTimestamp)_Log.txt"
+ }
+ elseif ($ManagementGroupsOnly -eq $true) {
+ $script:fileNameTranscript = "AzGovViz_ManagementGroupsOnly_$($ProductVersion)_$($fileTimestamp)_Log.txt"
+ }
+ else {
+ $script:fileNameTranscript = "AzGovViz_$($ProductVersion)_$($fileTimestamp)_Log.txt"
+ }
+ }
+ }
+ Write-Host "Writing transcript: $($outputPath)$($DirectorySeparatorChar)$($fileNameTranscript)"
+ Start-Transcript -Path "$($outputPath)$($DirectorySeparatorChar)$($fileNameTranscript)"
+}
+function showMemoryUsage {
+
+ function makeDouble {
+ [CmdletBinding()]
+ Param
+ (
+ [Parameter(Mandatory = $true)]$MemoryUsed
+ )
+
+ try {
+ $memoryUsedDouble = [double]($memoryUsed -replace ',', '.')
+ }
+ catch {
+ $memoryUsedDouble = [string]$MemoryUsed
+ }
+ return $memoryUsedDouble
+ }
+
+ function getMemoryUsage {
+ if ($IsLinux) {
+ $memoryUsed = 100 - (free | grep Mem | awk '{print $4/$2 * 100.0}')
+ makeDouble $memoryUsed
+ }
+ if ($IsWindows) {
+ $memoryUsed = (Get-CimInstance win32_operatingsystem | ForEach-Object { '{0:N2}' -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory) * 100) / $_.TotalVisibleMemorySize) })
+ makeDouble $memoryUsed
+ }
+ }
+ $memoryUsed = getMemoryUsage
+
+ if ($memoryUsed -is [double]) {
+ if ($memoryUsed -gt $CriticalMemoryUsage) {
+ Write-Host "System memory utilization HIGH: $([math]::Round($memoryUsed))%" -ForegroundColor Magenta
+ Write-Host 'Init garbage collection (GC)'
+ $PSMemoryBefore = [System.GC]::GetTotalMemory($false)
+ Write-Host " PS memory used before GC: $($PSMemoryBefore /1MB)MB ($PSMemoryBefore)"
+ $startGC = Get-Date
+ $PSMemoryAfter = [System.GC]::GetTotalMemory($true)
+ $endGC = Get-Date
+ $PSMemoryDiff = $PSMemoryBefore - $PSMemoryAfter
+ Write-Host " PS memory used after GC: $($PSMemoryAfter /1MB)MB ($PSMemoryAfter)"
+ Write-Host " GC cleared $($PSMemoryDiff /1MB)MB ($PSMemoryDiff)" -ForegroundColor Green
+ Write-Host " GC duration: $((New-TimeSpan -Start $startGC -End $endGC).TotalSeconds) seconds"
+ Write-Host " System memory utilization after GC: $(getMemoryUsage)%"
+ }
+ else {
+ if ($ShowMemoryUsage) {
+ Write-Host "System memory utilization: $([math]::Round($memoryUsed))%"
+ }
+ }
+ }
+ else {
+ Write-Host "System memory utilization: $($memoryUsed)% (not double)"
+ }
+}
+function stats {
+ #region Stats
+ if (-not $StatsOptOut) {
+
+ $dur = 0
+ if ($durationProduct.TotalMinutes -lt 5) {
+ $dur = 5
+ }
+
+ if ($azAPICallConf['htParameters'].onAzureDevOps) {
+ if ($env:BUILD_REPOSITORY_ID) {
+ $hashTenantIdOrRepositoryId = [string]($env:BUILD_REPOSITORY_ID)
+ }
+ else {
+ $hashTenantIdOrRepositoryId = [string]($azAPICallConf['checkContext'].Tenant.Id)
+ }
+ }
+ else {
+ $hashTenantIdOrRepositoryId = [string]($azAPICallConf['checkContext'].Tenant.Id)
+ }
+
+ $hashAccId = [string]($azAPICallConf['checkContext'].Account.Id)
+
+ $hasher384 = [System.Security.Cryptography.HashAlgorithm]::Create('sha384')
+ $hasher512 = [System.Security.Cryptography.HashAlgorithm]::Create('sha512')
+
+ $hashTenantIdOrRepositoryIdSplit = $hashTenantIdOrRepositoryId.split('-')
+ $hashAccIdSplit = $hashAccId.split('-')
+
+ if (($hashTenantIdOrRepositoryIdSplit[0])[0] -match '[a-z]') {
+ $hashTenantIdOrRepositoryIdUse = "$(($hashTenantIdOrRepositoryIdSplit[0]).substring(2))$($hashAccIdSplit[2])"
+ $hashTenantIdOrRepositoryIdUse = $hasher512.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($hashTenantIdOrRepositoryIdUse))
+ $hashTenantIdOrRepositoryIdUse = "$(([System.BitConverter]::ToString($hashTenantIdOrRepositoryIdUse)) -replace '-')"
+ }
+ else {
+ $hashTenantIdOrRepositoryIdUse = "$(($hashTenantIdOrRepositoryIdSplit[4]).substring(6))$($hashAccIdSplit[1])"
+ $hashTenantIdOrRepositoryIdUse = $hasher384.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($hashTenantIdOrRepositoryIdUse))
+ $hashTenantIdOrRepositoryIdUse = "$(([System.BitConverter]::ToString($hashTenantIdOrRepositoryIdUse)) -replace '-')"
+ }
+
+ if (($hashAccIdSplit[0])[0] -match '[a-z]') {
+ $hashAccIdUse = "$($hashAccIdSplit[0].substring(2))$($hashTenantIdOrRepositoryIdSplit[2])"
+ $hashAccIdUse = $hasher512.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($hashAccIdUse))
+ $hashAccIdUse = "$(([System.BitConverter]::ToString($hashAccIdUse)) -replace '-')"
+ $hashUse = "$($hashAccIdUse)$($hashTenantIdOrRepositoryIdUse)"
+ }
+ else {
+ $hashAccIdUse = "$($hashAccIdSplit[4].substring(6))$($hashTenantIdOrRepositoryIdSplit[1])"
+ $hashAccIdUse = $hasher384.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($hashAccIdUse))
+ $hashAccIdUse = "$(([System.BitConverter]::ToString($hashAccIdUse)) -replace '-')"
+ $hashUse = "$($hashTenantIdOrRepositoryIdUse)$($hashAccIdUse)"
+ }
+
+ $identifierBase = $hasher512.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($hashUse))
+ $script:statsIdentifier = "$(([System.BitConverter]::ToString($identifierBase)) -replace '-')"
+
+ $accountInfo = "$($azAPICallConf['htParameters'].accountType)$($azAPICallConf['htParameters'].userType)"
+ if ($azAPICallConf['htParameters'].accountType -eq 'ServicePrincipal' -or $azAPICallConf['htParameters'].accountType -eq 'ManagedService' -or $azAPICallConf['htParameters'].accountType -eq 'ClientAssertion') {
+ $accountInfo = $azAPICallConf['htParameters'].accountType
+ }
+
+ $scopeUsage = 'childManagementGroup'
+ if ($ManagementGroupId -eq $azAPICallConf['checkContext'].Tenant.Id) {
+ $scopeUsage = 'rootManagementGroup'
+ }
+
+ $statsCountSubscriptions = 'less than 100'
+ if (($htSubscriptionsMgPath.Keys).Count -ge 100) {
+ $statsCountSubscriptions = 'more than 100'
+ }
+
+ $tryCounter = 0
+ do {
+ if ($tryCounter -gt 0) {
+ Start-Sleep -Seconds ($tryCounter * 3)
+ }
+ $tryCounter++
+ $statsSuccess = $true
+ try {
+ $statusBody = @"
+{
+ "name": "Microsoft.ApplicationInsights.Event",
+ "time": "$((Get-Date).ToUniversalTime())",
+ "iKey": "ffcd6b2e-1a5e-429f-9495-e3492decfe06",
+ "data": {
+ "baseType": "EventData",
+ "baseData": {
+ "name": "$($Product)",
+ "ver": 2,
+ "properties": {
+ "accType": "$($accountInfo)",
+ "azCloud": "$($azAPICallConf['checkContext'].Environment.Name)",
+ "identifier": "$($statsIdentifier)",
+ "platform": "$($azAPICallConf['htParameters'].CodeRunPlatform)",
+ "productVersion": "$($ProductVersion)",
+ "AzAPICallVersion": "$($AzAPICallVersion)",
+ "psAzAccountsVersion": "$($azAPICallConf['htParameters'].AzAccountsVersion)",
+ "psVersion": "$($PSVersionTable.PSVersion)",
+ "scopeUsage": "$($scopeUsage)",
+ "statsCountErrors": "$($Error.Count)",
+ "statsCountSubscriptions": "$($statsCountSubscriptions)",
+ "statsParametersDoNotIncludeResourceGroupsAndResourcesOnRBAC": "$($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC)",
+ "statsParametersDoNotIncludeResourceGroupsOnPolicy": "$($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy)",
+ "statsParametersDoNotShowRoleAssignmentsUserData": "$($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData)",
+ "statsParametersHierarchyMapOnly": "$HierarchyMapOnly",
+ "statsParametersManagementGroupsOnly": "$($azAPICallConf['htParameters'].ManagementGroupsOnly)",
+ "statsParametersLargeTenant": "$($azAPICallConf['htParameters'].LargeTenant)",
+ "statsParametersNoASCSecureScore": "$($azAPICallConf['htParameters'].NoMDfCSecureScore)",
+ "statsParametersDoAzureConsumption": "$($azAPICallConf['htParameters'].DoAzureConsumption)",
+ "statsParametersNoJsonExport": "$($azAPICallConf['htParameters'].NoJsonExport)",
+ "statsParametersNoScopeInsights": "$($NoScopeInsights)",
+ "statsParametersNoSingleSubscriptionOutput": "$($NoSingleSubscriptionOutput)",
+ "statsParametersNoPolicyComplianceStates": "$($azAPICallConf['htParameters'].NoPolicyComplianceStates)",
+ "statsParametersNoResourceProvidersDetailed": "$($azAPICallConf['htParameters'].NoResourceProvidersDetailed)",
+ "statsParametersNoResourceProvidersAtAll": "$($azAPICallConf['htParameters'].NoResourceProvidersAtAll)",
+ "statsParametersNoResources": "$($azAPICallConf['htParameters'].NoResources)",
+ "statsParametersPolicyAtScopeOnly": "$($azAPICallConf['htParameters'].PolicyAtScopeOnly)",
+ "statsParametersRBACAtScopeOnly": "$($azAPICallConf['htParameters'].RBACAtScopeOnly)",
+ "statsParametersDoPSRule": "$($azAPICallConf['htParameters'].DoPSRule)",
+ "statsParametersNoPIMEligibility": "$($NoPIMEligibility)",
+ "statsParametersNoALZPolicyVersionChecker": "$($NoALZPolicyVersionChecker)",
+ "statsParametersNoStorageAccountAccessAnalysis": "$($NoStorageAccountAccessAnalysis)",
+ "statsParametersNoNetwork": "$($NoNetwork)",
+ "statsTry": "$($tryCounter)",
+ "statsDurationProduct": "$($dur)"
+ }
+ }
+ }
+}
+"@
+ $stats = Invoke-WebRequest -Uri 'https://dc.services.visualstudio.com/v2/track' -Method 'POST' -Body $statusBody
+ }
+ catch {
+ $statsSuccess = $false
+ }
+ }
+ until($statsSuccess -eq $true -or $tryCounter -gt 5)
+ }
+ else {
+ #noStats
+ $script:statsIdentifier = (New-Guid).Guid
+ $tryCounter = 0
+ do {
+ if ($tryCounter -gt 0) {
+ Start-Sleep -Seconds ($tryCounter * 3)
+ }
+ $tryCounter++
+ $statsSuccess = $true
+ try {
+ $statusBody = @"
+{
+ "name": "Microsoft.ApplicationInsights.Event",
+ "time": "$((Get-Date).ToUniversalTime())",
+ "iKey": "ffcd6b2e-1a5e-429f-9495-e3492decfe06",
+ "data": {
+ "baseType": "EventData",
+ "baseData": {
+ "name": "$($Product)",
+ "ver": 2,
+ "properties": {
+ "identifier": "$($statsIdentifier)",
+ "statsTry": "$($tryCounter)"
+ }
+ }
+ }
+}
+"@
+ $stats = Invoke-WebRequest -Uri 'https://dc.services.visualstudio.com/v2/track' -Method 'POST' -Body $statusBody
+ }
+ catch {
+ $statsSuccess = $false
+ }
+ }
+ until($statsSuccess -eq $true -or $tryCounter -gt 5)
+ }
+ #endregion Stats
+}
+function testGuid {
+ [OutputType([bool])]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]$StringGuid
+ )
+
+ $ObjectGuid = [System.Guid]::empty
+ return [System.Guid]::TryParse($StringGuid, [System.Management.Automation.PSReference]$ObjectGuid) # Returns True if successfully parsed
+}
+function testPowerShellVersion {
+
+ Write-Host 'Checking PowerShell edition and version'
+ $requiredPSVersion = '7.0.3'
+ $splitRequiredPSVersion = $requiredPSVersion.split('.')
+ $splitRequiredPSVersionMajor = $splitRequiredPSVersion[0]
+ $splitRequiredPSVersionMinor = $splitRequiredPSVersion[1]
+ $splitRequiredPSVersionPatch = $splitRequiredPSVersion[2]
+
+ $thisPSVersion = ($PSVersionTable.PSVersion)
+ $thisPSVersionMajor = ($thisPSVersion).Major
+ $thisPSVersionMinor = ($thisPSVersion).Minor
+ $thisPSVersionPatch = ($thisPSVersion).Patch
+
+ $psVersionCheckResult = 'letsCheck'
+
+ if ($PSVersionTable.PSEdition -eq 'Core' -and $thisPSVersionMajor -eq $splitRequiredPSVersionMajor) {
+ if ($thisPSVersionMinor -gt $splitRequiredPSVersionMinor) {
+ $psVersionCheckResult = 'passed'
+ $psVersionCheck = "(Major[$splitRequiredPSVersionMajor]; Minor[$thisPSVersionMinor] gt $($splitRequiredPSVersionMinor))"
+ }
+ else {
+ if ($thisPSVersionPatch -ge $splitRequiredPSVersionPatch) {
+ $psVersionCheckResult = 'passed'
+ $psVersionCheck = "(Major[$splitRequiredPSVersionMajor]; Minor[$splitRequiredPSVersionMinor]; Patch[$thisPSVersionPatch] gt $($splitRequiredPSVersionPatch))"
+ }
+ else {
+ $psVersionCheckResult = 'failed'
+ $psVersionCheck = "(Major[$splitRequiredPSVersionMajor]; Minor[$splitRequiredPSVersionMinor]; Patch[$thisPSVersionPatch] lt $($splitRequiredPSVersionPatch))"
+ }
+ }
+ }
+ else {
+ $psVersionCheckResult = 'failed'
+ $psVersionCheck = "(Major[$splitRequiredPSVersionMajor] ne $($splitRequiredPSVersionMajor))"
+ }
+
+ if ($psVersionCheckResult -eq 'passed') {
+ Write-Host " PS check $psVersionCheckResult : $($psVersionCheck); (minimum supported version '$requiredPSVersion')"
+ Write-Host " PS Edition: $($PSVersionTable.PSEdition); PS Version: $($PSVersionTable.PSVersion)"
+ Write-Host ' PS Version check succeeded' -ForegroundColor Green
+ }
+ else {
+ Write-Host " PS check $psVersionCheckResult : $($psVersionCheck)"
+ Write-Host " PS Edition: $($PSVersionTable.PSEdition); PS Version: $($PSVersionTable.PSVersion)"
+ Write-Host " Parallelization requires Powershell 'Core' version '$($requiredPSVersion)' or higher"
+ Throw 'Error - check the last console output for details'
+ }
+}
+function validateAccess {
+ #region validationAccess
+ #validation / check 'Microsoft Graph API' Access
+ $permissionCheckResults = @()
+ if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions -eq $true -or $azAPICallConf['htParameters'].accountType -eq 'ServicePrincipal' -or $azAPICallConf['htParameters'].accountType -eq 'ManagedService' -or $azAPICallConf['htParameters'].accountType -eq 'ClientAssertion') {
+
+ Write-Host "Checking $($azAPICallConf['htParameters'].accountType) permissions"
+
+ $permissionsCheckFailed = $false
+
+ $currentTask = 'Test MSGraph Users Read permission'
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/v1.0/users?`$count=true&`$top=1"
+ $method = 'GET'
+ $res = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -consistencyLevel 'eventual' -validateAccess
+ if ($res -eq 'failed') {
+ $permissionCheckResults += "MSGraph API 'Users Read' permission - check FAILED"
+ $permissionsCheckFailed = $true
+ }
+ else {
+ $permissionCheckResults += "MSGraph API 'Users Read' permission - check PASSED"
+ }
+
+ $currentTask = 'Test MSGraph Groups Read permission'
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/v1.0/groups?`$count=true&`$top=1"
+ $method = 'GET'
+ $res = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -consistencyLevel 'eventual' -validateAccess
+ if ($res -eq 'failed') {
+ $permissionCheckResults += "MSGraph API 'Groups Read' permission - check FAILED"
+ $permissionsCheckFailed = $true
+ }
+ else {
+ $permissionCheckResults += "MSGraph API 'Groups Read' permission - check PASSED"
+ }
+
+ $currentTask = 'Test MSGraph ServicePrincipals Read permission'
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/v1.0/servicePrincipals?`$count=true&`$top=1"
+ $method = 'GET'
+ $res = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -consistencyLevel 'eventual' -validateAccess
+ if ($res -eq 'failed') {
+ $permissionCheckResults += "MSGraph API 'ServicePrincipals Read' permission - check FAILED"
+ $permissionsCheckFailed = $true
+ }
+ else {
+ $permissionCheckResults += "MSGraph API 'ServicePrincipals Read' permission - check PASSED"
+ }
+
+ if (-not $NoPIMEligibility) {
+ $currentTask = 'Test MSGraph PrivilegedAccess.Read.AzureResources permission'
+ $uriExt = "&`$expand=parent&`$filter=(type eq 'subscription' or type eq 'managementgroup')&`$top=1"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/privilegedAccess/azureResources/resources?`$select=id,displayName,type,externalId" + $uriExt
+ $res = AzAPICall -AzAPICallConfiguration $azapicallConf -uri $uri -currentTask $currentTask -validateAccess
+ if ($res -eq 'failed') {
+ $permissionCheckResults += "MSGraph API 'PrivilegedAccess.Read.AzureResources' permission - check FAILED - if you cannot grant this permission or you do not have an AAD Premium 2 license then use parameter -NoPIMEligibility"
+ $permissionsCheckFailed = $true
+ }
+ else {
+ $permissionCheckResults += "MSGraph API 'PrivilegedAccess.Read.AzureResources' permission - check PASSED"
+ }
+ }
+ }
+ #endregion validationAccess
+
+ #ManagementGroup helper
+ #region managementGroupHelper
+ if (-not $ManagementGroupId) {
+ #$catchResult = "letscheck"
+ $currentTask = 'Getting all Management Groups'
+ #Write-Host $currentTask
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups?api-version=2020-05-01"
+ $method = 'GET'
+ $getAzManagementGroups = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -validateAccess
+
+ if ($getAzManagementGroups -eq 'failed') {
+ $permissionCheckResults += "RBAC 'Reader' permissions on Management Group - check FAILED (use Id, not displayName)"
+ $permissionsCheckFailed = $true
+ }
+ else {
+ $permissionCheckResults += "RBAC 'Reader' permissions on Management Group - check PASSED"
+ }
+
+ Write-Host 'Permission check results'
+ foreach ($permissionCheckResult in $permissionCheckResults) {
+ if ($permissionCheckResult -like '*PASSED*') {
+ Write-Host $permissionCheckResult -ForegroundColor Green
+ }
+ else {
+ Write-Host $permissionCheckResult -ForegroundColor DarkRed
+ }
+ }
+ if ($permissionsCheckFailed -eq $true) {
+ Write-Host "Please consult the documentation: https://$($GithubRepository)#required-permissions-in-azure"
+ Throw 'Error - Azure Governance Visualizer: check the last console output for details'
+ }
+
+ if ($getAzManagementGroups.Count -eq 0) {
+ Write-Host 'Management Groups count returned null'
+ Throw 'Error - Azure Governance Visualizer: check the last console output for details'
+ }
+ else {
+ Write-Host "Detected $($getAzManagementGroups.Count) Management Groups"
+ }
+
+ [array]$MgtGroupArray = addIndexNumberToArray -array ($getAzManagementGroups)
+ if (-not $MgtGroupArray) {
+ Write-Host 'Seems you do not have access to any Management Group. Please make sure you have the required RBAC role [Reader] assigned on at least one Management Group' -ForegroundColor Red
+ Throw 'Error - Azure Governance Visualizer: check the last console output for details'
+ }
+
+ selectMg
+
+ if ($($MgtGroupArray[$SelectedMG - 1].Name)) {
+ $script:ManagementGroupId = $($MgtGroupArray[$SelectedMG - 1].name)
+ $script:ManagementGroupName = $($MgtGroupArray[$SelectedMG - 1].properties.displayName)
+ }
+ else {
+ Write-Host 's.th. unexpected happened' -ForegroundColor Red
+ return
+ }
+ Write-Host "Selected Management Group: #$($SelectedMG) $ManagementGroupName (Id: $ManagementGroupId)" -ForegroundColor Green
+ Write-Host '_______________________________________'
+ }
+ else {
+ $currentTask = "Checking permissions for ManagementGroup '$ManagementGroupId'"
+ Write-Host $currentTask
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)?api-version=2020-05-01"
+ $method = 'GET'
+ $selectedManagementGroupId = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -listenOn 'Content' -validateAccess
+
+ if ($selectedManagementGroupId -eq 'failed') {
+ $permissionCheckResults += "RBAC 'Reader' permissions on Management Group '$($ManagementGroupId)' - check FAILED (use Id, not displayName)"
+ $permissionsCheckFailed = $true
+ }
+ else {
+ $permissionCheckResults += "RBAC 'Reader' permissions on Management Group '$($ManagementGroupId)' - check PASSED"
+ $script:ManagementGroupId = $selectedManagementGroupId.Name
+ $script:ManagementGroupName = $selectedManagementGroupId.properties.displayName
+ }
+
+ Write-Host 'Permission check results'
+ foreach ($permissionCheckResult in $permissionCheckResults) {
+ if ($permissionCheckResult -like '*PASSED*') {
+ Write-Host $permissionCheckResult -ForegroundColor Green
+ }
+ else {
+ Write-Host $permissionCheckResult -ForegroundColor DarkRed
+ }
+ }
+
+ if ($permissionsCheckFailed -eq $true) {
+ Write-Host "Please consult the documentation for permission requirements: https://$($GithubRepository)#technical-documentation"
+ Throw 'Error - Azure Governance Visualizer: check the last console output for details'
+ }
+ }
+ #endregion managementGroupHelper
+
+ if ($azAPICallConf['htParameters'].accountType -eq 'User') {
+ validateLeastPrivilegeForUser
+ }
+}
+function validateLeastPrivilegeForUser {
+ $currentTask = "Validate least priviledge (Azure Resource side) for executing user $($azapicallConf['htParameters'].userObjectId)"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&`$filter=principalId eq '$($azapicallConf['htParameters'].userObjectId)'"
+ $method = 'GET'
+ $getRoleAssignmentsForExecutingUserAtManagementGroupId = AzAPICall -AzAPICallConfiguration $azapicallConf -uri $uri
+ $nonReaderRolesAssigned = ($getRoleAssignmentsForExecutingUserAtManagementGroupId.properties.RoleDefinitionId | Sort-object -Unique).where({$_ -notlike '*acdd72a7-3385-48ef-bd42-f606fba81ae7'})
+ if ($nonReaderRolesAssigned.Count -gt 0) {
+ Write-Host "* * * LEAST PRIVILEGE ADVICE" -ForegroundColor DarkRed
+ Write-Host "The Azure Governance Visualizer script is executed with more permissions than required."
+ Write-Host "The executing identity '$($azapicallConf['checkContext'].Account.Id)' ($($azapicallConf['checkContext'].Account.Type)) Id: '$($azapicallConf['htparameters'].userObjectId)' has the following RBAC Role(s) assigned at Management Group scope '$ManagementGroupId':"
+ foreach ($nonReaderRoleAssigned in $nonReaderRolesAssigned) {
+ $currentTask = "Get RBAC Role definition '$nonReaderRoleAssigned'"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)$($nonReaderRoleAssigned)?api-version=2022-04-01"
+ $method = 'GET'
+ $getRole = AzAPICall -AzAPICallConfiguration $azapicallConf -uri $uri -listenOn Content
+
+ if ($getRole.properties.roleName -eq 'owner' -or $getRole.properties.roleName -eq 'contributor') {
+ Write-Host " - $($getRole.properties.roleName) ($($getRole.properties.type)) !!!"
+ }
+ else{
+ Write-Host " - $($getRole.properties.roleName) ($($getRole.properties.type))"
+ }
+ }
+ Write-Host "The required Azure RBAC role at Management Group scope '$ManagementGroupId' is 'Reader' (acdd72a7-3385-48ef-bd42-f606fba81ae7)."
+ Write-Host "Recommendation: consider executing the script in context of a Service Principal with least privilege. Review the Azure Governance Visualizer Setup Guide at 'https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/blob/master/setup.md'"
+ Write-Host ' * * * * * * * * * * * * * * * * * * * * * *' -ForegroundColor DarkRed
+ pause
+ }
+ else {
+ Write-Host "Azure Governance Visualizer Least Privilege check (Azure Resource side) for executing identity '$($azapicallConf['checkContext'].Account.Id)' ($($azapicallConf['checkContext'].Account.Type)) Id: '$($azapicallConf['htparameters'].userObjectId)' succeeded" -ForegroundColor Green
+ }
+}
+function verifyModules3rd {
+ [CmdletBinding()]Param(
+ [object]$modules
+ )
+
+ foreach ($module in $modules) {
+ $moduleVersion = $module.ModuleVersion
+
+ if ($moduleVersion) {
+ Write-Host "Verify '$($module.ModuleName)' version '$moduleVersion'"
+ }
+ else {
+ Write-Host "Verify '$($module.ModuleName)' (latest)"
+ }
+
+ $maxRetry = 3
+ $tryCount = 0
+ do {
+ $tryCount++
+ if ($tryCount -gt $maxRetry) {
+ Write-Host " Managing '$($module.ModuleName)' failed (tried $($tryCount - 1)x)"
+ throw " Managing '$($module.ModuleName)' failed"
+ }
+
+ $installModuleSuccess = $false
+ try {
+ if (-not $moduleVersion) {
+ Write-Host ' Check latest module version'
+ try {
+ $moduleVersion = (Find-Module -Name $($module.ModuleName)).Version
+ Write-Host " $($module.ModuleName) Latest module version: $moduleVersion"
+ }
+ catch {
+ Write-Host " $($module.ModuleName) - Check latest module version failed"
+ throw " $($module.ModuleName) - Check latest module version failed"
+ }
+ }
+
+ if (-not $installModuleSuccess) {
+ try {
+ $moduleVersionLoaded = (Get-InstalledModule -Name $($module.ModuleName)).Version
+ if ([System.Version]$moduleVersionLoaded -eq [System.Version]$moduleVersion) {
+ $installModuleSuccess = $true
+ }
+ else {
+ Write-Host " $($module.ModuleName) - Deviating module version '$moduleVersionLoaded'"
+ if ([System.Version]$moduleVersionLoaded -gt [System.Version]$moduleVersion) {
+ if (($env:SYSTEM_TEAMPROJECTID -and $env:BUILD_REPOSITORY_ID) -or $env:GITHUB_ACTIONS) {
+ #AzDO or GH
+ throw " $($module.ModuleName) - Deviating module version $moduleVersionLoaded"
+ }
+ else {
+ Write-Host " Current module version '$moduleVersionLoaded' greater than the minimum required version '$moduleVersion' -> tolerated" -ForegroundColor Yellow
+ $installModuleSuccess = $true
+ }
+ }
+ else {
+ Write-Host " Current module version '$moduleVersionLoaded' lower than the minimum required version '$moduleVersion' -> failed"
+ throw " $($module.ModuleName) - Deviating module version $moduleVersionLoaded"
+ }
+ }
+ }
+ catch {
+ throw
+ }
+ }
+ }
+ catch {
+ Write-Host " '$($module.ModuleName) $moduleVersion' not installed"
+ if (($env:SYSTEM_TEAMPROJECTID -and $env:BUILD_REPOSITORY_ID) -or $env:GITHUB_ACTIONS) {
+ Write-Host " Installing $($module.ModuleName) module ($($moduleVersion))"
+ $installAzAPICallModuleTryCounter = 0
+ do {
+ $installAzAPICallModuleTryCounter++
+ try {
+ $params = @{
+ Name = "$($module.ModuleName)"
+ Force = $true
+ RequiredVersion = $moduleVersion
+ ErrorAction = 'Stop'
+ }
+ Install-Module @params
+ $installAzAPICallModuleSuccess = $true
+ Write-Host " Try#$($installAzAPICallModuleTryCounter) Installing '$($module.ModuleName)' module ($($moduleVersion)) succeeded"
+ }
+ catch {
+ Write-Host " Try#$($installAzAPICallModuleTryCounter) Installing '$($module.ModuleName)' module ($($moduleVersion)) failed - sleep $($installAzAPICallModuleTryCounter) seconds"
+ Start-Sleep -Seconds $installAzAPICallModuleTryCounter
+ $installAzAPICallModuleSuccess = $false
+ }
+ }
+ until($installAzAPICallModuleTryCounter -gt 10 -or $installAzAPICallModuleSuccess)
+ if (-not $installAzAPICallModuleSuccess) {
+ throw " Installing '$($module.ModuleName)' module ($($moduleVersion)) failed"
+ }
+
+ }
+ else {
+ do {
+ $installModuleUserChoice = $null
+ $installModuleUserChoice = Read-Host " Do you want to install $($module.ModuleName) module ($($moduleVersion)) from the PowerShell Gallery? (y/n)"
+ if ($installModuleUserChoice -eq 'y') {
+ try {
+ Install-Module -Name $module.ModuleName -RequiredVersion $moduleVersion -Force -ErrorAction Stop
+ try {
+ Import-Module -Name $module.ModuleName -RequiredVersion $moduleVersion -Force -ErrorAction Stop
+ }
+ catch {
+ throw " 'Import-Module -Name $($module.ModuleName) -RequiredVersion $moduleVersion -Force' failed"
+ }
+ }
+ catch {
+ throw " 'Install-Module -Name $($module.ModuleName) -RequiredVersion $moduleVersion' failed"
+ }
+ }
+ elseif ($installModuleUserChoice -eq 'n') {
+ Write-Host " $($module.ModuleName) module is required, please visit https://aka.ms/$($module.ModuleProductName) or https://www.powershellgallery.com/packages/$($module.ModuleProductName)"
+ throw " $($module.ModuleName) module is required"
+ }
+ else {
+ Write-Host " Accepted input 'y' or 'n'; start over.."
+ }
+ }
+ until ($installModuleUserChoice -eq 'y')
+ }
+ }
+ }
+ until ($installModuleSuccess)
+ Write-Host " Verify '$($module.ModuleName)' version '$moduleVersion' succeeded" -ForegroundColor Green
+ }
+}
+#region functions4DataCollection
+
+function dataCollectionMGSecureScore {
+ [CmdletBinding()]Param(
+ [string]$Id
+ )
+
+ $mgAscSecureScoreResult = ''
+ if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) {
+ if ($htMgASCSecureScore.($Id)) {
+ $mgAscSecureScoreResult = $htMgASCSecureScore.($Id).SecureScore
+ }
+ else {
+ $mgAscSecureScoreResult = 'isNullOrEmpty'
+ }
+ }
+ return $mgAscSecureScoreResult
+}
+$funcDataCollectionMGSecureScore = $function:dataCollectionMGSecureScore.ToString()
+
+function dataCollectionDefenderPlans {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $ChildMgMgPath,
+ $SubscriptionQuotaId
+ )
+
+ $currentTask = "Getting Microsoft Defender for Cloud plans for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$SubscriptionQuotaId']"
+ #https://docs.microsoft.com/en-us/rest/api/securitycenter/pricings
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Security/pricings?api-version=2018-06-01"
+ $method = 'GET'
+ $defenderPlansResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($defenderPlansResult -eq 'SubScriptionNotRegistered' -or $defenderPlansResult -eq 'DisallowedProvider') {
+ #Subscription skipped for MDfC
+ $null = $script:arrayDefenderPlansSubscriptionsSkipped.Add([PSCustomObject]@{
+ subscriptionId = $scopeId
+ subscriptionName = $scopeDisplayName
+ subscriptionQuotaId = $subscriptionQuotaId
+ subscriptionMgPath = $childMgMgPath
+ reason = $defenderPlansResult
+ })
+ }
+ else {
+ if ($defenderPlansResult.Count -gt 0) {
+ foreach ($defenderPlanResult in $defenderPlansResult) {
+ $null = $script:arrayDefenderPlans.Add([PSCustomObject]@{
+ subscriptionId = $scopeId
+ subscriptionName = $scopeDisplayName
+ subscriptionMgPath = $childMgMgPath
+ defenderPlan = $defenderPlanResult.name
+ defenderPlanTier = $defenderPlanResult.properties.pricingTier
+ })
+ }
+ }
+ }
+}
+$funcDataCollectionDefenderPlans = $function:dataCollectionDefenderPlans.ToString()
+
+
+function dataCollectionAdvisorScores {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $ChildMgMgPath,
+ $SubscriptionQuotaId
+ )
+
+ $currentTask = "Getting Advisor Scores for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$SubscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Advisor/advisorScore?api-version=2020-07-01-preview"
+ $method = 'GET'
+ $advisorScoreResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' -skipOnErrorCode 404
+
+ if ($advisorScoreResult -eq 'SubScriptionNotRegistered' -or $advisorScoreResult -eq 'DisallowedProvider') {
+ }
+ else {
+ if ($advisorScoreResult -like 'azgvzerrorMessage_*') {
+
+ }
+ else {
+ if ($advisorScoreResult.Count -gt 0) {
+ foreach ($entry in $advisorScoreResult) {
+ #Write-Host ($entry | ConvertTo-Json -Depth 99)
+ if ($entry.Name) {
+ $objectGuid = [System.Guid]::empty
+ if ([System.Guid]::TryParse($entry.Name, [System.Management.Automation.PSReference]$ObjectGuid)) {
+ }
+ else {
+ $null = $script:arrayAdvisorScores.Add([PSCustomObject]@{
+ subscriptionId = $scopeId
+ subscriptionName = $scopeDisplayName
+ subscriptionQuotaId = $SubscriptionQuotaId
+ subscriptionMgPath = $childMgMgPath
+ category = $entry.Name
+ score = $entry.properties.lastRefreshedScore.score
+ })
+ }
+ }
+ }
+ }
+ }
+ }
+}
+$funcDataCollectionAdvisorScores = $function:dataCollectionAdvisorScores.ToString()
+
+function dataCollectionDefenderEmailContacts {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $SubscriptionQuotaId
+ )
+
+ $currentTask = "Getting Microsoft Defender for Cloud Email contacts for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$SubscriptionQuotaId']"
+ #https://docs.microsoft.com/en-us/rest/api/securitycenter/pricings
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Security/securityContacts?api-version=2020-01-01-preview"
+ $method = 'GET'
+ $defenderSecurityContactsResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -listenOn 'Content' -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($defenderSecurityContactsResult -eq 'SubScriptionNotRegistered' -or $defenderSecurityContactsResult -eq 'DisallowedProvider') {
+ }
+ else {
+
+ if ($defenderSecurityContactsResult -like 'azgvzerrorMessage_*') {
+ $errorInfo = $defenderSecurityContactsResult -replace 'azgvzerrorMessage_'
+ $script:htDefenderEmailContacts.($scopeId) = @{
+ subscriptionId = $scopeId
+ subscriptionName = $scopeDisplayName
+ emails = $errorInfo
+ roles = $errorInfo
+ alertNotificationsState = $errorInfo
+ alertNotificationsminimalSeverity = $errorInfo
+ }
+ }
+ else {
+ if ($defenderSecurityContactsResult.Count -gt 0) {
+ foreach ($entry in $defenderSecurityContactsResult) {
+
+ if ($entry.properties) {
+ if ($entry.properties.notificationsByRole.roles.count -gt 0) {
+ $roles = ($entry.properties.notificationsByRole.roles | Sort-Object) -join "$CsvDelimiterOpposite "
+ }
+ else {
+ $roles = 'none'
+ }
+
+ if ($entry.properties.emails) {
+ if (-not [string]::IsNullOrWhiteSpace($entry.properties.emails)) {
+ $emailsSplitted = $entry.properties.emails -split ';'
+ $arrayEmails = @()
+ foreach ($email in $emailsSplitted) {
+ $arrayEmails += "'$email'"
+ }
+ $emails = ($arrayEmails | Sort-Object) -join "$CsvDelimiterOpposite "
+ }
+ else {
+ $emails = $entry.properties.emails
+ }
+ }
+ else {
+ $emails = 'none'
+ }
+
+ if ($entry.properties.alertNotifications.state) {
+ $alertNotificationsState = $entry.properties.alertNotifications.state
+ }
+
+ if ($entry.properties.alertNotifications.minimalSeverity) {
+ $alertNotificationsminimalSeverity = $entry.properties.alertNotifications.minimalSeverity
+ }
+ }
+ else {
+ $roles = 'n/a'
+ $emails = 'n/a'
+ $alertNotificationsState = 'n/a'
+ $alertNotificationsminimalSeverity = 'n/a'
+ }
+
+ $script:htDefenderEmailContacts.($scopeId) = @{
+ subscriptionId = $scopeId
+ subscriptionName = $scopeDisplayName
+ emails = $emails
+ roles = $roles
+ alertNotificationsState = $alertNotificationsState
+ alertNotificationsminimalSeverity = $alertNotificationsminimalSeverity
+ }
+ }
+ }
+ else {
+ $script:htDefenderEmailContacts.($scopeId) = @{
+ subscriptionId = $scopeId
+ subscriptionName = $scopeDisplayName
+ emails = 'n/a'
+ roles = 'n/a'
+ alertNotificationsState = 'n/a'
+ alertNotificationsminimalSeverity = 'n/a'
+ }
+ }
+ }
+ }
+}
+$funcDataCollectionDefenderEmailContacts = $function:dataCollectionDefenderEmailContacts.ToString()
+
+function dataCollectionVNets {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $SubscriptionQuotaId
+ )
+
+ $currentTask = "Getting Virtual Networks for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$SubscriptionQuotaId']"
+ #https://docs.microsoft.com/en-us/rest/api/securitycenter/pricings
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Network/virtualNetworks?api-version=2022-05-01"
+ $method = 'GET'
+ $networkResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($networkResult -eq 'someError') {
+ }
+ else {
+ if ($networkResult.Count -gt 0) {
+ if ($networkResult -ne 'DisallowedProvider') {
+ foreach ($vnet in $networkResult) {
+ $null = $script:arrayVNets.Add($vnet)
+ }
+ }
+ }
+ }
+}
+$funcDataCollectionVNets = $function:dataCollectionVNets.ToString()
+
+function dataCollectionPrivateEndpoints {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $SubscriptionQuotaId
+ )
+
+ $currentTask = "Getting Private Endpoints for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$SubscriptionQuotaId']"
+ #https://docs.microsoft.com/en-us/rest/api/securitycenter/pricings
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Network/privateEndpoints?api-version=2022-05-01"
+ $method = 'GET'
+ $privateEndpointsResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' -unhandledErrorAction Continue
+
+ if ($privateEndpointsResult.Count -gt 0) {
+ if ($privateEndpointsResult -ne 'DisallowedProvider') {
+ foreach ($pe in $privateEndpointsResult) {
+ $null = $script:arrayPrivateEndPoints.Add($pe)
+ }
+ }
+ }
+}
+$funcDataCollectionPrivateEndpoints = $function:dataCollectionPrivateEndpoints.ToString()
+
+function dataCollectionDiagnosticsSub {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $ChildMgMgPath,
+ $ChildMgId,
+ $subscriptionQuotaId
+ )
+
+ $currentTask = "Getting Diagnostic Settings for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/microsoft.insights/diagnosticSettings?api-version=2021-05-01-preview"
+ $method = 'GET'
+ $getDiagnosticSettingsSub = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask
+
+ if ($getDiagnosticSettingsSub.Count -eq 0) {
+ $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{
+ Scope = 'Sub'
+ ScopeName = $scopeDisplayName
+ ScopeId = $scopeId
+ ScopeMgPath = $childMgMgPath
+ SubMgParent = $childMgId
+ DiagnosticsPresent = 'false'
+ })
+ }
+ else {
+ foreach ($diagnosticSetting in $getDiagnosticSettingsSub) {
+ $arrayLogs = [System.Collections.ArrayList]@()
+ if ($diagnosticSetting.Properties.logs) {
+ foreach ($logCategory in $diagnosticSetting.properties.logs) {
+ $null = $arrayLogs.Add([PSCustomObject]@{
+ Category = $logCategory.category
+ Enabled = $logCategory.enabled
+ })
+ }
+ }
+
+ $htLogs = @{}
+ if ($diagnosticSetting.Properties.logs) {
+ foreach ($logCategory in $diagnosticSetting.properties.logs) {
+ if ($logCategory.enabled) {
+ $htLogs.($logCategory.category) = 'true'
+ }
+ else {
+ $htLogs.($logCategory.category) = 'false'
+ }
+ }
+ }
+
+ if ($diagnosticSetting.Properties.workspaceId) {
+ $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{
+ Scope = 'Sub'
+ ScopeName = $scopeDisplayName
+ ScopeId = $scopeId
+ ScopeMgPath = $childMgMgPath
+ SubMgParent = $childMgId
+ DiagnosticsPresent = 'true'
+ DiagnosticSettingName = $diagnosticSetting.name
+ DiagnosticTargetType = 'LA'
+ DiagnosticTargetId = $diagnosticSetting.Properties.workspaceId
+ DiagnosticCategories = $arrayLogs
+ DiagnosticCategoriesHt = $htLogs
+ })
+ }
+ if ($diagnosticSetting.Properties.storageAccountId) {
+ $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{
+ Scope = 'Sub'
+ ScopeName = $scopeDisplayName
+ ScopeId = $scopeId
+ ScopeMgPath = $childMgMgPath
+ SubMgParent = $childMgId
+ DiagnosticsPresent = 'true'
+ DiagnosticSettingName = $diagnosticSetting.name
+ DiagnosticTargetType = 'SA'
+ DiagnosticTargetId = $diagnosticSetting.Properties.storageAccountId
+ DiagnosticCategories = $arrayLogs
+ DiagnosticCategoriesHt = $htLogs
+ })
+ }
+ if ($diagnosticSetting.Properties.eventHubAuthorizationRuleId) {
+ $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{
+ Scope = 'Sub'
+ ScopeName = $scopeDisplayName
+ ScopeId = $scopeId
+ ScopeMgPath = $childMgMgPath
+ SubMgParent = $childMgId
+ DiagnosticsPresent = 'true'
+ DiagnosticSettingName = $diagnosticSetting.name
+ DiagnosticTargetType = 'EH'
+ DiagnosticTargetId = $diagnosticSetting.Properties.eventHubAuthorizationRuleId
+ DiagnosticCategories = $arrayLogs
+ DiagnosticCategoriesHt = $htLogs
+ })
+ }
+ }
+ }
+}
+$funcDataCollectionDiagnosticsSub = $function:dataCollectionDiagnosticsSub.ToString()
+
+function dataCollectionDiagnosticsMG {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName
+ )
+
+ $mgPath = $htManagementGroupsMgPath.($scopeId).pathDelimited
+ $currentTask = "Getting Diagnostic Settings for Management Group: '$($scopeDisplayName)' ('$($scopeId)')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($mgdetail.Name)/providers/microsoft.insights/diagnosticSettings?api-version=2020-01-01-preview"
+ $method = 'GET'
+ $getDiagnosticSettingsMg = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask
+
+ if ($getDiagnosticSettingsMg -eq 'InvalidResourceType') {
+ #skipping until supported
+ }
+ else {
+ if ($getDiagnosticSettingsMg.Count -eq 0) {
+ $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{
+ Scope = 'Mg'
+ ScopeName = $scopeDisplayName
+ ScopeId = $scopeId
+ ScopeMgPath = $mgPath
+ DiagnosticsPresent = 'false'
+ DiagnosticsInheritedOrnot = $false
+ DiagnosticsInheritedFrom = 'none'
+ })
+ }
+ else {
+ foreach ($diagnosticSetting in $getDiagnosticSettingsMg) {
+ $arrayLogs = [System.Collections.ArrayList]@()
+ if ($diagnosticSetting.Properties.logs) {
+ foreach ($logCategory in $diagnosticSetting.properties.logs) {
+ $null = $arrayLogs.Add([PSCustomObject]@{
+ Category = $logCategory.category
+ Enabled = $logCategory.enabled
+ })
+ }
+ }
+
+ $htLogs = @{}
+ if ($diagnosticSetting.Properties.logs) {
+ foreach ($logCategory in $diagnosticSetting.properties.logs) {
+ if ($logCategory.enabled) {
+ $htLogs.($logCategory.category) = 'true'
+ }
+ else {
+ $htLogs.($logCategory.category) = 'false'
+ }
+ }
+ }
+
+ if ($diagnosticSetting.Properties.workspaceId) {
+ $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{
+ Scope = 'Mg'
+ ScopeName = $scopeDisplayName
+ ScopeId = $scopeId
+ ScopeMgPath = $mgPath
+ DiagnosticsPresent = 'true'
+ DiagnosticsInheritedOrnot = $false
+ DiagnosticsInheritedFrom = 'none'
+ DiagnosticSettingName = $diagnosticSetting.name
+ DiagnosticTargetType = 'LA'
+ DiagnosticTargetId = $diagnosticSetting.Properties.workspaceId
+ DiagnosticCategories = $arrayLogs
+ DiagnosticCategoriesHt = $htLogs
+ })
+ }
+ if ($diagnosticSetting.Properties.storageAccountId) {
+ $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{
+ Scope = 'Mg'
+ ScopeName = $scopeDisplayName
+ ScopeId = $scopeId
+ ScopeMgPath = $mgPath
+ DiagnosticsPresent = 'true'
+ DiagnosticsInheritedOrnot = $false
+ DiagnosticsInheritedFrom = 'none'
+ DiagnosticSettingName = $diagnosticSetting.name
+ DiagnosticTargetType = 'SA'
+ DiagnosticTargetId = $diagnosticSetting.Properties.storageAccountId
+ DiagnosticCategories = $arrayLogs
+ DiagnosticCategoriesHt = $htLogs
+ })
+ }
+ if ($diagnosticSetting.Properties.eventHubAuthorizationRuleId) {
+ $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{
+ Scope = 'Mg'
+ ScopeName = $scopeDisplayName
+ ScopeId = $scopeId
+ ScopeMgPath = $mgPath
+ DiagnosticsPresent = 'true'
+ DiagnosticsInheritedOrnot = $false
+ DiagnosticsInheritedFrom = 'none'
+ DiagnosticSettingName = $diagnosticSetting.name
+ DiagnosticTargetType = 'EH'
+ DiagnosticTargetId = $diagnosticSetting.Properties.eventHubAuthorizationRuleId
+ DiagnosticCategories = $arrayLogs
+ DiagnosticCategoriesHt = $htLogs
+ })
+ }
+ }
+ }
+ }
+}
+$funcDataCollectionDiagnosticsMG = $function:dataCollectionDiagnosticsMG.ToString()
+
+function dataCollectionStorageAccounts {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $ChildMgMgPath,
+ $ChildMgParentNameChainDelimited,
+ $subscriptionQuotaId
+ )
+
+ $currentTask = "Getting Storage Accounts for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Storage/storageAccounts?api-version=2021-09-01"
+ $method = 'GET'
+ $storageAccountsSubscriptionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($storageAccountsSubscriptionResult -ne 'DisallowedProvider') {
+ foreach ($storageAccount in $storageAccountsSubscriptionResult) {
+
+ $dtisostart = Get-Date (Get-Date).AddHours(-1).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z'
+ $dtisoend = Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z'
+ $currentTask = "Getting Storage Account '$($storageAccount.name)' UsedCapacity ('$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId'])"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)$($storageAccount.id)/providers/Microsoft.Insights/metrics?timespan=$($dtisostart)/$($dtisoend)&metricnames=UsedCapacity&aggregation=Average&api-version=2021-05-01"
+ $method = 'GET'
+ $storageAccountUsedCapacity = $null
+ $storageAccountUsedCapacity = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' -unhandledErrorAction Continue
+
+ $usedCapacity = 'n/a'
+ if ($storageAccountUsedCapacity.Count -gt 0) {
+ if (-not [string]::IsNullOrWhiteSpace($storageAccountUsedCapacity.timeseries.data.average)) {
+ $usedCapacity = [decimal]$storageAccountUsedCapacity.timeseries.data.average / 1024 / 1024 / 1024
+ }
+ }
+
+ $obj = [System.Collections.ArrayList]@()
+ $null = $obj.Add([PSCustomObject]@{
+ SA = $storageAccount
+ SAUsedCapacity = $usedCapacity
+ })
+ $null = $script:storageAccounts.Add($obj)
+ }
+ }
+
+}
+$funcDataCollectionStorageAccounts = $function:dataCollectionStorageAccounts.ToString()
+
+function dataCollectionResources {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $ChildMgMgPath,
+ $ChildMgParentNameChainDelimited,
+ $subscriptionQuotaId
+ )
+
+ #region resources LIST
+ $currentTask = "Getting Resources for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/resources?`$expand=createdTime,changedTime,properties&api-version=2023-07-01"
+ $method = 'GET'
+ $resourcesSubscriptionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+ #Write-Host 'arm resList count:'$resourcesSubscriptionResult.Count
+ #endregion resources LIST
+
+ #region resources GET
+ if ($resourcesSubscriptionResult.Count -gt 0) {
+ $arrayResourcesWithProperties = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
+ $resourcesSubscriptionResult | ForEach-Object -Parallel {
+ $resource = $_
+
+ #region using
+ $arrayResourcesWithProperties = $using:arrayResourcesWithProperties
+ $htResourceProvidersRef = $using:htResourceProvidersRef
+ $arrayPrivateEndPointsFromResourceProperties = $using:arrayPrivateEndPointsFromResourceProperties
+ $htAvailablePrivateEndpointTypes = $using:htAvailablePrivateEndpointTypes
+ $htResourcePropertiesConvertfromJSONFailed = $using:htResourcePropertiesConvertfromJSONFailed
+ $scopeId = $using:scopeId
+ $scopeDisplayName = $using:scopeDisplayName
+ $ChildMgParentNameChainDelimited = $using:ChildMgParentNameChainDelimited
+ $azAPICallConf = $using:azAPICallConf
+ #$htResourcesWithProperties = $using:htResourcesWithProperties
+ #endregion using
+
+ if ($htAvailablePrivateEndpointTypes.(($resource.type).ToLower())) {
+ #Write-Host "$($resource.type) in `$htAvailablePrivateEndpointTypes"
+ if ($htResourceProvidersRef.($resource.type)) {
+ if ($htResourceProvidersRef.($resource.type).APIDefault) {
+ $apiVersionToUse = $htResourceProvidersRef.($resource.type).APIDefault
+ $apiRef = 'default'
+ }
+ else {
+ $apiVersionToUse = $htResourceProvidersRef.($resource.type).APILatest
+ $apiRef = 'latest'
+ }
+
+ $currentTask = "Getting Resource Properties API-version: '$apiVersionToUse' ($apiRef); ResourceType: '$($resource.type)'; ResourceId: '$($resource.id)'"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)$($resource.id)?api-version=$apiVersionToUse"
+ $method = 'GET'
+ $resourceResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' -listenOn Content -unhandledErrorAction Continue
+
+ if ($resourceResult -ne 'ResourceOrResourcegroupNotFound' -and $resourceResult -ne 'convertfromJSONError') {
+ $null = $script:arrayResourcesWithProperties.Add($resourceResult)
+ #$script:htResourcesWithProperties.($resourceResult.id) = $resourceResult
+ if ($resourceResult.properties.privateEndpointConnections.Count -gt 0) {
+ foreach ($privateEndpointConnection in $resourceResult.properties.privateEndpointConnections) {
+ $resourceResultIdSplit = $resourceResult.id -split '/'
+ $null = $script:arrayPrivateEndPointsFromResourceProperties.Add([PSCustomObject]@{
+ ResourceName = $resourceResult.name
+ ResourceType = $resourceResult.type
+ ResourceId = $resourceResult.id
+ ResourceResourceGroup = $resourceResultIdSplit[4]
+ ResourceSubscriptionId = $scopeId
+ ResourceSubscriptionName = $scopeDisplayName
+ ResourceMGPath = $ChildMgParentNameChainDelimited
+ privateEndpointConnection = $privateEndpointConnection
+ })
+ }
+ }
+ }
+ else {
+ if ($resourceResult -eq 'convertfromJSONError') {
+ $script:htResourcePropertiesConvertfromJSONFailed.($resource.id) = @{}
+ }
+ }
+ }
+ else {
+ Write-Host "[Azure Governance Visualizer] Please file an issue at the Azure Governance Visualizer GitHub repository (aka.ms/AzGovViz) and provide this information (scrub subscription Id and company identifyable names): No API-version matches! ResourceType: '$($resource.type)'; ResourceId: '$($resource.id)' - Thank you!" -ForegroundColor DarkRed
+ }
+ }
+ else {
+ #Write-Host "$($resource.type) not in `$htAvailablePrivateEndpointTypes"
+ }
+
+ } -ThrottleLimit $azAPICallConf['htParameters'].ThrottleLimit
+ }
+ #Write-Host 'arm resGet count:' $arrayResourcesWithProperties.Count
+ #endregion resources GET
+
+ # if ($resourcesSubscriptionResult.Count -ne $arrayResourcesWithProperties.Count) {
+ # Write-Host " FYI: Getting Resources for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId'] - ARM list count: $($resourcesSubscriptionResult.Count); ARG get count: $($arrayResourcesWithProperties.Count)"
+ # }
+
+ #region PSRule
+ if ($azAPICallConf['htParameters'].DoPSRule -eq $true) {
+ if ($resourcesSubscriptionResult.Count -gt 0) {
+
+ $startPSRule = Get-Date
+ try {
+ <#
+ $path = (Get-Module PSRule.Rules.Azure -ListAvailable | Sort-Object Version -Descending -Top 1).ModuleBase
+ Write-Host "Import-Module (Join-Path $path -ChildPath 'PSRule.Rules.Azure-nodeps.psd1')"
+ Import-Module (Join-Path $path -ChildPath 'PSRule.Rules.Azure-nodeps.psd1')
+ #>
+ if ($azAPICallConf['htParameters'].PSRuleFailedOnly -eq $true) {
+ $psruleResults = $arrayResourcesWithProperties | Invoke-PSRule -Module psrule.rules.Azure -As Detail -Culture en-us -WarningAction Ignore -ErrorAction SilentlyContinue -Outcome Fail, Error
+ }
+ else {
+ $psruleResults = $arrayResourcesWithProperties | Invoke-PSRule -Module psrule.rules.Azure -As Detail -Culture en-us -WarningAction Ignore -ErrorAction SilentlyContinue
+ }
+ }
+ catch {
+ Write-Host " Please report 'PSRule for Azure' error '$($scopeDisplayName)' ('$scopeId'): $_"
+ }
+
+ $endPSRule = Get-Date
+ $durationPSRule = $((New-TimeSpan -Start $startPSRule -End $endPSRule).TotalSeconds)
+
+ $null = $script:arrayPSRuleTracking.Add([PSCustomObject]@{
+ subscriptionId = $scopeId
+ duration = $durationPSRule
+ })
+
+ if ($psruleResults.Count -gt 0) {
+ foreach ($psRuleResult in $psRuleResults) {
+ $null = $script:arrayPSRule.Add([PSCustomObject]@{
+ resourceType = $psRuleResult.TargetType
+ subscriptionId = $scopeId
+ mgPath = $ChildMgParentNameChainDelimited
+ resourceId = $psRuleResult.TargetObject.id
+ pillar = $psRuleResult.Info.Annotations.pillar
+ category = $psRuleResult.Info.Annotations.category
+ severity = $psRuleResult.Info.Annotations.severity
+ rule = $psRuleResult.Info.DisplayName
+ description = $psRuleResult.Info.Description
+ recommendation = $psRuleResult.Info.Recommendation
+ link = $psRuleResult.Info.Annotations.'online version'
+ result = $psRuleResult.Outcome
+ errorMsg = $psRuleResult.Error.Message
+ })
+ }
+ }
+ }
+ }
+ #endregion PSRule
+
+ foreach ($resourceTypeLocation in ($resourcesSubscriptionResult | Group-Object -Property type, location)) {
+ $null = $script:resourcesAll.Add([PSCustomObject]@{
+ subscriptionId = $scopeId
+ type = ($resourceTypeLocation.values[0]).ToLower()
+ location = ($resourceTypeLocation.values[1]).ToLower()
+ count_ = $resourceTypeLocation.Count
+ })
+ }
+
+ foreach ($resourceType in ($resourcesSubscriptionResult | Group-Object -Property type)) {
+ if (-not $htResourceTypesUniqueResource.(($resourceType.name).ToLower())) {
+ $script:htResourceTypesUniqueResource.(($resourceType.name).ToLower()) = @{}
+ $script:htResourceTypesUniqueResource.(($resourceType.name).ToLower()).resourceId = $resourceType.Group.Id | Select-Object -First 1
+ }
+ }
+
+ $startSubResourceIdsThis = Get-Date
+
+ <# Build the $JSONcafResourceNaming #pending PR https://github.com/MicrosoftDocs/cloud-adoption-framework/pull/916
+ $arrayCAFNamingConvention = [System.Collections.ArrayList]@()
+ $htCAFNamingConvention = @{}
+ #$cafNamingFromFile = Get-Content -Path .\cafNaming.md -Encoding utf8
+ $CAFFileName = 'resource-abbreviations.md'
+ Invoke-webrequest -OutFile .\$($CAFFileName) -URI "https://raw.githubusercontent.com/MicrosoftDocs/cloud-adoption-framework/main/docs/ready/azure-best-practices/resource-abbreviations.md"
+ $cafNamingFromFile = Get-Content -Path .\$($CAFFileName) -Encoding utf8
+ $cafNamingFromFile.count
+ foreach ($line in $cafNamingFromFile) {
+ #$line
+ if ($line -match "microsoft.") {
+ $tranformed = $line -replace '`' -split " \| "
+ $friendlyName = $($tranformed[0] -replace "\| ")
+ $resourceType = $($tranformed[1])
+ $namingConvention = $($tranformed[2] -replace " \|" -replace "\|")
+ $null = $arrayCAFNamingConvention.Add([PSCustomObject]@{
+ resourceType = $resourceType
+ friendlyName = $friendlyName
+ namingConvention = $namingConvention
+ })
+ }
+ }
+
+ $htCAFNamingConvention = [ordered]@{}
+ $arrayCAFNamingConventionGroupedByType = $arrayCAFNamingConvention | Sort-Object -Property resourceType | Group-Object -Property resourceType
+ foreach ($entry in $arrayCAFNamingConventionGroupedByType){
+ $htCAFNamingConvention.($entry.name) = @{}
+ $htCAFNamingConvention.($entry.name).friendlyName = $entry.group.friendlyName
+ $htCAFNamingConvention.($entry.name).namingConvention = $entry.group.namingConvention
+ }
+ $htCAFNamingConvention | ConvertTo-Json
+#>
+
+ $JSONcafResourceNaming = @'
+ {
+ "Microsoft.AnalysisServices/servers": {
+ "friendlyName": "Azure Analysis Services server",
+ "namingConvention": "as"
+ },
+ "Microsoft.ApiManagement/service": {
+ "friendlyName": "API management service instance",
+ "namingConvention": "apim-"
+ },
+ "Microsoft.AppConfiguration/configurationStores": {
+ "friendlyName": "App Configuration store",
+ "namingConvention": "appcs-"
+ },
+ "Microsoft.Authorization/policyDefinitions": {
+ "friendlyName": "Policy definition",
+ "namingConvention": "policy-"
+ },
+ "Microsoft.Automation/automationAccounts": {
+ "friendlyName": "Automation account",
+ "namingConvention": "aa-"
+ },
+ "Microsoft.Blueprint/blueprints": {
+ "friendlyName": "Blueprint",
+ "namingConvention": "bp-"
+ },
+ "Microsoft.Blueprint/blueprints/artifacts": {
+ "friendlyName": "Blueprint assignment",
+ "namingConvention": "bpa-"
+ },
+ "Microsoft.Cache/Redis": {
+ "friendlyName": "Azure Cache for Redis instance",
+ "namingConvention": "redis-"
+ },
+ "Microsoft.Cdn/profiles": {
+ "friendlyName": "CDN profile",
+ "namingConvention": "cdnp-"
+ },
+ "Microsoft.Cdn/profiles/endpoints": {
+ "friendlyName": "CDN endpoint",
+ "namingConvention": "cdne-"
+ },
+ "Microsoft.CognitiveServices/accounts": {
+ "friendlyName": "Azure Cognitive Services",
+ "namingConvention": "cog-"
+ },
+ "Microsoft.Compute/availabilitySets": {
+ "friendlyName": "Availability set",
+ "namingConvention": "avail-"
+ },
+ "Microsoft.Compute/cloudServices": {
+ "friendlyName": "Cloud service",
+ "namingConvention": "cld-"
+ },
+ "Microsoft.Compute/diskEncryptionSets": {
+ "friendlyName": "Disk encryption set",
+ "namingConvention": "des"
+ },
+ "Microsoft.Compute/disks": {
+ "friendlyName": [
+ "Managed disk (data)",
+ "Managed disk (OS)"
+ ],
+ "namingConvention": [
+ "disk",
+ "osdisk"
+ ]
+ },
+ "Microsoft.Compute/galleries": {
+ "friendlyName": "Gallery",
+ "namingConvention": "gal"
+ },
+ "Microsoft.Compute/snapshots": {
+ "friendlyName": "Snapshot",
+ "namingConvention": "snap-"
+ },
+ "Microsoft.Compute/virtualMachines": {
+ "friendlyName": "Virtual machine",
+ "namingConvention": "vm"
+ },
+ "Microsoft.Compute/virtualMachineScaleSets": {
+ "friendlyName": "Virtual machine scale set",
+ "namingConvention": "vmss-"
+ },
+ "Microsoft.ContainerInstance/containerGroups": {
+ "friendlyName": "Container instance",
+ "namingConvention": "ci"
+ },
+ "Microsoft.ContainerRegistry/registries": {
+ "friendlyName": "Container registry",
+ "namingConvention": "cr"
+ },
+ "Microsoft.ContainerService/managedClusters": {
+ "friendlyName": "AKS cluster",
+ "namingConvention": "aks-"
+ },
+ "Microsoft.Databricks/workspaces": {
+ "friendlyName": "Azure Databricks workspace",
+ "namingConvention": "dbw-"
+ },
+ "Microsoft.DataFactory/factories": {
+ "friendlyName": "Azure Data Factory",
+ "namingConvention": "adf-"
+ },
+ "Microsoft.DataLakeAnalytics/accounts": {
+ "friendlyName": "Data Lake Analytics account",
+ "namingConvention": "dla"
+ },
+ "Microsoft.DataLakeStore/accounts": {
+ "friendlyName": "Data Lake Store account",
+ "namingConvention": "dls"
+ },
+ "Microsoft.DataMigration/services": {
+ "friendlyName": "Database Migration Service instance",
+ "namingConvention": "dms-"
+ },
+ "Microsoft.DataProtection/BackupVaults": {
+ "friendlyName": "Backup vault",
+ "namingConvention": "bv-"
+ },
+ "Microsoft.DBforMySQL/servers": {
+ "friendlyName": "MySQL database",
+ "namingConvention": "mysql-"
+ },
+ "Microsoft.DBforPostgreSQL/servers": {
+ "friendlyName": "PostgreSQL database",
+ "namingConvention": "psql-"
+ },
+ "Microsoft.Devices/IotHubs": {
+ "friendlyName": "IoT hub",
+ "namingConvention": "iot-"
+ },
+ "Microsoft.Devices/provisioningServices": {
+ "friendlyName": "Provisioning services",
+ "namingConvention": "provs-"
+ },
+ "Microsoft.Devices/provisioningServices/certificates": {
+ "friendlyName": "Provisioning services certificate",
+ "namingConvention": "pcert-"
+ },
+ "Microsoft.DocumentDB/databaseAccounts/sqlDatabases": {
+ "friendlyName": "Azure Cosmos DB database",
+ "namingConvention": "cosmos-"
+ },
+ "Microsoft.EventGrid/domains": {
+ "friendlyName": "Event Grid domain",
+ "namingConvention": "evgd-"
+ },
+ "Microsoft.EventGrid/domains/topics": {
+ "friendlyName": "Event Grid topic",
+ "namingConvention": "evgt-"
+ },
+ "Microsoft.EventGrid/eventSubscriptions": {
+ "friendlyName": "Event Grid subscriptions",
+ "namingConvention": "evgs-"
+ },
+ "Microsoft.EventHub/namespaces": {
+ "friendlyName": "Event Hubs namespace",
+ "namingConvention": "evhns-"
+ },
+ "Microsoft.EventHub/namespaces/eventHubs": {
+ "friendlyName": "Event hub",
+ "namingConvention": "evh-"
+ },
+ "Microsoft.HDInsight/clusters": {
+ "friendlyName": [
+ "HDInsight - Hadoop cluster",
+ "HDInsight - Kafka cluster",
+ "HDInsight - Spark cluster",
+ "HDInsight - Storm cluster",
+ "HDInsight - ML Services cluster",
+ "HDInsight - HBase cluster"
+ ],
+ "namingConvention": [
+ "hadoop-",
+ "kafka-",
+ "spark-",
+ "storm-",
+ "mls-",
+ "hbase-"
+ ]
+ },
+ "Microsoft.HybridCompute/machines": {
+ "friendlyName": "Azure Arc enabled server",
+ "namingConvention": "arcs-"
+ },
+ "Microsoft.Insights/actionGroups": {
+ "friendlyName": "Azure Monitor action group",
+ "namingConvention": "ag-"
+ },
+ "Microsoft.Insights/components": {
+ "friendlyName": "Application Insights",
+ "namingConvention": "appi-"
+ },
+ "Microsoft.KeyVault/vaults": {
+ "friendlyName": "Key vault",
+ "namingConvention": "kv-"
+ },
+ "Microsoft.Kubernetes/connectedClusters": {
+ "friendlyName": "Azure Arc enabled Kubernetes cluster",
+ "namingConvention": "arck"
+ },
+ "Microsoft.Kusto/clusters": {
+ "friendlyName": "Azure Data Explorer cluster",
+ "namingConvention": "dec"
+ },
+ "Microsoft.Kusto/clusters/databases": {
+ "friendlyName": "Azure Data Explorer cluster database",
+ "namingConvention": "dedb"
+ },
+ "Microsoft.Logic/integrationAccounts": {
+ "friendlyName": "Integration account",
+ "namingConvention": "ia-"
+ },
+ "Microsoft.Logic/workflows": {
+ "friendlyName": "Logic apps",
+ "namingConvention": "logic-"
+ },
+ "Microsoft.MachineLearningServices/workspaces": {
+ "friendlyName": "Azure Machine Learning workspace",
+ "namingConvention": "mlw-"
+ },
+ "Microsoft.ManagedIdentity/userAssignedIdentities": {
+ "friendlyName": "Managed Identity",
+ "namingConvention": "id-"
+ },
+ "Microsoft.Management/managementGroups": {
+ "friendlyName": "Management group",
+ "namingConvention": "mg-"
+ },
+ "Microsoft.Migrate/assessmentProjects": {
+ "friendlyName": "Azure Migrate project",
+ "namingConvention": "migr-"
+ },
+ "Microsoft.Network/applicationGateways": {
+ "friendlyName": "Application gateway",
+ "namingConvention": "agw-"
+ },
+ "Microsoft.Network/applicationSecurityGroups": {
+ "friendlyName": "Application security group (ASG)",
+ "namingConvention": "asg-"
+ },
+ "Microsoft.Network/azureFirewalls": {
+ "friendlyName": "Firewall",
+ "namingConvention": "afw-"
+ },
+ "Microsoft.Network/bastionHosts": {
+ "friendlyName": "Bastion",
+ "namingConvention": "bas-"
+ },
+ "Microsoft.Network/connections": {
+ "friendlyName": "Connections",
+ "namingConvention": "con-"
+ },
+ "Microsoft.Network/dnsZones": {
+ "friendlyName": "DNS",
+ "namingConvention": "dnsz-"
+ },
+ "Microsoft.Network/expressRouteCircuits": {
+ "friendlyName": "ExpressRoute circuit",
+ "namingConvention": "erc-"
+ },
+ "Microsoft.Network/firewallPolicies": {
+ "friendlyName": [
+ "Web Application Firewall (WAF) policy",
+ "Firewall policy"
+ ],
+ "namingConvention": [
+ "waf",
+ "afwp-"
+ ]
+ },
+ "Microsoft.Network/firewallPolicies/ruleGroups": {
+ "friendlyName": "Web Application Firewall (WAF) policy rule group",
+ "namingConvention": "wafrg"
+ },
+ "Microsoft.Network/frontDoors": {
+ "friendlyName": "Front Door instance",
+ "namingConvention": "fd-"
+ },
+ "Microsoft.Network/frontdoorWebApplicationFirewallPolicies": {
+ "friendlyName": "Front Door firewall policy",
+ "namingConvention": "fdfp-"
+ },
+ "Microsoft.Network/loadBalancers": {
+ "friendlyName": [
+ "Load balancer (external)",
+ "Load balancer (internal)"
+ ],
+ "namingConvention": [
+ "lbe-",
+ "lbi-"
+ ]
+ },
+ "Microsoft.Network/loadBalancers/inboundNatRules": {
+ "friendlyName": "Load balancer rule",
+ "namingConvention": "rule-"
+ },
+ "Microsoft.Network/localNetworkGateways": {
+ "friendlyName": "Local network gateway",
+ "namingConvention": "lgw-"
+ },
+ "Microsoft.Network/natGateways": {
+ "friendlyName": "NAT gateway",
+ "namingConvention": "ng-"
+ },
+ "Microsoft.Network/networkInterfaces": {
+ "friendlyName": "Network interface (NIC)",
+ "namingConvention": "nic-"
+ },
+ "Microsoft.Network/networkSecurityGroups": {
+ "friendlyName": "Network security group (NSG)",
+ "namingConvention": "nsg-"
+ },
+ "Microsoft.Network/networkSecurityGroups/securityRules": {
+ "friendlyName": "Network security group (NSG) security rules",
+ "namingConvention": "nsgsr-"
+ },
+ "Microsoft.Network/networkWatchers": {
+ "friendlyName": "Network Watcher",
+ "namingConvention": "nw-"
+ },
+ "Microsoft.Network/privateDnsZones": {
+ "friendlyName": "DNS zone",
+ "namingConvention": "pdnsz-"
+ },
+ "Microsoft.Network/privateLinkServices": {
+ "friendlyName": "Private Link",
+ "namingConvention": "pl-"
+ },
+ "Microsoft.Network/publicIPAddresses": {
+ "friendlyName": "Public IP address",
+ "namingConvention": "pip-"
+ },
+ "Microsoft.Network/publicIPPrefixes": {
+ "friendlyName": "Public IP address prefix",
+ "namingConvention": "ippre-"
+ },
+ "Microsoft.Network/routeFilters": {
+ "friendlyName": "Route filter",
+ "namingConvention": "rf-"
+ },
+ "Microsoft.Network/routeTables": {
+ "friendlyName": "Route table",
+ "namingConvention": "rt-"
+ },
+ "Microsoft.Network/routeTables/routes": {
+ "friendlyName": "User defined route (UDR)",
+ "namingConvention": "udr-"
+ },
+ "Microsoft.Network/trafficManagerProfiles": {
+ "friendlyName": "Traffic Manager profile",
+ "namingConvention": "traf-"
+ },
+ "Microsoft.Network/virtualNetworkGateways": {
+ "friendlyName": "Virtual network gateway",
+ "namingConvention": "vgw-"
+ },
+ "Microsoft.Network/virtualNetworks": {
+ "friendlyName": "Virtual network",
+ "namingConvention": "vnet-"
+ },
+ "Microsoft.Network/virtualNetworks/subnets": {
+ "friendlyName": "Virtual network subnet",
+ "namingConvention": "snet-"
+ },
+ "Microsoft.Network/virtualNetworks/virtualNetworkPeerings": {
+ "friendlyName": "Virtual network peering",
+ "namingConvention": "peer-"
+ },
+ "Microsoft.Network/virtualWans": {
+ "friendlyName": "Virtual WAN",
+ "namingConvention": "vwan-"
+ },
+ "Microsoft.Network/vpnGateways": {
+ "friendlyName": "VPN Gateway",
+ "namingConvention": "vpng-"
+ },
+ "Microsoft.Network/vpnGateways/vpnConnections": {
+ "friendlyName": "VPN connection",
+ "namingConvention": "vcn-"
+ },
+ "Microsoft.Network/vpnGateways/vpnSites": {
+ "friendlyName": "VPN site",
+ "namingConvention": "vst-"
+ },
+ "Microsoft.NotificationHubs/namespaces": {
+ "friendlyName": "Notification Hubs namespace",
+ "namingConvention": "ntfns-"
+ },
+ "Microsoft.NotificationHubs/namespaces/notificationHubs": {
+ "friendlyName": "Notification Hubs",
+ "namingConvention": "ntf-"
+ },
+ "Microsoft.OperationalInsights/workspaces": {
+ "friendlyName": "Log Analytics workspace",
+ "namingConvention": "log-"
+ },
+ "Microsoft.PowerBIDedicated/capacities": {
+ "friendlyName": "Power BI Embedded",
+ "namingConvention": "pbi-"
+ },
+ "Microsoft.Purview/accounts": {
+ "friendlyName": "Azure Purview instance",
+ "namingConvention": "pview-"
+ },
+ "Microsoft.RecoveryServices/vaults": {
+ "friendlyName": "Recovery Services vault",
+ "namingConvention": "rsv-"
+ },
+ "Microsoft.RecoveryServices/vaults/backupPolicies": {
+ "friendlyName": "Recovery Services vault backup policy",
+ "namingConvention": "rsvbp-"
+ },
+ "Microsoft.Resources/resourceGroups": {
+ "friendlyName": "Resource group",
+ "namingConvention": "rg-"
+ },
+ "Microsoft.Search/searchServices": {
+ "friendlyName": "Azure Cognitive Search",
+ "namingConvention": "srch-"
+ },
+ "Microsoft.ServiceBus/namespaces": {
+ "friendlyName": "Service Bus",
+ "namingConvention": "sb-"
+ },
+ "Microsoft.ServiceBus/namespaces/queues": {
+ "friendlyName": "Service Bus queue",
+ "namingConvention": "sbq-"
+ },
+ "Microsoft.ServiceBus/namespaces/topics": {
+ "friendlyName": "Service Bus topic",
+ "namingConvention": "sbt-"
+ },
+ "Microsoft.serviceEndPointPolicies": {
+ "friendlyName": "Service endpoint",
+ "namingConvention": "se-"
+ },
+ "Microsoft.ServiceFabric/clusters": {
+ "friendlyName": "Service Fabric cluster",
+ "namingConvention": "sf-"
+ },
+ "Microsoft.SignalRService/SignalR": {
+ "friendlyName": "SignalR",
+ "namingConvention": "sigr"
+ },
+ "Microsoft.Sql/managedInstances": {
+ "friendlyName": "SQL Managed Instance",
+ "namingConvention": "sqlmi-"
+ },
+ "Microsoft.Sql/servers": {
+ "friendlyName": [
+ "Azure SQL Data Warehouse",
+ "Azure SQL Database server"
+ ],
+ "namingConvention": [
+ "sqldw-",
+ "sql-"
+ ]
+ },
+ "Microsoft.Sql/servers/databases": {
+ "friendlyName": [
+ "SQL Server Stretch Database",
+ "Azure SQL database"
+ ],
+ "namingConvention": [
+ "sqlstrdb-",
+ "sqldb-"
+ ]
+ },
+ "Microsoft.Storage/storageAccounts": {
+ "friendlyName": [
+ "Storage account",
+ "VM storage account"
+ ],
+ "namingConvention": [
+ "st",
+ "stvm"
+ ]
+ },
+ "Microsoft.StorSimple/managers": {
+ "friendlyName": "Azure StorSimple",
+ "namingConvention": "ssimp"
+ },
+ "Microsoft.StreamAnalytics/cluster": {
+ "friendlyName": "Azure Stream Analytics",
+ "namingConvention": "asa-"
+ },
+ "Microsoft.Synapse/workspaces": {
+ "friendlyName": [
+ "Azure Synapse Analytics Workspaces",
+ "Azure Synapse Analytics"
+ ],
+ "namingConvention": [
+ "synw",
+ "syn"
+ ]
+ },
+ "Microsoft.Synapse/workspaces/sqlPools": {
+ "friendlyName": [
+ "Azure Synapse Analytics Spark Pool",
+ "Azure Synapse Analytics SQL Dedicated Pool"
+ ],
+ "namingConvention": [
+ "synsp",
+ "syndp"
+ ]
+ },
+ "Microsoft.TimeSeriesInsights/environments": {
+ "friendlyName": "Time Series Insights environment",
+ "namingConvention": "tsi-"
+ },
+ "Microsoft.Web/serverFarms": {
+ "friendlyName": "App Service plan",
+ "namingConvention": "plan-"
+ },
+ "Microsoft.Web/sites": {
+ "friendlyName": [
+ "Web app",
+ "Function app",
+ "App Service environment"
+ ],
+ "namingConvention": [
+ "app-",
+ "func-",
+ "ase-"
+ ]
+ },
+ "Microsoft.Web/staticSites": {
+ "friendlyName": "Static web app",
+ "namingConvention": "stapp-"
+ }
+ }
+'@
+ $htCAFNamingConvention = $JSONcafResourceNaming | ConvertFrom-Json
+
+ $resourcesSubscriptionResultGroupedByType = $resourcesSubscriptionResult | Group-Object -Property type
+ foreach ($entry in $resourcesSubscriptionResultGroupedByType) {
+
+ if ($htCAFNamingConvention.($entry.Name)) {
+ $doCAFResourceNamingCheck = $true
+ $namingConvention = $htCAFNamingConvention.($entry.Name).namingConvention
+ $namingConventionFriendlyName = $htCAFNamingConvention.($entry.Name).friendlyName
+ }
+ else {
+ $doCAFResourceNamingCheck = $false
+ $namingConvention = 'n/a'
+ $namingConventionFriendlyName = 'n/a'
+ }
+
+ foreach ($resource in ($entry.Group)) {
+
+ if ($doCAFResourceNamingCheck) {
+ $cafResourceNamingCheck = 'failed'
+ $applicableNaming = $namingConvention -join "$CsvDelimiterOpposite "
+ foreach ($naming in $namingConvention) {
+ if (($resource.name).StartsWith($naming, 'CurrentCultureIgnoreCase')) {
+ $cafResourceNamingCheck = 'passed'
+ #$applicableNaming = $naming
+ }
+ }
+ }
+ else {
+ $cafResourceNamingCheck = 'n/a'
+ $applicableNaming = 'n/a'
+ }
+ $null = $script:resourcesIdsAll.Add([PSCustomObject]@{
+ subscriptionId = $scopeId
+ mgPath = $childMgMgPath
+ type = ($resource.type).ToLower()
+ id = ($resource.Id).ToLower()
+ name = ($resource.name).ToLower()
+ location = ($resource.location).ToLower()
+ tags = ($resource.tags)
+ createdTime = ($resource.createdTime)
+ changedTime = ($resource.changedTime)
+ cafResourceNamingResult = $cafResourceNamingCheck
+ cafResourceNaming = $applicableNaming
+ cafResourceNamingFriendlyName = $namingConventionFriendlyName -join "$CSVDelimiterOpposite "
+ })
+
+ if ($resource.identity.userAssignedIdentities) {
+ $resource.identity.userAssignedIdentities.psobject.properties | ForEach-Object {
+ if ((-not [string]::IsNullOrEmpty($resource.Id)) -and (-not [string]::IsNullOrEmpty($_.Value.principalId))) {
+ $hlp = ($_.Name.split('/'))
+ $hlpMiSubId = $hlp[2]
+ if ($scopeId -eq $hlpMiSubId) {
+ $miCrossSubscription = $false
+ }
+ else {
+ $miCrossSubscription = $true
+ }
+ $null = $script:arrayUserAssignedIdentities4Resources.Add([PSCustomObject]@{
+ resourceId = $resource.Id
+ resourceName = $resource.name
+ resourceMgPath = $childMgMgPath
+ resourceSubscriptionName = $scopeDisplayName
+ resourceSubscriptionId = $scopeId
+ resourceResourceGroupName = ($resource.Id -split ('/'))[4]
+ resourceType = $resource.type
+ resourceLocation = $resource.location
+ miPrincipalId = $_.Value.principalId
+ miClientId = $_.Value.clientId
+ miMgPath = $htSubscriptionsMgPath.($hlpMiSubId).pathDelimited
+ miSubscriptionName = $htSubscriptionsMgPath.($hlpMiSubId).DisplayName
+ miSubscriptionId = $hlpMiSubId
+ miResourceGroupName = $hlp[4]
+ miResourceId = $_.Name
+ miResourceName = $_.Name -replace '.*/'
+ miCrossSubscription = $miCrossSubscription
+ })
+ }
+ }
+ }
+ }
+ }
+ $endSubResourceIdsThis = Get-Date
+ $null = $script:arraySubResourcesAddArrayDuration.Add([PSCustomObject]@{
+ sub = $scopeId
+ DurationSec = (New-TimeSpan -Start $startSubResourceIdsThis -End $endSubResourceIdsThis).TotalSeconds
+ })
+
+
+ #resourceTags
+ $script:htSubscriptionTagList.($scopeId) = @{}
+ $script:htSubscriptionTagList.($scopeId).Resource = @{}
+ foreach ($tags in ($resourcesSubscriptionResult.where( { $_.Tags -and -not [String]::IsNullOrWhiteSpace($_.Tags) } )).Tags) {
+ foreach ($tagName in $tags.PSObject.Properties.Name) {
+ #resource
+ if ($htSubscriptionTagList.($scopeId).Resource.ContainsKey($tagName)) {
+ $script:htSubscriptionTagList.($scopeId).Resource."$tagName" += 1
+ }
+ else {
+ $script:htSubscriptionTagList.($scopeId).Resource."$tagName" = 1
+ }
+
+ #resourceAll
+ if ($htAllTagList.Resource.ContainsKey($tagName)) {
+ $script:htAllTagList.Resource."$tagName" += 1
+ }
+ else {
+ $script:htAllTagList.Resource."$tagName" = 1
+ }
+
+ #all
+ if ($htAllTagList.AllScopes.ContainsKey($tagName)) {
+ $script:htAllTagList.AllScopes."$tagName" += 1
+ }
+ else {
+ $script:htAllTagList.AllScopes."$tagName" = 1
+ }
+ }
+ }
+}
+$funcDataCollectionResources = $function:dataCollectionResources.ToString()
+
+function dataCollectionResourceGroups {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $subscriptionQuotaId
+ )
+
+ #https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2020-06-01
+ $currentTask = "Getting ResourceGroups for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/resourcegroups?api-version=2021-04-01"
+ $method = 'GET'
+ $resourceGroupsSubscriptionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $null = $script:resourceGroupsAll.Add([PSCustomObject]@{
+ subscriptionId = $scopeId
+ count_ = ($resourceGroupsSubscriptionResult).count
+ })
+
+ #resourceGroupTags
+ if ($azAPICallConf['htParameters'].NoResources -eq $true) {
+ $script:htSubscriptionTagList.($scopeId) = @{}
+ }
+
+ $script:htSubscriptionTagList.($scopeId).ResourceGroup = @{}
+ foreach ($tags in ($resourceGroupsSubscriptionResult.where( { $_.Tags -and -not [String]::IsNullOrWhiteSpace($_.Tags) } )).Tags) {
+ foreach ($tagName in $tags.PSObject.Properties.Name) {
+
+ #resource
+ if ($htSubscriptionTagList.($scopeId).ResourceGroup.ContainsKey($tagName)) {
+ $script:htSubscriptionTagList.($scopeId).ResourceGroup."$tagName" += 1
+ }
+ else {
+ $script:htSubscriptionTagList.($scopeId).ResourceGroup."$tagName" = 1
+ }
+
+ #resourceAll
+ if ($htAllTagList.ResourceGroup.ContainsKey($tagName)) {
+ $script:htAllTagList.ResourceGroup."$tagName" += 1
+ }
+ else {
+ $script:htAllTagList.ResourceGroup."$tagName" = 1
+ }
+
+ #all
+ if ($htAllTagList.AllScopes.ContainsKey($tagName)) {
+ $script:htAllTagList.AllScopes."$tagName" += 1
+ }
+ else {
+ $script:htAllTagList.AllScopes."$tagName" = 1
+ }
+ }
+ }
+}
+$funcDataCollectionResourceGroups = $function:dataCollectionResourceGroups.ToString()
+
+function dataCollectionResourceProviders {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayname,
+ $subscriptionQuotaId
+ )
+
+ ($script:htResourceProvidersAll).($scopeId) = @{}
+ $currentTask = "Getting ResourceProviders for Subscription: '$($scopeDisplayname)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers?api-version=2019-10-01"
+ $method = 'GET'
+ $resProvResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ ($script:htResourceProvidersAll).($scopeId).Providers = $resProvResult | Select-Object namespace, registrationState
+}
+$funcDataCollectionResourceProviders = $function:dataCollectionResourceProviders.ToString()
+
+function dataCollectionFeatures {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayname,
+ [object]$MgParentNameChain,
+ $subscriptionQuotaId
+ )
+
+ $currentTask = "Getting Features for Subscription: '$($scopeDisplayname)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Features/features?api-version=2021-07-01"
+ $method = 'GET'
+ $featuresResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $featuresResultRegistered = $featuresResult.where({ $_.properties.state -eq 'Registered' })
+
+ if ($featuresResultRegistered.Count -gt 0) {
+ foreach ($registeredFeature in $featuresResultRegistered) {
+ $null = $script:arrayFeaturesAll.Add([PSCustomObject]@{
+ subscriptionId = $registeredFeature.id.split('/')[2]
+ mgPathArray = $MgParentNameChain
+ mgPath = ($MgParentNameChain -join ',')
+ feature = $registeredFeature.name
+ })
+ }
+ }
+}
+$funcDataCollectionFeatures = $function:dataCollectionFeatures.ToString()
+
+function dataCollectionResourceLocks {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayname,
+ $subscriptionQuotaId
+ )
+
+ $currentTask = "Getting ResourceLocks for Subscription: '$($scopeDisplayname)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/locks?api-version=2016-09-01"
+ $method = 'GET'
+ $requestSubscriptionResourceLocks = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $requestSubscriptionResourceLocksCount = ($requestSubscriptionResourceLocks).Count
+ if ($requestSubscriptionResourceLocksCount -gt 0) {
+ $htTemp = @{}
+ $locksAnyLockSubscriptionCount = 0
+ $locksCannotDeleteSubscriptionCount = 0
+ $locksReadOnlySubscriptionCount = 0
+ $arrayResourceGroupsAnyLock = [System.Collections.ArrayList]@()
+ $arrayResourceGroupsCannotDeleteLock = [System.Collections.ArrayList]@()
+ $arrayResourceGroupsReadOnlyLock = [System.Collections.ArrayList]@()
+ $arrayResourcesAnyLock = [System.Collections.ArrayList]@()
+ $arrayResourcesCannotDeleteLock = [System.Collections.ArrayList]@()
+ $arrayResourcesReadOnlyLock = [System.Collections.ArrayList]@()
+ foreach ($requestSubscriptionResourceLock in $requestSubscriptionResourceLocks) {
+
+ $splitRequestSubscriptionResourceLockId = ($requestSubscriptionResourceLock.Id).Split('/')
+ switch (($splitRequestSubscriptionResourceLockId).Count - 1) {
+ #subLock
+ 6 {
+ $locksAnyLockSubscriptionCount++
+ if ($requestSubscriptionResourceLock.properties.level -eq 'CanNotDelete') {
+ $locksCannotDeleteSubscriptionCount++
+ }
+ if ($requestSubscriptionResourceLock.properties.level -eq 'ReadOnly') {
+ $locksReadOnlySubscriptionCount++
+ }
+ }
+ #rgLock
+ 8 {
+ $resourceGroupName = $splitRequestSubscriptionResourceLockId[0..4] -join '/'
+ $null = $arrayResourceGroupsAnyLock.Add([PSCustomObject]@{
+ rg = $resourceGroupName
+ })
+ if ($requestSubscriptionResourceLock.properties.level -eq 'CanNotDelete') {
+ $null = $arrayResourceGroupsCannotDeleteLock.Add([PSCustomObject]@{
+ rg = $resourceGroupName
+ })
+ }
+ if ($requestSubscriptionResourceLock.properties.level -eq 'ReadOnly') {
+ $null = $arrayResourceGroupsReadOnlyLock.Add([PSCustomObject]@{
+ rg = $resourceGroupName
+ })
+ }
+ }
+ #resLock
+ 12 {
+ $resourceId = $splitRequestSubscriptionResourceLockId[0..8] -join '/'
+ $null = $arrayResourcesAnyLock.Add([PSCustomObject]@{
+ res = $resourceId
+ })
+ if ($requestSubscriptionResourceLock.properties.level -eq 'CanNotDelete') {
+ $null = $arrayResourcesCannotDeleteLock.Add([PSCustomObject]@{
+ res = $resourceId
+ })
+ }
+ if ($requestSubscriptionResourceLock.properties.level -eq 'ReadOnly') {
+ $null = $arrayResourcesReadOnlyLock.Add([PSCustomObject]@{
+ res = $resourceId
+ })
+ }
+ }
+ }
+ }
+
+ $htTemp.SubscriptionLocksCannotDeleteCount = $locksCannotDeleteSubscriptionCount
+ $htTemp.SubscriptionLocksReadOnlyCount = $locksReadOnlySubscriptionCount
+
+ #resourceGroups
+ $htTemp.ResourceGroupsLocksCannotDeleteCount = $arrayResourceGroupsCannotDeleteLock.Count
+ $htTemp.ResourceGroupsLocksCannotDelete = $arrayResourceGroupsCannotDeleteLock
+
+ $htTemp.ResourceGroupsLocksReadOnlyCount = $arrayResourceGroupsReadOnlyLock.Count
+ $htTemp.ResourceGroupsLocksReadOnly = $arrayResourceGroupsReadOnlyLock
+
+ #resources
+ $htTemp.ResourcesLocksCannotDeleteCount = $arrayResourcesCannotDeleteLock.Count
+ $htTemp.ResourcesLocksCannotDelete = $arrayResourcesCannotDeleteLock
+
+ $htTemp.ResourcesLocksReadOnlyCount = $arrayResourcesReadOnlyLock.Count
+ $htTemp.ResourcesLocksReadOnly = $arrayResourcesReadOnlyLock
+
+ $script:htResourceLocks.($scopeId) = $htTemp
+ }
+}
+$funcDataCollectionResourceLocks = $function:dataCollectionResourceLocks.ToString()
+
+function dataCollectionTags {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $subscriptionQuotaId
+ )
+
+ $currentTask = "Getting Tags for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Resources/tags/default?api-version=2020-06-01"
+ $method = 'GET'
+ $requestSubscriptionTags = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -listenOn 'Content' -caller 'CustomDataCollection'
+
+ $script:htSubscriptionTagList.($scopeId).Subscription = @{}
+ if ($requestSubscriptionTags.properties.tags) {
+ $subscriptionTags = @()
+ ($script:htSubscriptionTags).($scopeId) = @{}
+ foreach ($tag in ($requestSubscriptionTags.properties.tags).PSObject.Properties) {
+ $subscriptionTags += "$($tag.Name)/$($tag.Value)"
+
+ ($script:htSubscriptionTags).($scopeId).($tag.Name) = $tag.Value
+ $tagName = $tag.Name
+
+ #subscription
+ if ($htSubscriptionTagList.($scopeId).Subscription.ContainsKey($tagName)) {
+ $script:htSubscriptionTagList.($scopeId).Subscription."$tagName" += 1
+ }
+ else {
+ $script:htSubscriptionTagList.($scopeId).Subscription."$tagName" = 1
+ }
+
+ #subscriptionAll
+ if ($htAllTagList.Subscription.ContainsKey($tagName)) {
+ $script:htAllTagList.Subscription."$tagName" += 1
+ }
+ else {
+ $script:htAllTagList.Subscription."$tagName" = 1
+ }
+
+ #all
+ if ($htAllTagList.AllScopes.ContainsKey($tagName)) {
+ $script:htAllTagList.AllScopes."$tagName" += 1
+ }
+ else {
+ $script:htAllTagList.AllScopes."$tagName" = 1
+ }
+
+ }
+ $subscriptionTagsCount = ($subscriptionTags).Count
+ $subscriptionTags = $subscriptionTags -join "$CsvDelimiterOpposite "
+ }
+ else {
+ $subscriptionTagsCount = 0
+ $subscriptionTags = 'none'
+ }
+ $htSubscriptionTagsReturn = @{}
+ $htSubscriptionTagsReturn.subscriptionTagsCount = $subscriptionTagsCount
+ $htSubscriptionTagsReturn.subscriptionTags = $subscriptionTags
+ return $htSubscriptionTagsReturn
+}
+$funcDataCollectionTags = $function:dataCollectionTags.ToString()
+
+function dataCollectionPolicyComplianceStates {
+ [CmdletBinding()]Param(
+ [string]$TargetMgOrSub,
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $subscriptionQuotaId
+ )
+
+
+ if ($TargetMgOrSub -eq 'Sub') {
+ $currentTask = "Getting Policy Compliance for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.PolicyInsights/policyStates/latest/summarize?api-version=2019-10-01"
+ }
+ if ($TargetMgOrSub -eq 'MG') {
+ $currentTask = "Getting Policy Compliance for Management Group: '$($scopeDisplayName)' ('$scopeId')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.PolicyInsights/policyStates/latest/summarize?api-version=2019-10-01"
+ }
+ $method = 'POST'
+ $policyComplianceResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($policyComplianceResult -eq 'ResponseTooLarge') {
+ if ($TargetMgOrSub -eq 'Sub') {
+ $script:htCachePolicyComplianceResponseTooLargeSUB.($scopeId) = @{}
+ }
+ if ($TargetMgOrSub -eq 'MG') {
+ $script:htCachePolicyComplianceResponseTooLargeMG.($scopeId) = @{}
+ }
+ }
+ elseif ($policyComplianceResult -eq 'DisallowedProvider') {
+ #nothing to do
+ }
+ else {
+ if ($TargetMgOrSub -eq 'Sub') { ($script:htCachePolicyComplianceSUB).($scopeId) = @{} }
+ if ($TargetMgOrSub -eq 'MG') { ($script:htCachePolicyComplianceMG).($scopeId) = @{} }
+ foreach ($policyAssignment in $policyComplianceResult.policyassignments | Sort-Object -Property policyAssignmentId) {
+ $policyAssignmentIdToLower = ($policyAssignment.policyAssignmentId).ToLower()
+ if ($TargetMgOrSub -eq 'Sub') { ($script:htCachePolicyComplianceSUB).($scopeId).($policyAssignmentIdToLower) = @{} }
+ if ($TargetMgOrSub -eq 'MG') { ($script:htCachePolicyComplianceMG).($scopeId).($policyAssignmentIdToLower) = @{} }
+ foreach ($policyComplianceState in $policyAssignment.results.policydetails) {
+ if ($policyComplianceState.ComplianceState -eq 'compliant') {
+ if ($TargetMgOrSub -eq 'Sub') { ($script:htCachePolicyComplianceSUB).($scopeId).($policyAssignmentIdToLower).CompliantPolicies = $policyComplianceState.count }
+ if ($TargetMgOrSub -eq 'MG') { ($script:htCachePolicyComplianceMG).($scopeId).($policyAssignmentIdToLower).CompliantPolicies = $policyComplianceState.count }
+ }
+ if ($policyComplianceState.ComplianceState -eq 'noncompliant') {
+ if ($TargetMgOrSub -eq 'Sub') { ($script:htCachePolicyComplianceSUB).($scopeId).($policyAssignmentIdToLower).NonCompliantPolicies = $policyComplianceState.count }
+ if ($TargetMgOrSub -eq 'MG') { ($script:htCachePolicyComplianceMG).($scopeId).($policyAssignmentIdToLower).NonCompliantPolicies = $policyComplianceState.count }
+ }
+ }
+
+ foreach ($resourceComplianceState in $policyAssignment.results.resourcedetails) {
+ if ($resourceComplianceState.ComplianceState -eq 'compliant') {
+ if ($TargetMgOrSub -eq 'Sub') { ($script:htCachePolicyComplianceSUB).($scopeId).($policyAssignmentIdToLower).CompliantResources = $resourceComplianceState.count }
+ if ($TargetMgOrSub -eq 'MG') { ($script:htCachePolicyComplianceMG).($scopeId).($policyAssignmentIdToLower).CompliantResources = $resourceComplianceState.count }
+
+ }
+ if ($resourceComplianceState.ComplianceState -eq 'nonCompliant') {
+ if ($TargetMgOrSub -eq 'Sub') { ($script:htCachePolicyComplianceSUB).($scopeId).($policyAssignmentIdToLower).NonCompliantResources = $resourceComplianceState.count }
+ if ($TargetMgOrSub -eq 'MG') { ($script:htCachePolicyComplianceMG).($scopeId).($policyAssignmentIdToLower).NonCompliantResources = $resourceComplianceState.count }
+
+ }
+ if ($resourceComplianceState.ComplianceState -eq 'conflict') {
+ if ($TargetMgOrSub -eq 'Sub') { ($script:htCachePolicyComplianceSUB).($scopeId).($policyAssignmentIdToLower).ConflictingResources = $resourceComplianceState.count }
+ if ($TargetMgOrSub -eq 'MG') { ($script:htCachePolicyComplianceMG).($scopeId).($policyAssignmentIdToLower).ConflictingResources = $resourceComplianceState.count }
+ }
+ }
+ }
+ }
+}
+$funcDataCollectionPolicyComplianceStates = $function:dataCollectionPolicyComplianceStates.ToString()
+
+function dataCollectionASCSecureScoreSub {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $subscriptionQuotaId
+ )
+
+ if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) {
+ $currentTask = "Getting Microsoft Defender for Cloud Secure Score for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Security/securescores?api-version=2020-01-01"
+ $method = 'GET'
+ $subASCSecureScoreResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($subASCSecureScoreResult -ne 'DisallowedProvider') {
+ $subASCSecureScoreResultASCScore = ($subASCSecureScoreResult.where({ $_.name -eq 'ascScore' }))
+ if ($subASCSecureScoreResultASCScore.count -gt 0) {
+ $secureScorePercentageRounded = [math]::Round(($subASCSecureScoreResultASCScore.properties.score.current / $subASCSecureScoreResultASCScore.properties.score.max * 100), 2)
+ $subscriptionASCSecureScore = "$($secureScorePercentageRounded)% ($($subASCSecureScoreResultASCScore.properties.score.current) of $($subASCSecureScoreResultASCScore.properties.score.max) points)"
+ }
+ else {
+ $subscriptionASCSecureScore = 'n/a'
+ }
+ }
+ else {
+ $subscriptionASCSecureScore = 'n/a'
+ }
+
+ }
+ else {
+ $subscriptionASCSecureScore = "excluded (-NoMDfCSecureScore $($azAPICallConf['htParameters'].NoMDfCSecureScore))"
+ }
+ return $subscriptionASCSecureScore
+}
+$funcDataCollectionASCSecureScoreSub = $function:dataCollectionASCSecureScoreSub.ToString()
+
+function dataCollectionBluePrintDefinitionsMG {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $hierarchyLevel,
+ $mgParentId,
+ $mgParentName,
+ $mgAscSecureScoreResult
+ )
+
+ $currentTask = "Getting Blueprint definitions for Management Group: '$($scopeDisplayName)' ('$scopeId')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Blueprint/blueprints?api-version=2018-11-01-preview"
+ $method = 'GET'
+ $scopeBlueprintDefinitionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $addRowToTableDone = $false
+ if (($scopeBlueprintDefinitionResult).count -gt 0) {
+ foreach ($blueprint in $scopeBlueprintDefinitionResult) {
+
+ if (-not $($htCacheDefinitionsBlueprint).($blueprint.Id)) {
+ ($script:htCacheDefinitionsBlueprint).($blueprint.Id) = @{}
+ }
+
+ $blueprintName = $blueprint.name
+ $blueprintId = $blueprint.Id
+ $blueprintDisplayName = $blueprint.properties.displayName
+ $blueprintDescription = $blueprint.properties.description
+ $blueprintScoped = "/providers/Microsoft.Management/managementGroups/$($scopeId)"
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $scopeDisplayName `
+ -mgId $scopeId `
+ -mgParentId $mgParentId `
+ -mgParentName $mgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -BlueprintName $blueprintName `
+ -BlueprintId $blueprintId `
+ -BlueprintDisplayName $blueprintDisplayName `
+ -BlueprintDescription $blueprintDescription `
+ -BlueprintScoped $blueprintScoped
+ }
+ }
+
+ $returnObject = @{}
+ if ($addRowToTableDone) {
+ $returnObject.'addRowToTableDone' = @{}
+ }
+ return $returnObject
+}
+$funcDataCollectionBluePrintDefinitionsMG = $function:dataCollectionBluePrintDefinitionsMG.ToString()
+
+function dataCollectionBluePrintDefinitionsSub {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $hierarchyLevel,
+ $childMgDisplayName,
+ $childMgId,
+ $childMgParentId,
+ $childMgParentName,
+ $mgAscSecureScoreResult,
+ $subscriptionQuotaId,
+ $subscriptionState,
+ $subscriptionASCSecureScore,
+ $subscriptionTags,
+ $subscriptionTagsCount
+ )
+
+ $currentTask = "Getting Blueprint definitions for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Blueprint/blueprints?api-version=2018-11-01-preview"
+ $method = 'GET'
+ $scopeBlueprintDefinitionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $addRowToTableDone = $false
+ if ($scopeBlueprintDefinitionResult -ne 'DisallowedProvider') {
+ if (($scopeBlueprintDefinitionResult).count -gt 0) {
+ foreach ($blueprint in $scopeBlueprintDefinitionResult) {
+
+ if (-not $($htCacheDefinitionsBlueprint).($blueprint.Id)) {
+ ($script:htCacheDefinitionsBlueprint).($blueprint.Id) = @{}
+ }
+
+ $blueprintName = $blueprint.name
+ $blueprintId = $blueprint.Id
+ $blueprintDisplayName = $blueprint.properties.displayName
+ $blueprintDescription = $blueprint.properties.description
+ $blueprintScoped = "/subscriptions/$($scopeId)"
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $childMgDisplayName `
+ -mgId $childMgId `
+ -mgParentId $childMgParentId `
+ -mgParentName $childMgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Subscription $scopeDisplayName `
+ -SubscriptionId $scopeId `
+ -SubscriptionQuotaId $subscriptionQuotaId `
+ -SubscriptionState $subscriptionState `
+ -SubscriptionASCSecureScore $subscriptionASCSecureScore `
+ -SubscriptionTags $subscriptionTags `
+ -SubscriptionTagsCount $subscriptionTagsCount `
+ -BlueprintName $blueprintName `
+ -BlueprintId $blueprintId `
+ -BlueprintDisplayName $blueprintDisplayName `
+ -BlueprintDescription $blueprintDescription `
+ -BlueprintScoped $blueprintScoped
+ }
+ }
+ }
+
+ $returnObject = @{}
+ if ($addRowToTableDone) {
+ $returnObject.'addRowToTableDone' = @{}
+ }
+ return $returnObject
+}
+$funcDataCollectionBluePrintDefinitionsSub = $function:dataCollectionBluePrintDefinitionsSub.ToString()
+
+function dataCollectionBluePrintAssignmentsSub {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $hierarchyLevel,
+ $childMgDisplayName,
+ $childMgId,
+ $childMgParentId,
+ $childMgParentName,
+ $mgAscSecureScoreResult,
+ $subscriptionQuotaId,
+ $subscriptionState,
+ $subscriptionASCSecureScore,
+ $subscriptionTags,
+ $subscriptionTagsCount
+ )
+
+ $currentTask = "Getting Blueprint assignments for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Blueprint/blueprintAssignments?api-version=2018-11-01-preview"
+ $method = 'GET'
+ $subscriptionBlueprintAssignmentsResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $addRowToTableDone = $false
+ if ($subscriptionBlueprintAssignmentsResult -ne 'DisallowedProvider') {
+ if (($subscriptionBlueprintAssignmentsResult).count -gt 0) {
+ foreach ($subscriptionBlueprintAssignment in $subscriptionBlueprintAssignmentsResult) {
+
+ if (-not ($htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id)) {
+ ($script:htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id) = @{}
+ ($script:htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id) = $subscriptionBlueprintAssignment
+ }
+
+ if (($subscriptionBlueprintAssignment.properties.blueprintId) -like '/subscriptions/*') {
+ $blueprintScope = $subscriptionBlueprintAssignment.properties.blueprintId -replace '/providers/Microsoft.Blueprint/blueprints/.*', ''
+ $blueprintName = $subscriptionBlueprintAssignment.properties.blueprintId -replace '.*/blueprints/', '' -replace '/versions/.*', ''
+ }
+ if (($subscriptionBlueprintAssignment.properties.blueprintId) -like '/providers/Microsoft.Management/managementGroups/*') {
+ $blueprintScope = $subscriptionBlueprintAssignment.properties.blueprintId -replace '/providers/Microsoft.Blueprint/blueprints/.*', ''
+ $blueprintName = $subscriptionBlueprintAssignment.properties.blueprintId -replace '.*/blueprints/', '' -replace '/versions/.*', ''
+ }
+
+ $currentTask = "Getting Blueprint definitions related to Blueprint assignments for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/$($blueprintScope)/providers/Microsoft.Blueprint/blueprints/$($blueprintName)?api-version=2018-11-01-preview"
+ $method = 'GET'
+ $subscriptionBlueprintDefinitionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -listenOn 'Content' -caller 'CustomDataCollection'
+
+ if ($subscriptionBlueprintDefinitionResult -eq 'BlueprintNotFound') {
+ $blueprintName = 'BlueprintNotFound'
+ $blueprintId = 'BlueprintNotFound'
+ $blueprintAssignmentVersion = $subscriptionBlueprintAssignment.properties.blueprintId -replace '.*/'
+ $blueprintDisplayName = 'BlueprintNotFound'
+ $blueprintDescription = 'BlueprintNotFound'
+ $blueprintScoped = $blueprintScope
+ $blueprintAssignmentId = $subscriptionBlueprintAssignmentsResult.Id
+ }
+ else {
+ $blueprintName = $subscriptionBlueprintDefinitionResult.name
+ $blueprintId = $subscriptionBlueprintDefinitionResult.Id
+ $blueprintAssignmentVersion = $subscriptionBlueprintAssignment.properties.blueprintId -replace '.*/'
+ $blueprintDisplayName = $subscriptionBlueprintDefinitionResult.properties.displayName
+ $blueprintDescription = $subscriptionBlueprintDefinitionResult.properties.description
+ $blueprintScoped = $blueprintScope
+ $blueprintAssignmentId = $subscriptionBlueprintAssignmentsResult.Id
+ }
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $childMgDisplayName `
+ -mgId $childMgId `
+ -mgParentId $childMgParentId `
+ -mgParentName $childMgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Subscription $scopeDisplayName `
+ -SubscriptionId $scopeId `
+ -SubscriptionQuotaId $subscriptionQuotaId `
+ -SubscriptionState $subscriptionState `
+ -SubscriptionASCSecureScore $subscriptionASCSecureScore `
+ -SubscriptionTags $subscriptionTags `
+ -SubscriptionTagsCount $subscriptionTagsCount `
+ -BlueprintName $blueprintName `
+ -BlueprintId $blueprintId `
+ -BlueprintDisplayName $blueprintDisplayName `
+ -BlueprintDescription $blueprintDescription `
+ -BlueprintScoped $blueprintScoped `
+ -BlueprintAssignmentVersion $blueprintAssignmentVersion `
+ -BlueprintAssignmentId $blueprintAssignmentId
+ }
+ }
+ }
+
+ $returnObject = @{}
+ if ($addRowToTableDone) {
+ $returnObject.'addRowToTableDone' = @{}
+ }
+ return $returnObject
+}
+$funcDataCollectionBluePrintAssignmentsSub = $function:dataCollectionBluePrintAssignmentsSub.ToString()
+
+function dataCollectionPolicyExemptions {
+ [CmdletBinding()]Param(
+ [string]$TargetMgOrSub,
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $subscriptionQuotaId
+ )
+
+ if ($TargetMgOrSub -eq 'Sub') {
+ $currentTask = "Getting Policy exemptions for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyExemptions?api-version=2020-07-01-preview"
+ }
+ if ($TargetMgOrSub -eq 'MG') {
+ $currentTask = "Getting Policy exemptions for Management Group: '$($scopeDisplayName)' ('$scopeId')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/policyExemptions?api-version=2020-07-01-preview&`$filter=atScope()"
+ }
+ $method = 'GET'
+ $requestPolicyExemptionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $requestPolicyExemptionAPICount = ($requestPolicyExemptionAPI).Count
+ if ($requestPolicyExemptionAPICount -gt 0) {
+ foreach ($exemption in $requestPolicyExemptionAPI) {
+ if (-not $htPolicyAssignmentExemptions.($exemption.Id)) {
+ $script:htPolicyAssignmentExemptions.($exemption.Id) = @{}
+ $script:htPolicyAssignmentExemptions.($exemption.Id).exemption = $exemption
+ }
+ }
+ }
+}
+$funcDataCollectionPolicyExemptions = $function:dataCollectionPolicyExemptions.ToString()
+
+function dataCollectionPolicyDefinitions {
+ [CmdletBinding()]Param(
+ [string]$TargetMgOrSub,
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $subscriptionQuotaId
+ )
+
+ if ($TargetMgOrSub -eq 'Sub') {
+ $currentTask = "Getting Policy definitions for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'"
+ }
+ if ($TargetMgOrSub -eq 'MG') {
+ $currentTask = "Getting Policy definitions for Management Group: '$($scopeDisplayName)' ('$scopeId')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementgroups/$($scopeId)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'"
+ }
+ $method = 'GET'
+ $requestPolicyDefinitionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $scopePolicyDefinitions = $requestPolicyDefinitionAPI.where( { $_.properties.policyType -eq 'custom' } )
+
+ if ($TargetMgOrSub -eq 'Sub') {
+ $PolicyDefinitionsScopedCount = (($scopePolicyDefinitions.where( { ($_.id) -like "/subscriptions/$($scopeId)/*" } ))).count
+ }
+ if ($TargetMgOrSub -eq 'MG') {
+ $PolicyDefinitionsScopedCount = (($scopePolicyDefinitions.where( { ($_.id) -like "/providers/Microsoft.Management/managementGroups/$($scopeId)/*" } ))).count
+ }
+
+ foreach ($scopePolicyDefinition in $scopePolicyDefinitions) {
+ $hlpPolicyDefinitionId = ($scopePolicyDefinition.id).ToLower()
+
+ $doIt = $true
+ if ($TargetMgOrSub -eq 'MG') {
+ $doIt = $false
+ if ($hlpPolicyDefinitionId -like "/providers/Microsoft.Management/managementGroups/$($scopeId)/*" -and $hlpPolicyDefinitionId -notlike "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/*") {
+ $doIt = $true
+ }
+ if ($scopeId -eq $ManagementGroupId) {
+ $doIt = $true
+ }
+ }
+
+ if ($doIt) {
+
+ if (-not $script:htCacheDefinitionsPolicy.($hlpPolicyDefinitionId)) {
+ if (($scopePolicyDefinition.Properties.description).length -eq 0) {
+ $policyDefinitionDescription = 'no description given'
+ }
+ else {
+ $policyDefinitionDescription = $scopePolicyDefinition.Properties.description
+ }
+
+ $htTemp = @{}
+ $htTemp.Id = $hlpPolicyDefinitionId
+
+ if ($hlpPolicyDefinitionId -like '/providers/Microsoft.Management/managementGroups/*') {
+ $htTemp.Scope = (($hlpPolicyDefinitionId).split('/'))[0..4] -join '/'
+ $htTemp.ScopeMgSub = 'Mg'
+ $htTemp.ScopeId = (($hlpPolicyDefinitionId).split('/'))[4]
+ $htTemp.ScopeMGLevel = $htManagementGroupsMgPath.((($hlpPolicyDefinitionId).split('/'))[4]).ParentNameChainCount
+ }
+
+ if ($hlpPolicyDefinitionId -like '/subscriptions/*') {
+ $htTemp.Scope = (($hlpPolicyDefinitionId).split('/'))[0..2] -join '/'
+ $htTemp.ScopeMgSub = 'Sub'
+ $htTemp.ScopeId = (($hlpPolicyDefinitionId).split('/'))[2]
+ $htTemp.ScopeMGLevel = $htSubscriptionsMgPath.((($hlpPolicyDefinitionId).split('/'))[2]).level
+ }
+
+
+ if ($azAPICallConf['htParameters'].NoALZPolicyVersionChecker -eq $false) {
+
+ $policyJsonRule = $scopePolicyDefinition.properties.policyRule | ConvertTo-Json -Depth 99
+ $hash = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonRule))
+ $stringHash = [System.BitConverter]::ToString($hash)
+
+ if ($alzPolicies.($scopePolicyDefinition.name) -or $alzPolicyHashes.($stringHash) -or $scopePolicyDefinition.properties.metadata.source -eq 'https://github.com/Azure/Enterprise-Scale/') {
+
+ $policyHashMatch = $false
+ if ($alzPolicyHashes.($stringHash)) {
+ $policyHashMatch = $true
+ $htTemp.ALZ = 'true'
+ if ($alzPolicyHashes.($stringHash).metadataSource -eq 'https://github.com/Azure/Enterprise-Scale/' -and $alzPolicyHashes.($stringHash).metadataSource -eq $scopePolicyDefinition.properties.metadata.source -and $alzPolicyHashes.($stringHash).policyName -eq $scopePolicyDefinition.name) {
+ $htTemp.ALZIdentificationLevel = 'PolicyRule Hash, Policy Name, MetaData Tag'
+ }
+ elseif ($alzPolicyHashes.($stringHash).policyName -eq $scopePolicyDefinition.name) {
+ $htTemp.ALZIdentificationLevel = 'PolicyRule Hash, Policy Name'
+ }
+ else {
+ $htTemp.ALZIdentificationLevel = 'PolicyRule Hash'
+ }
+ $htTemp.ALZPolicyName = $alzPolicyHashes.($stringHash).policyName
+ $htTemp.hash = $stringHash
+ if ($alzpolicies.($alzPolicyHashes.($stringHash).policyName).status -eq 'obsolete') {
+ $htTemp.ALZState = 'obsolete'
+ $htTemp.ALZLatestVer = ''
+ }
+ else {
+ if ($scopePolicyDefinition.Properties.metadata.version) {
+ if ($alzpolicies.($alzPolicyHashes.($stringHash).policyName).latestVersion -eq $scopePolicyDefinition.Properties.metadata.version) {
+ $htTemp.ALZState = 'upToDate'
+ }
+ else {
+ if ($alzpolicies.($alzPolicyHashes.($stringHash).policyName).latestVersion -like '*-deprecated') {
+ $htTemp.ALZState = 'deprecated'
+ }
+ else {
+ $htTemp.ALZState = 'outDated'
+ }
+ }
+ }
+ else {
+ $htTemp.ALZState = 'potentiallyOutDated (no ver)'
+ }
+ $htTemp.ALZLatestVer = $alzpolicies.($alzPolicyHashes.($stringHash).policyName).latestVersion
+ }
+ }
+
+ $policyNameMatch = $false
+ if ($alzPolicies.($scopePolicyDefinition.name) -and -not $policyHashMatch) {
+ $policyNameMatch = $true
+ $htTemp.ALZ = 'true'
+ if ($alzPolicies.($scopePolicyDefinition.name).metadataSource -eq 'https://github.com/Azure/Enterprise-Scale/' -and $alzPolicies.($scopePolicyDefinition.name).metadataSource -eq $scopePolicyDefinition.properties.metadata.source) {
+ $htTemp.ALZIdentificationLevel = 'Policy Name, MetaData Tag'
+ }
+ else {
+ $htTemp.ALZIdentificationLevel = 'Policy Name'
+ }
+
+ $htTemp.ALZPolicyName = $alzPolicies.($scopePolicyDefinition.name).policyName
+ $htTemp.hash = $stringHash
+ if ($alzPolicies.($scopePolicyDefinition.name).status -eq 'obsolete') {
+ $htTemp.ALZState = 'obsolete'
+ $htTemp.ALZLatestVer = ''
+ }
+ else {
+ if ($scopePolicyDefinition.Properties.metadata.version) {
+ if ($alzPolicies.($scopePolicyDefinition.name).latestVersion -eq $scopePolicyDefinition.Properties.metadata.version) {
+ $htTemp.ALZState = 'upToDate'
+ }
+ else {
+ if ($alzPolicies.($scopePolicyDefinition.name).latestVersion -like '*-deprecated') {
+ $htTemp.ALZState = 'deprecated'
+ }
+ else {
+ $htTemp.ALZState = 'outDated'
+ }
+ }
+ }
+ else {
+ $htTemp.ALZState = 'potentiallyOutDated (no ver)'
+ }
+
+ $htTemp.ALZLatestVer = $alzPolicies.($scopePolicyDefinition.name).latestVersion
+ }
+ }
+
+ if ($scopePolicyDefinition.properties.metadata.source -eq 'https://github.com/Azure/Enterprise-Scale/' -and -not $policyHashMatch -and -not $policyNameMatch) {
+ $htTemp.ALZ = 'true'
+ $htTemp.ALZState = 'unknown'
+ $htTemp.ALZLatestVer = ''
+ $htTemp.ALZIdentificationLevel = 'MetaData Tag'
+ $htTemp.ALZPolicyName = ''
+ $htTemp.hash = $stringHash
+ }
+ }
+ else {
+ $htTemp.ALZ = 'false'
+ $htTemp.ALZState = ''
+ $htTemp.ALZLatestVer = ''
+ $htTemp.ALZIdentificationLevel = ''
+ $htTemp.ALZPolicyName = ''
+ $htTemp.hash = $stringHash
+ }
+ }
+ else {
+ $htTemp.ALZ = 'n/a'
+ $htTemp.ALZState = ''
+ $htTemp.ALZLatestVer = ''
+ $htTemp.ALZIdentificationLevel = ''
+ $htTemp.ALZPolicyName = ''
+ $htTemp.hash = ''
+ }
+
+ $htTemp.DisplayName = $($scopePolicyDefinition.Properties.displayname)
+ $htTemp.Name = $scopePolicyDefinition.Name
+ $htTemp.Description = $($policyDefinitionDescription)
+ $htTemp.Type = $($scopePolicyDefinition.Properties.policyType)
+ $htTemp.Category = $($scopePolicyDefinition.Properties.metadata.category)
+ if ($scopePolicyDefinition.Properties.metadata.version) {
+ $htTemp.Version = $($scopePolicyDefinition.Properties.metadata.version)
+ }
+ else {
+ $htTemp.Version = 'n/a'
+ }
+ $htTemp.PolicyDefinitionId = $hlpPolicyDefinitionId
+ if ($scopePolicyDefinition.Properties.metadata.deprecated -eq $true -or $scopePolicyDefinition.Properties.displayname -like "``[Deprecated``]*") {
+ $htTemp.Deprecated = $scopePolicyDefinition.Properties.metadata.deprecated
+ }
+ else {
+ $htTemp.Deprecated = $false
+ }
+ if ($scopePolicyDefinition.Properties.metadata.preview -eq $true -or $scopePolicyDefinition.Properties.displayname -like "``[*Preview``]*") {
+ $htTemp.Preview = $scopePolicyDefinition.Properties.metadata.preview
+ }
+ else {
+ $htTemp.Preview = $false
+ }
+
+ #region effect
+ $htEffectDetected = detectPolicyEffect -policyDefinition $scopePolicyDefinition
+ $htTemp.effectDefaultValue = $htEffectDetected.defaultValue
+ $htTemp.effectAllowedValue = $htEffectDetected.allowedValues
+ $htTemp.effectFixedValue = $htEffectDetected.fixedValue
+ #endregion effect
+
+ $htTemp.Json = $scopePolicyDefinition
+ $script:htCacheDefinitionsPolicy.($hlpPolicyDefinitionId) = $htTemp
+ }
+
+
+ if (-not [string]::IsNullOrWhiteSpace($scopePolicyDefinition.properties.policyRule.then.details.roleDefinitionIds)) {
+ $script:htCacheDefinitionsPolicy.($hlpPolicyDefinitionId).RoleDefinitionIds = $scopePolicyDefinition.properties.policyRule.then.details.roleDefinitionIds
+ foreach ($roledefinitionId in $scopePolicyDefinition.properties.policyRule.then.details.roleDefinitionIds) {
+ if (-not [string]::IsNullOrEmpty($roledefinitionId)) {
+ if (-not $htRoleDefinitionIdsUsedInPolicy.($roledefinitionId)) {
+ $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId) = @{}
+ $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = [array]$hlpPolicyDefinitionId
+ }
+ else {
+ $usedInPolicies = $htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies
+ $usedInPolicies += $hlpPolicyDefinitionId
+ $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = $usedInPolicies
+ }
+ }
+ else {
+ Write-Host "$currentTask $($hlpPolicyDefinitionId) Finding: empty roleDefinitionId in roledefinitionIds"
+ }
+ }
+ }
+ else {
+ $script:htCacheDefinitionsPolicy.($hlpPolicyDefinitionId).RoleDefinitionIds = 'n/a'
+ }
+
+ #region namingValidation
+ if (-not [string]::IsNullOrEmpty($scopePolicyDefinition.Properties.displayname)) {
+ $namingValidationResult = NamingValidation -toCheck $scopePolicyDefinition.Properties.displayname
+ if ($namingValidationResult.Count -gt 0) {
+ if (-not $script:htNamingValidation.Policy.($hlpPolicyDefinitionId)) {
+ $script:htNamingValidation.Policy.($hlpPolicyDefinitionId) = @{}
+ }
+ $script:htNamingValidation.Policy.($hlpPolicyDefinitionId).displayNameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.Policy.($hlpPolicyDefinitionId).displayName = $scopePolicyDefinition.Properties.displayname
+ }
+ }
+ if (-not [string]::IsNullOrEmpty($scopePolicyDefinition.Name)) {
+ $namingValidationResult = NamingValidation -toCheck $scopePolicyDefinition.Name
+ if ($namingValidationResult.Count -gt 0) {
+ if (-not $script:htNamingValidation.Policy.($hlpPolicyDefinitionId)) {
+ $script:htNamingValidation.Policy.($hlpPolicyDefinitionId) = @{}
+ }
+ $script:htNamingValidation.Policy.($hlpPolicyDefinitionId).nameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.Policy.($hlpPolicyDefinitionId).name = $scopePolicyDefinition.Name
+ }
+ }
+ #endregion namingValidation
+ }
+ }
+
+ $returnObject = @{}
+ $returnObject.'PolicyDefinitionsScopedCount' = $PolicyDefinitionsScopedCount
+ return $returnObject
+}
+$funcDataCollectionPolicyDefinitions = $function:dataCollectionPolicyDefinitions.ToString()
+
+function dataCollectionPolicySetDefinitions {
+ [CmdletBinding()]Param(
+ [string]$TargetMgOrSub,
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $subscriptionQuotaId
+ )
+
+ if ($TargetMgOrSub -eq 'Sub') {
+ $currentTask = "Getting PolicySet definitions for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'"
+ }
+ if ($TargetMgOrSub -eq 'MG') {
+ $currentTask = "Getting PolicySet definitions for Management Group: '$($scopeDisplayName)' ('$scopeId')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementgroups/$($scopeId)/providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'"
+ }
+ $method = 'GET'
+ $requestPolicySetDefinitionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $scopePolicySetDefinitions = $requestPolicySetDefinitionAPI.where( { $_.properties.policyType -eq 'custom' } )
+ if ($TargetMgOrSub -eq 'Sub') {
+ $PolicySetDefinitionsScopedCount = ($scopePolicySetDefinitions.where( { ($_.Id) -like "/subscriptions/$($scopeId)/*" } )).count
+ }
+ if ($TargetMgOrSub -eq 'MG') {
+ $PolicySetDefinitionsScopedCount = (($scopePolicySetDefinitions.where( { ($_.Id) -like "/providers/Microsoft.Management/managementGroups/$($scopeId)/*" } ))).count
+ }
+
+ foreach ($scopePolicySetDefinition in $scopePolicySetDefinitions) {
+ $hlpPolicySetDefinitionId = ($scopePolicySetDefinition.id).ToLower()
+
+ $doIt = $true
+ if ($TargetMgOrSub -eq 'MG') {
+ $doIt = $false
+ if ($hlpPolicySetDefinitionId -like "/providers/Microsoft.Management/managementGroups/$($scopeId)/*" -and $hlpPolicySetDefinitionId -notlike "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/*") {
+ $doIt = $true
+ }
+ if ($scopeId -eq $ManagementGroupId) {
+ $doIt = $true
+ }
+ }
+
+ if ($doIt) {
+ if (-not $script:htCacheDefinitionsPolicySet.($hlpPolicySetDefinitionId)) {
+ if (($scopePolicySetDefinition.Properties.description).length -eq 0) {
+ $policySetDefinitionDescription = 'no description given'
+ }
+ else {
+ $policySetDefinitionDescription = $scopePolicySetDefinition.Properties.description
+ }
+
+ $htTemp = @{}
+ $htTemp.Id = $hlpPolicySetDefinitionId
+ if ($scopePolicySetDefinition.Id -like '/providers/Microsoft.Management/managementGroups/*') {
+ $htTemp.Scope = (($scopePolicySetDefinition.Id).split('/'))[0..4] -join '/'
+ $htTemp.ScopeMgSub = 'Mg'
+ $htTemp.ScopeId = (($scopePolicySetDefinition.Id).split('/'))[4]
+ $htTemp.ScopeMGLevel = $htManagementGroupsMgPath.((($scopePolicySetDefinition.Id).split('/'))[4]).ParentNameChainCount
+ }
+
+ if ($scopePolicySetDefinition.Id -like '/subscriptions/*') {
+ $htTemp.Scope = (($scopePolicySetDefinition.Id).split('/'))[0..2] -join '/'
+ $htTemp.ScopeMgSub = 'Sub'
+ $htTemp.ScopeId = (($scopePolicySetDefinition.Id).split('/'))[2]
+ $htTemp.ScopeMGLevel = $htSubscriptionsMgPath.((($scopePolicySetDefinition.Id).split('/'))[2]).level
+ }
+
+ if ($azAPICallConf['htParameters'].NoALZPolicyVersionChecker -eq $false) {
+
+ $policyJsonParameters = $scopePolicySetDefinition.properties.parameters | ConvertTo-Json -Depth 99
+ $policyJsonPolicyDefinitions = $scopePolicySetDefinition.properties.policyDefinitions | ConvertTo-Json -Depth 99
+ $hashParameters = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonParameters))
+ $stringHashParameters = [System.BitConverter]::ToString($hashParameters)
+ $hashPolicyDefinitions = [System.Security.Cryptography.HashAlgorithm]::Create('sha256').ComputeHash([System.Text.Encoding]::UTF8.GetBytes($policyJsonPolicyDefinitions))
+ $stringHashPolicyDefinitions = [System.BitConverter]::ToString($hashPolicyDefinitions)
+ $stringHash = "$($stringHashParameters)_$($stringHashPolicyDefinitions)"
+
+ if ($alzPolicySets.($scopePolicySetDefinition.name) -or $allESLZPolicySetHashes.($stringHash) -or $scopePolicySetDefinition.properties.metadata.source -eq 'https://github.com/Azure/Enterprise-Scale/') {
+
+ $policySetHashMatch = $false
+ if ($alzPolicySetHashes.($stringHash)) {
+ $policySetHashMatch = $true
+ $htTemp.ALZ = 'true'
+ if ($allESLZPolicySetHashes.($stringHash).metadataSource -eq 'https://github.com/Azure/Enterprise-Scale/' -and $allESLZPolicySetHashes.($stringHash).metadataSource -eq $scopePolicySetDefinition.properties.metadata.source -and $allESLZPolicySetHashes.($stringHash).policySetName -eq $scopePolicySetDefinition.name) {
+ $htTemp.ALZIdentificationLevel = 'PolicySet Hash, PolicySet Name, MetaData Tag'
+ }
+ elseif ($allESLZPolicySetHashes.($stringHash).policySetName -eq $scopePolicySetDefinition.name) {
+ $htTemp.ALZIdentificationLevel = 'PolicySet Hash, PolicySet Name'
+ }
+ else {
+ $htTemp.ALZIdentificationLevel = 'PolicySet Hash'
+ }
+ $htTemp.ALZPolicySetName = $alzPolicySetHashes.($stringHash).policySetName
+ if ($alzPolicySetHashes.($stringHash).status -eq 'obsolete') {
+ $htTemp.ALZState = 'obsolete'
+ $htTemp.ALZLatestVer = ''
+ }
+ else {
+ if ($alzPolicySetHashes.($stringHash).latestVersion -eq $scopePolicySetDefinition.Properties.metadata.version) {
+ $htTemp.ALZState = 'upToDate'
+ }
+ else {
+ if ($alzPolicySetHashes.($stringHash).latestVersion -like '*-deprecated') {
+ $htTemp.ALZState = 'deprecated'
+ }
+ else {
+ $htTemp.ALZState = 'outDated'
+ }
+ }
+ $htTemp.ALZLatestVer = $alzPolicySetHashes.($stringHash).latestVersion
+ }
+ }
+
+ $policySetNameMatch = $false
+ if ($alzPolicySets.($scopePolicySetDefinition.name) -and -not $policySetHashMatch) {
+ $policySetNameMatch = $true
+ $htTemp.ALZ = 'true'
+ if ($alzPolicySets.($scopePolicySetDefinition.name).metadataSource -eq 'https://github.com/Azure/Enterprise-Scale/' -and $alzPolicySets.($scopePolicySetDefinition.name).metadataSource -eq $scopePolicySetDefinition.properties.metadata.source) {
+ $htTemp.ALZIdentificationLevel = 'PolicySet Name, MetaData Tag'
+ }
+ else {
+ $htTemp.ALZIdentificationLevel = 'PolicySet Name'
+ }
+ $htTemp.ALZPolicySetName = $alzPolicySets.($scopePolicySetDefinition.name).policySetName
+ if ($alzPolicySets.($scopePolicySetDefinition.name).status -eq 'obsolete') {
+ $htTemp.ALZState = 'obsolete'
+ $htTemp.ALZLatestVer = ''
+ }
+ else {
+ if ($alzPolicySets.($scopePolicySetDefinition.name).latestVersion -eq $scopePolicySetDefinition.Properties.metadata.version) {
+ $htTemp.ALZState = 'upToDate'
+ }
+ else {
+ if ($alzPolicySets.($scopePolicySetDefinition.name).latestVersion -like '*-deprecated') {
+ $htTemp.ALZState = 'deprecated'
+ }
+ else {
+ $htTemp.ALZState = 'outDated'
+ }
+ }
+ $htTemp.ALZLatestVer = $alzPolicySets.($scopePolicySetDefinition.name).latestVersion
+ }
+ }
+
+ if ($scopePolicySetDefinition.properties.metadata.source -eq 'https://github.com/Azure/Enterprise-Scale/' -and -not $policySetHashMatch -and -not $policySetNameMatch) {
+ $htTemp.ALZ = 'true'
+ $htTemp.ALZState = 'unknown'
+ $htTemp.ALZLatestVer = ''
+ $htTemp.ALZIdentificationLevel = 'MetaData Tag'
+ $htTemp.ALZPolicyName = ''
+ $htTemp.hash = $stringHash
+ }
+ }
+ else {
+ $htTemp.ALZ = 'false'
+ $htTemp.ALZState = ''
+ $htTemp.ALZLatestVer = ''
+ $htTemp.ALZIdentificationLevel = ''
+ $htTemp.ALZPolicySetName = ''
+ }
+ }
+ else {
+ $htTemp.ALZ = 'n/a'
+ $htTemp.ALZState = ''
+ $htTemp.ALZLatestVer = ''
+ $htTemp.ALZIdentificationLevel = ''
+ $htTemp.ALZPolicySetName = ''
+ }
+
+ $htTemp.DisplayName = $($scopePolicySetDefinition.Properties.displayname)
+ $htTemp.Name = $scopePolicySetDefinition.Name
+ $htTemp.Description = $($policySetDefinitionDescription)
+ $htTemp.Type = $($scopePolicySetDefinition.Properties.policyType)
+ $htTemp.Category = $($scopePolicySetDefinition.Properties.metadata.category)
+ if ($scopePolicySetDefinition.Properties.metadata.version) {
+ $htTemp.Version = $($scopePolicySetDefinition.Properties.metadata.version)
+ }
+ else {
+ $htTemp.Version = 'n/a'
+ }
+ $htTemp.PolicyDefinitionId = $hlpPolicySetDefinitionId
+ $arrayPolicySetPolicyIdsToLower = @()
+ $htPolicySetPolicyRefIds = @{}
+ $arrayPolicySetPolicyIdsToLower = foreach ($policySetPolicy in $scopePolicySetDefinition.properties.policydefinitions) {
+ $($policySetPolicy.policyDefinitionId).ToLower()
+ $htPolicySetPolicyRefIds.($policySetPolicy.policyDefinitionReferenceId) = ($policySetPolicy.policyDefinitionId)
+ }
+ $htTemp.PolicySetPolicyIds = $arrayPolicySetPolicyIdsToLower
+ $htTemp.PolicySetPolicyRefIds = $htPolicySetPolicyRefIds
+ $htTemp.Json = $scopePolicySetDefinition
+ if ($scopePolicySetDefinition.Properties.metadata.deprecated -eq $true -or $scopePolicySetDefinition.Properties.displayname -like "``[Deprecated``]*") {
+ $htTemp.Deprecated = $scopePolicySetDefinition.Properties.metadata.deprecated
+ }
+ else {
+ $htTemp.Deprecated = $false
+ }
+ if ($scopePolicySetDefinition.Properties.metadata.preview -eq $true -or $scopePolicySetDefinition.Properties.displayname -like "``[*Preview``]*") {
+ $htTemp.Preview = $scopePolicySetDefinition.Properties.metadata.preview
+ }
+ else {
+ $htTemp.Preview = $false
+ }
+ ($script:htCacheDefinitionsPolicySet).($hlpPolicySetDefinitionId) = $htTemp
+ }
+ #namingValidation
+ if (-not [string]::IsNullOrEmpty($scopePolicySetDefinition.Properties.displayname)) {
+ $namingValidationResult = NamingValidation -toCheck $scopePolicySetDefinition.Properties.displayname
+ if ($namingValidationResult.Count -gt 0) {
+ if (-not $script:htNamingValidation.PolicySet.($scopePolicySetDefinition.Id)) {
+ $script:htNamingValidation.PolicySet.($scopePolicySetDefinition.Id) = @{}
+ }
+ $script:htNamingValidation.PolicySet.($scopePolicySetDefinition.Id).displayNameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.PolicySet.($scopePolicySetDefinition.Id).displayName = $scopePolicySetDefinition.Properties.displayname
+ }
+ }
+ if (-not [string]::IsNullOrEmpty($scopePolicySetDefinition.Name)) {
+ $namingValidationResult = NamingValidation -toCheck $scopePolicySetDefinition.Name
+ if ($namingValidationResult.Count -gt 0) {
+ if (-not $script:htNamingValidation.PolicySet.($scopePolicySetDefinition.Id)) {
+ $script:htNamingValidation.PolicySet.($scopePolicySetDefinition.Id) = @{}
+ }
+ $script:htNamingValidation.PolicySet.($scopePolicySetDefinition.Id).nameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.PolicySet.($scopePolicySetDefinition.Id).name = $scopePolicySetDefinition.Name
+ }
+ }
+ }
+ }
+
+ $returnObject = @{}
+ $returnObject.'PolicySetDefinitionsScopedCount' = $PolicySetDefinitionsScopedCount
+ return $returnObject
+}
+$funcDataCollectionPolicySetDefinitions = $function:dataCollectionPolicySetDefinitions.ToString()
+
+function dataCollectionPolicyAssignmentsMG {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $hierarchyLevel,
+ $mgParentId,
+ $mgParentName,
+ $mgAscSecureScoreResult,
+ $PolicyDefinitionsScopedCount,
+ $PolicySetDefinitionsScopedCount
+ )
+
+ $addRowToTableDone = $false
+ $currentTask = "Getting Policy assignments for Management Group: '$($scopeDisplayName)' ('$($scopeId)')"
+ if ($azAPICallConf['htParameters'].LargeTenant -eq $false -or $azAPICallConf['htParameters'].PolicyAtScopeOnly -eq $false) {
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementgroups/$($scopeId)/providers/Microsoft.Authorization/policyAssignments?`$filter=atscope()&api-version=2021-06-01"
+ }
+ else {
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementgroups/$($scopeId)/providers/Microsoft.Authorization/policyAssignments?`$filter=atExactScope()&api-version=2021-06-01"
+ }
+ $method = 'GET'
+ $L0mgmtGroupPolicyAssignments = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $L0mgmtGroupPolicyAssignmentsPolicyCount = (($L0mgmtGroupPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' } ))).count
+ $L0mgmtGroupPolicyAssignmentsPolicySetCount = (($L0mgmtGroupPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/' } ))).count
+ $L0mgmtGroupPolicyAssignmentsPolicyAtScopeCount = (($L0mgmtGroupPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -and $_.Id -match "/providers/Microsoft.Management/managementGroups/$($scopeId)" } ))).count
+ $L0mgmtGroupPolicyAssignmentsPolicySetAtScopeCount = (($L0mgmtGroupPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/' -and $_.Id -match "/providers/Microsoft.Management/managementGroups/$($scopeId)" } ))).count
+ $L0mgmtGroupPolicyAssignmentsPolicyAndPolicySetAtScopeCount = ($L0mgmtGroupPolicyAssignmentsPolicyAtScopeCount + $L0mgmtGroupPolicyAssignmentsPolicySetAtScopeCount)
+
+ if (-not $htMgAtScopePolicyAssignments.($scopeId)) {
+ $script:htMgAtScopePolicyAssignments.($scopeId) = @{}
+ $script:htMgAtScopePolicyAssignments.($scopeId).AssignmentsCount = $L0mgmtGroupPolicyAssignmentsPolicyAndPolicySetAtScopeCount
+ }
+
+ foreach ($L0mgmtGroupPolicyAssignment in $L0mgmtGroupPolicyAssignments) {
+
+ $doIt = $false
+ if ($L0mgmtGroupPolicyAssignment.properties.scope -eq "/providers/Microsoft.Management/managementGroups/$($scopeId)" -and $L0mgmtGroupPolicyAssignment.properties.scope -ne "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)") {
+ $doIt = $true
+ }
+ if ($scopeId -eq $ManagementGroupId) {
+ $doIt = $true
+ }
+
+ if ($doIt) {
+ $htTemp = @{}
+ $htTemp.Assignment = $L0mgmtGroupPolicyAssignment
+ $htTemp.AssignmentScopeMgSubRg = 'Mg'
+ $splitAssignment = (($L0mgmtGroupPolicyAssignment.Id).ToLower()).Split('/')
+ $htTemp.AssignmentScopeId = [string]($splitAssignment[4])
+ $script:htCacheAssignmentsPolicy.(($L0mgmtGroupPolicyAssignment.Id).ToLower()) = $htTemp
+ }
+
+ #region namingValidation
+ if (-not [string]::IsNullOrEmpty($L0mgmtGroupPolicyAssignment.Properties.DisplayName)) {
+ $namingValidationResult = NamingValidation -toCheck $L0mgmtGroupPolicyAssignment.Properties.DisplayName
+ if ($namingValidationResult.Count -gt 0) {
+ if (-not $script:htNamingValidation.PolicyAssignment.($L0mgmtGroupPolicyAssignment.Id)) {
+ $script:htNamingValidation.PolicyAssignment.($L0mgmtGroupPolicyAssignment.Id) = @{}
+ }
+ $script:htNamingValidation.PolicyAssignment.($L0mgmtGroupPolicyAssignment.Id).displayNameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.PolicyAssignment.($L0mgmtGroupPolicyAssignment.Id).displayName = $L0mgmtGroupPolicyAssignment.Properties.DisplayName
+ }
+ }
+ if (-not [string]::IsNullOrEmpty($L0mgmtGroupPolicyAssignment.Name)) {
+ $namingValidationResult = NamingValidation -toCheck $L0mgmtGroupPolicyAssignment.Name
+ if ($namingValidationResult.Count -gt 0) {
+ if (-not $script:htNamingValidation.PolicyAssignment.($L0mgmtGroupPolicyAssignment.Id)) {
+ $script:htNamingValidation.PolicyAssignment.($L0mgmtGroupPolicyAssignment.Id) = @{}
+ }
+ $script:htNamingValidation.PolicyAssignment.($L0mgmtGroupPolicyAssignment.Id).nameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.PolicyAssignment.($L0mgmtGroupPolicyAssignment.Id).name = $L0mgmtGroupPolicyAssignment.Name
+ }
+ }
+ #endregion namingValidation
+
+ if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -OR $L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') {
+
+ #policy
+ if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/') {
+ $policyVariant = 'Policy'
+ $policyDefinitionId = ($L0mgmtGroupPolicyAssignment.properties.policydefinitionid).ToLower()
+
+ $policyDefinitionSplitted = $policyDefinitionId.split('/')
+ $hlpPolicyDefinitionScope = $policyDefinitionSplitted[4]
+
+ if ( ($policyDefinitionId -like '/providers/microsoft.management/managementgroups/*' -and $htManagementGroupsMgPath.($scopeId).path -contains ($hlpPolicyDefinitionScope)) -or $policyDefinitionId -like '/providers/microsoft.authorization/policydefinitions/*' ) {
+ $tryCounter = 0
+ do {
+ $tryCounter++
+ if (($htCacheDefinitionsPolicy).($policyDefinitionId)) {
+ $policyReturnedFromHt = $true
+ $policyDefinition = ($htCacheDefinitionsPolicy).($policyDefinitionId)
+
+ if ([string]::IsnullOrEmpty($policyDefinition.PolicyDefinitionId)) {
+ Write-Host "check: $policyDefinitionId"
+ $policyDefinition
+ }
+
+ if ($policyDefinition.Type -eq 'Custom') {
+ $policyDefintionScope = $policyDefinition.Scope
+ $policyDefintionScopeMgSub = $policyDefinition.ScopeMgSub
+ $policyDefintionScopeId = $policyDefinition.ScopeId
+ }
+ else {
+ $policyDefintionScope = 'n/a'
+ $policyDefintionScopeMgSub = 'n/a'
+ $policyDefintionScopeId = 'n/a'
+ }
+
+ $policyAvailability = ''
+ $policyDisplayName = ($policyDefinition).DisplayName
+ $policyDescription = ($policyDefinition).Description
+ $policyDefinitionType = ($policyDefinition).Type
+ $policyDefinitionIsALZ = ($policyDefinition).ALZ
+ $policyCategory = ($policyDefinition).Category
+ $policyDefinitionEffectDefault = ($policyDefinition).effectDefaultValue
+ $policyDefinitionEffectFixed = ($policyDefinition).effectFixedValue
+ }
+ else {
+ #test
+ Write-Host " attention! $scopeDisplayName ($scopeId); policyAssignment '$($L0mgmtGroupPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)' -retry"
+ Start-Sleep -Seconds 1
+ }
+ }
+ until ($policyReturnedFromHt -or $tryCounter -gt 2)
+ if (-not $policyReturnedFromHt) {
+ Write-Host " attention! $scopeDisplayName ($scopeId); policyAssignment '$($L0mgmtGroupPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)'"
+ Write-Host " scope: $($scopeId) Policy / Custom:$($mgPolicyDefinitions.Count) CustomAtScope:$($PolicyDefinitionsScopedCount)"
+ Write-Host " built-in PolicyDefinitions: $($($htCacheDefinitionsPolicy).Values.where({$_.Type -eq 'BuiltIn'}).Count)"
+ Write-Host " custom PolicyDefinitions: $($($htCacheDefinitionsPolicy).Values.where({$_.Type -eq 'Custom'}).Count)"
+ Write-Host ' Listing all PolicyDefinitions:'
+ foreach ($tmpPolicyDefinitionId in ($($htCacheDefinitionsPolicy).Keys | Sort-Object)) {
+ Write-Host $tmpPolicyDefinitionId
+ }
+ Throw 'Error - Azure Governance Visualizer: check the last console output for details'
+ }
+ }
+ #policyDefinition Scope does not exist
+ else {
+ if ($htManagementGroupsMgPath.Keys -contains $hlpPolicyDefinitionScope) {
+ Write-Host " $scopeDisplayName ($scopeId); policyAssignment '$($L0mgmtGroupPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)' - the scope '$($hlpPolicyDefinitionScope)' is not contained in the '$scopeId' Management Group chain. The Policy definition scope '$hlpPolicyDefinitionScope' has MGPath: '$($htManagementGroupsMgPath.($hlpPolicyDefinitionScope).pathDelimited)'"
+ }
+ else {
+ Write-Host " $scopeDisplayName ($scopeId); policyAssignment '$($L0mgmtGroupPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)' - the scope '$($hlpPolicyDefinitionScope)' could not be found"
+ }
+ $policyAvailability = 'na'
+
+ $policyDefintionScope = "/$($policyDefinitionSplitted[1])/$($policyDefinitionSplitted[2])/$($policyDefinitionSplitted[3])/$($hlpPolicyDefinitionScope)"
+ $policyDefintionScopeMgSub = 'Mg'
+ $policyDefintionScopeId = $hlpPolicyDefinitionScope
+
+ $policyDisplayName = 'unknown'
+ $policyDescription = 'unknown'
+ $policyDefinitionType = 'likely Custom'
+ $policyDefinitionIsALZ = 'unknown'
+ $policyCategory = 'unknown'
+ $policyDefinitionEffectDefault = 'unknown'
+ $policyDefinitionEffectFixed = 'unknown'
+ }
+
+ $policyAssignmentScope = $L0mgmtGroupPolicyAssignment.Properties.Scope
+ $policyAssignmentId = ($L0mgmtGroupPolicyAssignment.Id).ToLower()
+ $policyAssignmentName = $L0mgmtGroupPolicyAssignment.Name
+ $policyAssignmentDisplayName = $L0mgmtGroupPolicyAssignment.Properties.DisplayName
+ if (($L0mgmtGroupPolicyAssignment.Properties.Description).length -eq 0) {
+ $policyAssignmentDescription = 'no description given'
+ }
+ else {
+ $policyAssignmentDescription = $L0mgmtGroupPolicyAssignment.Properties.Description
+ }
+
+ if ($L0mgmtGroupPolicyAssignment.identity) {
+ $policyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId
+ }
+ else {
+ $policyAssignmentIdentity = 'n/a'
+ }
+
+ $assignedBy = 'n/a'
+ $createdBy = ''
+ $createdOn = ''
+ $updatedBy = ''
+ $updatedOn = ''
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata) {
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.assignedBy) {
+ $assignedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.assignedBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.createdBy) {
+ $createdBy = $L0mgmtGroupPolicyAssignment.properties.metadata.createdBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.createdOn) {
+ $createdOn = $L0mgmtGroupPolicyAssignment.properties.metadata.createdOn
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy) {
+ $updatedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn) {
+ $updatedOn = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn
+ }
+ }
+
+ if ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.Message) {
+ $nonComplianceMessage = $L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.Message
+ }
+ else {
+ $nonComplianceMessage = ''
+ }
+
+ $formatedPolicyAssignmentParameters = ''
+ $hlp = $L0mgmtGroupPolicyAssignment.Properties.Parameters
+ if (-not [string]::IsNullOrEmpty($hlp)) {
+ $arrayPolicyAssignmentParameters = @()
+ $arrayPolicyAssignmentParameters = foreach ($parameterName in $hlp.PSObject.Properties.Name | Sort-Object) {
+ "$($parameterName)=$($hlp.($parameterName).Value -join "$($CsvDelimiter) ")"
+ }
+ $formatedPolicyAssignmentParameters = $arrayPolicyAssignmentParameters -join "$($CsvDelimiterOpposite) "
+ }
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $scopeDisplayName `
+ -mgId $scopeId `
+ -mgParentId $mgParentId `
+ -mgParentName $mgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Policy $policyDisplayName `
+ -PolicyAvailability $policyAvailability `
+ -PolicyDescription $policyDescription `
+ -PolicyVariant $policyVariant `
+ -PolicyType $policyDefinitionType `
+ -PolicyIsALZ $policyDefinitionIsALZ `
+ -PolicyCategory $policyCategory `
+ -PolicyDefinitionIdGuid ($policyDefinitionId -replace '.*/') `
+ -PolicyDefinitionId $policyDefinitionId `
+ -PolicyDefintionScope $policyDefintionScope `
+ -PolicyDefintionScopeMgSub $policyDefintionScopeMgSub `
+ -PolicyDefintionScopeId $policyDefintionScopeId `
+ -PolicyDefinitionsScopedLimit $LimitPOLICYPolicyDefinitionsScopedManagementGroup `
+ -PolicyDefinitionsScopedCount $policyDefinitionsScopedCount `
+ -PolicySetDefinitionsScopedLimit $LimitPOLICYPolicySetDefinitionsScopedManagementGroup `
+ -PolicySetDefinitionsScopedCount $policySetDefinitionsScopedCount `
+ -PolicyDefinitionEffectDefault $policyDefinitionEffectDefault `
+ -PolicyDefinitionEffectFixed $policyDefinitionEffectFixed `
+ -PolicyAssignmentScope $policyAssignmentScope `
+ -PolicyAssignmentScopeMgSubRg 'Mg' `
+ -PolicyAssignmentScopeName ($policyAssignmentScope -replace '.*/', '') `
+ -PolicyAssignmentNotScopes $L0mgmtGroupPolicyAssignment.Properties.NotScopes `
+ -PolicyAssignmentId $policyAssignmentId `
+ -PolicyAssignmentName $policyAssignmentName `
+ -PolicyAssignmentDisplayName $policyAssignmentDisplayName `
+ -PolicyAssignmentDescription $policyAssignmentDescription `
+ -PolicyAssignmentEnforcementMode $L0mgmtGroupPolicyAssignment.Properties.EnforcementMode `
+ -PolicyAssignmentNonComplianceMessages $nonComplianceMessage `
+ -PolicyAssignmentIdentity $policyAssignmentIdentity `
+ -PolicyAssignmentLimit $LimitPOLICYPolicyAssignmentsManagementGroup `
+ -PolicyAssignmentCount $L0mgmtGroupPolicyAssignmentsPolicyCount `
+ -PolicyAssignmentAtScopeCount $L0mgmtGroupPolicyAssignmentsPolicyAtScopeCount `
+ -PolicyAssignmentParameters $L0mgmtGroupPolicyAssignment.Properties.Parameters `
+ -PolicyAssignmentParametersFormated $formatedPolicyAssignmentParameters `
+ -PolicyAssignmentAssignedBy $assignedBy `
+ -PolicyAssignmentCreatedBy $createdBy `
+ -PolicyAssignmentCreatedOn $createdOn `
+ -PolicyAssignmentUpdatedBy $updatedBy `
+ -PolicyAssignmentUpdatedOn $updatedOn `
+ -PolicySetAssignmentLimit $LimitPOLICYPolicySetAssignmentsManagementGroup `
+ -PolicySetAssignmentCount $L0mgmtGroupPolicyAssignmentsPolicySetCount `
+ -PolicySetAssignmentAtScopeCount $L0mgmtGroupPolicyAssignmentsPolicySetAtScopeCount `
+ -PolicyAndPolicySetAssignmentAtScopeCount $L0mgmtGroupPolicyAssignmentsPolicyAndPolicySetAtScopeCount
+ }
+
+ #policySet
+ if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') {
+ $policyVariant = 'PolicySet'
+ $policySetDefinitionId = ($L0mgmtGroupPolicyAssignment.properties.policydefinitionid).ToLower()
+ $policySetDefinitionSplitted = $policySetDefinitionId.split('/')
+ $hlpPolicySetDefinitionScope = $policySetDefinitionSplitted[4]
+
+ $tryCounter = 0
+ do {
+ $tryCounter++
+ if (($htCacheDefinitionsPolicySet).($policySetDefinitionId)) {
+ $policySetReturnedFromHt = $true
+ $policySetDefinition = ($htCacheDefinitionsPolicySet).($policySetDefinitionId)
+ if ($policySetDefinition.Type -eq 'Custom') {
+ $policySetDefintionScope = $policySetDefinition.Scope
+ $policySetDefintionScopeMgSub = $policySetDefinition.ScopeMgSub
+ $policySetDefintionScopeId = $policySetDefinition.ScopeId
+ }
+ else {
+ $policySetDefintionScope = 'n/a'
+ $policySetDefintionScopeMgSub = 'n/a'
+ $policySetDefintionScopeId = 'n/a'
+ }
+ $policySetDisplayName = $policySetDefinition.DisplayName
+ $policySetDescription = $policySetDefinition.Description
+ $policySetDefinitionType = $policySetDefinition.Type
+ $policySetDefinitionIsALZ = $policySetDefinition.ALZ
+ $policySetCategory = $policySetDefinition.Category
+ }
+ else {
+ #test
+ #Write-Host "pa '($L0mgmtGroupPolicyAssignment.Id)' scope: '$($scopeId)' - policySetDefinition not available: $policySetDefinitionId"
+ Start-Sleep -Seconds 1
+ }
+ }
+ until ($policySetReturnedFromHt -or $tryCounter -gt 2)
+ if (-not $policySetReturnedFromHt) {
+ Write-Host " $scopeDisplayName ($scopeId); policyAssignment '$($L0mgmtGroupPolicyAssignment.Id)' policyDefinition (PolicySet) could not be found: '$($policySetDefinitionId)'"
+ $policySetDefintionScope = "/$($policySetDefinitionSplitted[1])/$($policySetDefinitionSplitted[2])/$($policySetDefinitionSplitted[3])/$($hlpPolicySetDefinitionScope)"
+ $policySetDefintionScopeMgSub = 'Mg'
+ $policySetDefintionScopeId = $hlpPolicySetDefinitionScope
+ $policySetDisplayName = 'unknown'
+ $policySetDescription = 'unknown'
+ $policySetDefinitionType = 'likely Custom'
+ $policySetDefinitionIsALZ = 'unknown'
+ $policySetCategory = 'unknown'
+ }
+
+ $policyAssignmentScope = $L0mgmtGroupPolicyAssignment.Properties.Scope
+ $policyAssignmentId = ($L0mgmtGroupPolicyAssignment.Id).ToLower()
+ $policyAssignmentName = $L0mgmtGroupPolicyAssignment.Name
+ $policyAssignmentDisplayName = $L0mgmtGroupPolicyAssignment.Properties.DisplayName
+ if (($L0mgmtGroupPolicyAssignment.Properties.Description).length -eq 0) {
+ $policyAssignmentDescription = 'no description given'
+ }
+ else {
+ $policyAssignmentDescription = $L0mgmtGroupPolicyAssignment.Properties.Description
+ }
+
+ if ($L0mgmtGroupPolicyAssignment.identity) {
+ $policyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId
+ }
+ else {
+ $policyAssignmentIdentity = 'n/a'
+ }
+
+ $assignedBy = 'n/a'
+ $createdBy = ''
+ $createdOn = ''
+ $updatedBy = ''
+ $updatedOn = ''
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata) {
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.assignedBy) {
+ $assignedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.assignedBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.createdBy) {
+ $createdBy = $L0mgmtGroupPolicyAssignment.properties.metadata.createdBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.createdOn) {
+ $createdOn = $L0mgmtGroupPolicyAssignment.properties.metadata.createdOn
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy) {
+ $updatedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy
+ }
+ if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn) {
+ $updatedOn = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn
+ }
+ }
+
+ if (($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId } )).Message) {
+ $nonComplianceMessage = ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId } )).Message
+ }
+ else {
+ $nonComplianceMessage = ''
+ }
+
+ $formatedPolicyAssignmentParameters = ''
+ $hlp = $L0mgmtGroupPolicyAssignment.Properties.Parameters
+ if (-not [string]::IsNullOrEmpty($hlp)) {
+ $arrayPolicyAssignmentParameters = @()
+ $arrayPolicyAssignmentParameters = foreach ($parameterName in $hlp.PSObject.Properties.Name | Sort-Object) {
+ "$($parameterName)=$($hlp.($parameterName).Value -join "$($CsvDelimiter) ")"
+ }
+ $formatedPolicyAssignmentParameters = $arrayPolicyAssignmentParameters -join "$($CsvDelimiterOpposite) "
+ }
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $scopeDisplayName `
+ -mgId $scopeId `
+ -mgParentId $mgParentId `
+ -mgParentName $mgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Policy $policySetDisplayName `
+ -PolicyDescription $policySetDescription `
+ -PolicyVariant $policyVariant `
+ -PolicyType $policySetDefinitionType `
+ -PolicyIsALZ $policySetDefinitionIsALZ `
+ -PolicyCategory $policySetCategory `
+ -PolicyDefinitionIdGuid ($policySetDefinitionId -replace '.*/') `
+ -PolicyDefinitionId $policySetDefinitionId `
+ -PolicyDefintionScope $policySetDefintionScope `
+ -PolicyDefintionScopeMgSub $policySetDefintionScopeMgSub `
+ -PolicyDefintionScopeId $policySetDefintionScopeId `
+ -PolicyDefinitionsScopedLimit $LimitPOLICYPolicyDefinitionsScopedManagementGroup `
+ -PolicyDefinitionsScopedCount $policyDefinitionsScopedCount `
+ -PolicySetDefinitionsScopedLimit $LimitPOLICYPolicySetDefinitionsScopedManagementGroup `
+ -PolicySetDefinitionsScopedCount $policySetDefinitionsScopedCount `
+ -PolicyAssignmentScope $policyAssignmentScope `
+ -PolicyAssignmentScopeMgSubRg 'Mg' `
+ -PolicyAssignmentScopeName ($policyAssignmentScope -replace '.*/', '') `
+ -PolicyAssignmentNotScopes $L0mgmtGroupPolicyAssignment.Properties.NotScopes `
+ -PolicyAssignmentId $policyAssignmentId `
+ -PolicyAssignmentName $policyAssignmentName `
+ -PolicyAssignmentDisplayName $policyAssignmentDisplayName `
+ -PolicyAssignmentDescription $policyAssignmentDescription `
+ -PolicyAssignmentEnforcementMode $L0mgmtGroupPolicyAssignment.Properties.EnforcementMode `
+ -PolicyAssignmentNonComplianceMessages $nonComplianceMessage `
+ -PolicyAssignmentIdentity $policyAssignmentIdentity `
+ -PolicyAssignmentLimit $LimitPOLICYPolicyAssignmentsManagementGroup `
+ -PolicyAssignmentCount $L0mgmtGroupPolicyAssignmentsPolicyCount `
+ -PolicyAssignmentAtScopeCount $L0mgmtGroupPolicyAssignmentsPolicyAtScopeCount `
+ -PolicyAssignmentParameters $L0mgmtGroupPolicyAssignment.Properties.Parameters `
+ -PolicyAssignmentParametersFormated $formatedPolicyAssignmentParameters `
+ -PolicyAssignmentAssignedBy $assignedBy `
+ -PolicyAssignmentCreatedBy $createdBy `
+ -PolicyAssignmentCreatedOn $createdOn `
+ -PolicyAssignmentUpdatedBy $updatedBy `
+ -PolicyAssignmentUpdatedOn $updatedOn `
+ -PolicySetAssignmentLimit $LimitPOLICYPolicySetAssignmentsManagementGroup `
+ -PolicySetAssignmentCount $L0mgmtGroupPolicyAssignmentsPolicySetCount `
+ -PolicySetAssignmentAtScopeCount $L0mgmtGroupPolicyAssignmentsPolicySetAtScopeCount `
+ -PolicyAndPolicySetAssignmentAtScopeCount $L0mgmtGroupPolicyAssignmentsPolicyAndPolicySetAtScopeCount
+ }
+ }
+ }
+
+ $returnObject = @{}
+ if ($addRowToTableDone) {
+ $returnObject.'addRowToTableDone' = @{}
+ }
+ return $returnObject
+}
+$funcDataCollectionPolicyAssignmentsMG = $function:dataCollectionPolicyAssignmentsMG.ToString()
+
+function dataCollectionPolicyAssignmentsSub {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $hierarchyLevel,
+ $childMgDisplayName,
+ $childMgId,
+ $childMgParentId,
+ $childMgParentName,
+ $mgAscSecureScoreResult,
+ $subscriptionQuotaId,
+ $subscriptionState,
+ $subscriptionASCSecureScore,
+ $subscriptionTags,
+ $subscriptionTagsCount,
+ $PolicyDefinitionsScopedCount,
+ $PolicySetDefinitionsScopedCount
+ )
+
+ $currentTask = "Getting Policy assignments for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyAssignments?api-version=2021-06-01"
+ $method = 'GET'
+
+ $addRowToTableDone = $false
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy -eq $false) {
+ $L1mgmtGroupSubPolicyAssignments = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $L1mgmtGroupSubPolicyAssignmentsPolicyCount = ($L1mgmtGroupSubPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' } )).count
+ $L1mgmtGroupSubPolicyAssignmentsPolicySetCount = ($L1mgmtGroupSubPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/' } )).count
+ $L1mgmtGroupSubPolicyAssignmentsPolicyAtScopeCount = ($L1mgmtGroupSubPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -and $_.Id -match "/subscriptions/$($scopeId)" } )).count
+ $L1mgmtGroupSubPolicyAssignmentsPolicySetAtScopeCount = ($L1mgmtGroupSubPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/' -and $_.Id -match "/subscriptions/$($scopeId)" } )).count
+ $L1mgmtGroupSubPolicyAssignmentsQuery = $L1mgmtGroupSubPolicyAssignments
+ }
+ else {
+ $L1mgmtGroupSubPolicyAssignments = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $L1mgmtGroupSubPolicyAssignmentsPolicyCount = ($L1mgmtGroupSubPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -and $_.Id -notmatch "/subscriptions/$($scopeId)/resourceGroups" } )).count
+ $L1mgmtGroupSubPolicyAssignmentsPolicySetCount = ($L1mgmtGroupSubPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/' -and $_.Id -notmatch "/subscriptions/$($scopeId)/resourceGroups" } )).count
+ $L1mgmtGroupSubPolicyAssignmentsPolicyAtScopeCount = ($L1mgmtGroupSubPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -and $_.Id -match "/subscriptions/$($scopeId)" -and $_.Id -notmatch "/subscriptions/$($scopeId)/resourceGroups" } )).count
+ $L1mgmtGroupSubPolicyAssignmentsPolicySetAtScopeCount = ($L1mgmtGroupSubPolicyAssignments.where( { $_.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/' -and $_.Id -match "/subscriptions/$($scopeId)" -and $_.Id -notmatch "/subscriptions/$($scopeId)/resourceGroups" } )).count
+ foreach ($L1mgmtGroupSubPolicyAssignment in $L1mgmtGroupSubPolicyAssignments.where( { $_.Id -match "/subscriptions/$($scopeId)/resourceGroups" } )) {
+ ($script:htCacheAssignmentsPolicyOnResourceGroupsAndResources).(($L1mgmtGroupSubPolicyAssignment.Id).ToLower()) = $L1mgmtGroupSubPolicyAssignment
+ }
+ $L1mgmtGroupSubPolicyAssignmentsQuery = $L1mgmtGroupSubPolicyAssignments.where( { $_.Id -notmatch "/subscriptions/$($scopeId)/resourceGroups" } )
+ }
+
+ $L1mgmtGroupSubPolicyAssignmentsPolicyAndPolicySetAtScopeCount = ($L1mgmtGroupSubPolicyAssignmentsPolicyAtScopeCount + $L1mgmtGroupSubPolicyAssignmentsPolicySetAtScopeCount)
+
+ foreach ($L1mgmtGroupSubPolicyAssignment in $L1mgmtGroupSubPolicyAssignmentsQuery ) {
+ if ($L1mgmtGroupSubPolicyAssignment.Id -like "/subscriptions/$($scopeId)/*") {
+ $htTemp = @{}
+ $htTemp.Assignment = $L1mgmtGroupSubPolicyAssignment
+ $splitAssignment = (($L1mgmtGroupSubPolicyAssignment.Id).ToLower()).Split('/')
+ if (($L1mgmtGroupSubPolicyAssignment.Id).ToLower() -like "/subscriptions/$($scopeId)/resourceGroups*") {
+ $htTemp.AssignmentScopeMgSubRg = 'Rg'
+ $htTemp.AssignmentScopeId = "$($splitAssignment[2])/$($splitAssignment[4])"
+ }
+ else {
+ $htTemp.AssignmentScopeMgSubRg = 'Sub'
+ $htTemp.AssignmentScopeId = [string]$splitAssignment[2]
+ }
+ $script:htCacheAssignmentsPolicy.(($L1mgmtGroupSubPolicyAssignment.Id).ToLower()) = $htTemp
+ }
+
+ #region namingValidation
+ if (-not [string]::IsNullOrEmpty($L1mgmtGroupSubPolicyAssignment.Properties.DisplayName)) {
+ $namingValidationResult = NamingValidation -toCheck $L1mgmtGroupSubPolicyAssignment.Properties.DisplayName
+ if ($namingValidationResult.Count -gt 0) {
+ if (-not $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id)) {
+ $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id) = @{}
+ }
+ $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id).displayNameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id).displayName = $L1mgmtGroupSubPolicyAssignment.Properties.DisplayName
+ }
+ }
+ if (-not [string]::IsNullOrEmpty($L1mgmtGroupSubPolicyAssignment.Name)) {
+ $namingValidationResult = NamingValidation -toCheck $L1mgmtGroupSubPolicyAssignment.Name
+ if ($namingValidationResult.Count -gt 0) {
+ if (-not $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id)) {
+ $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id) = @{}
+ }
+ $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id).nameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id).name = $L1mgmtGroupSubPolicyAssignment.Name
+ }
+ }
+ #endregion namingValidation
+
+ if ($L1mgmtGroupSubPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -OR $L1mgmtGroupSubPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') {
+
+ #policy
+ if ($L1mgmtGroupSubPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/') {
+ $policyVariant = 'Policy'
+ $policyDefinitionId = ($L1mgmtGroupSubPolicyAssignment.properties.policydefinitionid).ToLower()
+
+ if (($htCacheDefinitionsPolicy).($policyDefinitionId)) {
+ $policyAvailability = ''
+
+ #handling some strange scenario where the synchronized hashTable responds fragments?!
+ $tryCounter = 0
+ do {
+ $tryCounter++
+ $policyAssignmentsPolicyDefinition = ($htCacheDefinitionsPolicy).($policyDefinitionId)
+
+ if (($policyAssignmentsPolicyDefinition).Type -eq 'Custom' -or ($policyAssignmentsPolicyDefinition).Type -eq 'Builtin') {
+ $policyReturnedFromHt = $true
+
+ $policyDisplayName = ($policyAssignmentsPolicyDefinition).DisplayName
+ $policyDescription = ($policyAssignmentsPolicyDefinition).Description
+ $policyDefinitionType = ($policyAssignmentsPolicyDefinition).Type
+ $policyDefinitionIsALZ = ($policyAssignmentsPolicyDefinition).ALZ
+ $policyCategory = ($policyAssignmentsPolicyDefinition).Category
+ $policyDefinitionEffectDefault = ($policyAssignmentsPolicyDefinition).effectDefaultValue
+ $policyDefinitionEffectFixed = ($policyAssignmentsPolicyDefinition).effectFixedValue
+
+ if (($policyAssignmentsPolicyDefinition).Type -ne $policyDefinitionType) {
+ Write-Host "$scopeDisplayName ($scopeId) $policyVariant was processing: $policyDefinitionId"
+ Write-Host "'$(($policyAssignmentsPolicyDefinition).Type)' ne '$policyDefinitionType'"
+ Write-Host "!Please report this error: $($azAPICallConf['htParameters'].GithubRepository)" -ForegroundColor Yellow
+ throw
+ }
+
+ if ($policyDefinitionType -eq 'Custom') {
+ $policyDefintionScope = ($policyAssignmentsPolicyDefinition).Scope
+ $policyDefintionScopeMgSub = ($policyAssignmentsPolicyDefinition).ScopeMgSub
+ $policyDefintionScopeId = ($policyAssignmentsPolicyDefinition).ScopeId
+ }
+
+ if ($policyDefinitionType -eq 'Builtin') {
+ $policyDefintionScope = 'n/a'
+ $policyDefintionScopeMgSub = 'n/a'
+ $policyDefintionScopeId = 'n/a'
+ }
+ }
+ else {
+ Write-Host " **INCONSISTENCY! processing policyId:'$policyDefinitionId'; policyAss:'$($L1mgmtGroupSubPolicyAssignment.Id)'; policyAssignmentsPolicyDefinition.Type: '$($policyAssignmentsPolicyDefinition.Type)'"
+ Start-Sleep -Seconds 1
+ }
+ }
+ until($policyReturnedFromHt -or $tryCounter -gt 5)
+ if (-not $policyReturnedFromHt) {
+ Write-Host "FinalHandler - $scopeDisplayName ($scopeId) $policyVariant was processing: policyId:'$policyDefinitionId'; policyAss:'$($L1mgmtGroupSubPolicyAssignment.Id)'; policyAssignmentsPolicyDefinition.Type: '$($policyAssignmentsPolicyDefinition.Type)'"
+ Write-Host ($policyAssignmentsPolicyDefinition | ConvertTo-Json -Depth 99)
+ Write-Host "!Please report this error: $($azAPICallConf['htParameters'].GithubRepository)" -ForegroundColor Yellow
+ throw
+ }
+ }
+ #policyDefinition not exists!
+ else {
+ $policyDefinitionSplitted = $policyDefinitionId.split('/')
+
+ if ($policyDefinitionId -like '/providers/microsoft.management/managementgroups/*') {
+ $hlpPolicyDefinitionScope = $policyDefinitionSplitted[4]
+ if ($htSubscriptionsMgPath.($scopeId).path -contains $hlpPolicyDefinitionScope) {
+ Write-Host " ATTENTION: $scopeDisplayName ($scopeId); policyAssignment '$($L1mgmtGroupSubPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)' - the scope '$($hlpPolicyDefinitionScope)' HOWEVER IS CONTAINED in the '$scopeId' Management Group chain. The Policy definition scope '$hlpPolicyDefinitionScope' has MGPath: '$($htManagementGroupsMgPath.($hlpPolicyDefinitionScope).pathDelimited)'"
+ }
+ else {
+ if ($htManagementGroupsMgPath.($hlpPolicyDefinitionScope)) {
+ Write-Host " $scopeDisplayName ($scopeId); policyAssignment '$($L1mgmtGroupSubPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)' - the scope '$($hlpPolicyDefinitionScope)' IS NOT CONTAINED in the '$scopeId' Management Group chain. The Policy definition scope '$hlpPolicyDefinitionScope' has MGPath: '$($htManagementGroupsMgPath.($hlpPolicyDefinitionScope).pathDelimited)'"
+ }
+ else {
+ Write-Host " $scopeDisplayName ($scopeId); policyAssignment '$($L1mgmtGroupSubPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)' - the scope '$($hlpPolicyDefinitionScope)' IS NOT CONTAINED in the '$scopeId' Management Group chain. The Policy definition scope '$hlpPolicyDefinitionScope' could not be found"
+ }
+ }
+ $policyDefintionScope = "/$($policyDefinitionSplitted[1])/$($policyDefinitionSplitted[2])/$($policyDefinitionSplitted[3])/$($hlpPolicyDefinitionScope)"
+ $policyDefintionScopeMgSub = 'Mg'
+ $policyDefintionScopeId = $hlpPolicyDefinitionScope
+ }
+ else {
+ $hlpPolicyDefinitionScope = $policyDefinitionSplitted[2]
+ Write-Host " $scopeDisplayName ($scopeId); policyAssignment '$($L1mgmtGroupSubPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)'"
+ $policyDefintionScope = "/$($policyDefinitionSplitted[1])/$($hlpPolicyDefinitionScope)"
+ $policyDefintionScopeMgSub = 'Sub'
+ $policyDefintionScopeId = $hlpPolicyDefinitionScope
+ }
+ $policyAvailability = 'na'
+ $policyDisplayName = 'unknown'
+ $policyDescription = 'unknown'
+ $policyDefinitionType = 'likely Custom'
+ $policyDefinitionIsALZ = 'unknown'
+ $policyCategory = 'unknown'
+ $policyDefinitionEffectDefault = 'unknown'
+ $policyDefinitionEffectFixed = 'unknown'
+ }
+
+ $PolicyAssignmentScope = $L1mgmtGroupSubPolicyAssignment.Properties.Scope
+ if ($PolicyAssignmentScope -like '/providers/Microsoft.Management/managementGroups/*') {
+ $PolicyAssignmentScopeMgSubRg = 'Mg'
+ }
+ else {
+ $splitPolicyAssignmentScope = ($PolicyAssignmentScope).Split('/')
+ switch (($splitPolicyAssignmentScope).Count - 1) {
+ #sub
+ 2 {
+ $PolicyAssignmentScopeMgSubRg = 'Sub'
+ }
+ 4 {
+ $PolicyAssignmentScopeMgSubRg = 'Rg'
+ }
+ Default {
+ $PolicyAssignmentScopeMgSubRg = 'unknown'
+ }
+ }
+ }
+
+ $PolicyAssignmentId = ($L1mgmtGroupSubPolicyAssignment.Id).ToLower()
+ $PolicyAssignmentName = $L1mgmtGroupSubPolicyAssignment.Name
+ $PolicyAssignmentDisplayName = $L1mgmtGroupSubPolicyAssignment.Properties.DisplayName
+ if (($L1mgmtGroupSubPolicyAssignment.Properties.Description).length -eq 0) {
+ $PolicyAssignmentDescription = 'no description given'
+ }
+ else {
+ $PolicyAssignmentDescription = $L1mgmtGroupSubPolicyAssignment.Properties.Description
+ }
+
+ if ($L1mgmtGroupSubPolicyAssignment.identity) {
+ $PolicyAssignmentIdentity = $L1mgmtGroupSubPolicyAssignment.identity.principalId
+ }
+ else {
+ $PolicyAssignmentIdentity = 'n/a'
+ }
+
+ $assignedBy = 'n/a'
+ $createdBy = ''
+ $createdOn = ''
+ $updatedBy = ''
+ $updatedOn = ''
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata) {
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.assignedBy) {
+ $assignedBy = $L1mgmtGroupSubPolicyAssignment.properties.metadata.assignedBy
+ }
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.createdBy) {
+ $createdBy = $L1mgmtGroupSubPolicyAssignment.properties.metadata.createdBy
+ }
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.createdOn) {
+ $createdOn = $L1mgmtGroupSubPolicyAssignment.properties.metadata.createdOn
+ }
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.updatedBy) {
+ $updatedBy = $L1mgmtGroupSubPolicyAssignment.properties.metadata.updatedBy
+ }
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.updatedOn) {
+ $updatedOn = $L1mgmtGroupSubPolicyAssignment.properties.metadata.updatedOn
+ }
+ }
+
+ if ($L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.Message) {
+ $nonComplianceMessage = $L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.Message
+ }
+ else {
+ $nonComplianceMessage = ''
+ }
+
+ $formatedPolicyAssignmentParameters = ''
+ $hlp = $L1mgmtGroupSubPolicyAssignment.Properties.Parameters
+ if (-not [string]::IsNullOrEmpty($hlp)) {
+ $arrayPolicyAssignmentParameters = @()
+ $arrayPolicyAssignmentParameters = foreach ($parameterName in $hlp.PSObject.Properties.Name | Sort-Object) {
+ "$($parameterName)=$($hlp.($parameterName).Value -join "$($CsvDelimiter) ")"
+ }
+ $formatedPolicyAssignmentParameters = $arrayPolicyAssignmentParameters -join "$($CsvDelimiterOpposite) "
+ }
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $childMgDisplayName `
+ -mgId $childMgId `
+ -mgParentId $childMgParentId `
+ -mgParentName $childMgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Subscription $scopeDisplayName `
+ -SubscriptionId $scopeId `
+ -SubscriptionQuotaId $subscriptionQuotaId `
+ -SubscriptionState $subscriptionState `
+ -SubscriptionASCSecureScore $subscriptionASCSecureScore `
+ -SubscriptionTags $subscriptionTags `
+ -SubscriptionTagsCount $subscriptionTagsCount `
+ -Policy $policyDisplayName `
+ -PolicyAvailability $policyAvailability `
+ -PolicyDescription $policyDescription `
+ -PolicyVariant $policyVariant `
+ -PolicyType $policyDefinitionType `
+ -PolicyIsALZ $policyDefinitionIsALZ `
+ -PolicyCategory $policyCategory `
+ -PolicyDefinitionIdGuid ($policyDefinitionId -replace '.*/') `
+ -PolicyDefinitionId $policyDefinitionId `
+ -PolicyDefintionScope $policyDefintionScope `
+ -PolicyDefintionScopeMgSub $policyDefintionScopeMgSub `
+ -PolicyDefintionScopeId $policyDefintionScopeId `
+ -PolicyDefinitionsScopedLimit $LimitPOLICYPolicyDefinitionsScopedSubscription `
+ -PolicyDefinitionsScopedCount $PolicyDefinitionsScopedCount `
+ -PolicySetDefinitionsScopedLimit $LimitPOLICYPolicySetDefinitionsScopedSubscription `
+ -PolicySetDefinitionsScopedCount $PolicySetDefinitionsScopedCount `
+ -PolicyDefinitionEffectDefault $policyDefinitionEffectDefault `
+ -PolicyDefinitionEffectFixed $policyDefinitionEffectFixed `
+ -PolicyAssignmentScope $PolicyAssignmentScope `
+ -PolicyAssignmentScopeMgSubRg $PolicyAssignmentScopeMgSubRg `
+ -PolicyAssignmentScopeName ($PolicyAssignmentScope -replace '.*/', '') `
+ -PolicyAssignmentNotScopes $L1mgmtGroupSubPolicyAssignment.Properties.NotScopes `
+ -PolicyAssignmentId $PolicyAssignmentId `
+ -PolicyAssignmentName $PolicyAssignmentName `
+ -PolicyAssignmentDisplayName $PolicyAssignmentDisplayName `
+ -PolicyAssignmentDescription $PolicyAssignmentDescription `
+ -PolicyAssignmentEnforcementMode $L1mgmtGroupSubPolicyAssignment.Properties.EnforcementMode `
+ -PolicyAssignmentNonComplianceMessages $nonComplianceMessage `
+ -PolicyAssignmentIdentity $PolicyAssignmentIdentity `
+ -PolicyAssignmentLimit $LimitPOLICYPolicyAssignmentsSubscription `
+ -PolicyAssignmentCount $L1mgmtGroupSubPolicyAssignmentsPolicyCount `
+ -PolicyAssignmentAtScopeCount $L1mgmtGroupSubPolicyAssignmentsPolicyAtScopeCount `
+ -PolicyAssignmentParameters $L1mgmtGroupSubPolicyAssignment.Properties.Parameters `
+ -PolicyAssignmentParametersFormated $formatedPolicyAssignmentParameters `
+ -PolicyAssignmentAssignedBy $assignedBy `
+ -PolicyAssignmentCreatedBy $createdBy `
+ -PolicyAssignmentCreatedOn $createdOn `
+ -PolicyAssignmentUpdatedBy $updatedBy `
+ -PolicyAssignmentUpdatedOn $updatedOn `
+ -PolicySetAssignmentLimit $LimitPOLICYPolicySetAssignmentsSubscription `
+ -PolicySetAssignmentCount $L1mgmtGroupSubPolicyAssignmentsPolicySetCount `
+ -PolicySetAssignmentAtScopeCount $L1mgmtGroupSubPolicyAssignmentsPolicySetAtScopeCount `
+ -PolicyAndPolicySetAssignmentAtScopeCount $L1mgmtGroupSubPolicyAssignmentsPolicyAndPolicySetAtScopeCount
+ }
+
+ #policySet
+ if ($L1mgmtGroupSubPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') {
+ $policyVariant = 'PolicySet'
+ $policySetDefinitionId = ($L1mgmtGroupSubPolicyAssignment.properties.policydefinitionid).ToLower()
+ $policySetDefinitionSplitted = $policySetDefinitionId.split('/')
+
+ if (($htCacheDefinitionsPolicySet).($policySetDefinitionId)) {
+ $policyAvailability = ''
+
+ #handling some strange behavior where the synchronized hashTable responds fragments?!
+ $tryCounter = 0
+ do {
+ $tryCounter++
+ $policyAssignmentsPolicySetDefinition = ($htCacheDefinitionsPolicySet).($policySetDefinitionId)
+
+ if (($policyAssignmentsPolicySetDefinition).Type -eq 'Custom' -or ($policyAssignmentsPolicySetDefinition).Type -eq 'Builtin') {
+ $policySetReturnedFromHt = $true
+
+ $policySetDisplayName = ($policyAssignmentsPolicySetDefinition).DisplayName
+ $policySetDescription = ($policyAssignmentsPolicySetDefinition).Description
+ $policySetDefinitionType = ($policyAssignmentsPolicySetDefinition).Type
+ $policySetDefinitionIsALZ = ($policyAssignmentsPolicySetDefinition).ALZ
+ $policySetCategory = ($policyAssignmentsPolicySetDefinition).Category
+
+ if (($policyAssignmentsPolicySetDefinition).Type -ne $policySetDefinitionType) {
+ Write-Host "$scopeDisplayName ($scopeId) $policyVariant was processing: $policySetDefinitionId"
+ Write-Host "'$(($policyAssignmentsPolicySetDefinition).Type)' ne '$policySetDefinitionType'"
+ Write-Host "!Please report this error: $($azAPICallConf['htParameters'].GithubRepository)" -ForegroundColor Yellow
+ throw
+ }
+
+ if ($policySetDefinitionType -eq 'Custom') {
+ $policySetDefintionScope = ($policyAssignmentsPolicySetDefinition).Scope
+ $policySetDefintionScopeMgSub = ($policyAssignmentsPolicySetDefinition).ScopeMgSub
+ $policySetDefintionScopeId = ($policyAssignmentsPolicySetDefinition).ScopeId
+ }
+ if ($policySetDefinitionType -eq 'Builtin') {
+ $policySetDefintionScope = 'n/a'
+ $policySetDefintionScopeMgSub = 'n/a'
+ $policySetDefintionScopeId = 'n/a'
+ }
+ }
+ else {
+ #Write-Host "TryHandler - $scopeDisplayName ($scopeId) $policyVariant was processing: policySetId:'$policySetDefinitionId'; policyAss:'$($L1mgmtGroupSubPolicyAssignment.Id)'; type:'$(($policyAssignmentsPolicySetDefinition).Type)' - sleeping '$tryCounter' seconds"
+ Start-Sleep -Seconds 1
+ }
+ }
+ until($policySetReturnedFromHt -or $tryCounter -gt 5)
+ if (-not $policySetReturnedFromHt) {
+ Write-Host "FinalHandler - $scopeDisplayName ($scopeId) $policyVariant was processing: policySetId:'$policySetDefinitionId'; policyAss:'$($L1mgmtGroupSubPolicyAssignment.Id)'"
+ Write-Host "!Please report this error: $($azAPICallConf['htParameters'].GithubRepository)" -ForegroundColor Yellow
+ throw
+ }
+ }
+ #policySetDefinition not exists!
+ else {
+ $policyAvailability = 'na'
+ $policySetDisplayName = 'unknown'
+ $policySetDescription = 'unknown'
+ $policySetDefinitionType = 'likely Custom'
+ $policySetDefinitionIsALZ = 'unknown'
+ $policySetCategory = 'unknown'
+
+ if ($policySetDefinitionId -like '/providers/microsoft.management/managementgroups/*') {
+ $hlpPolicySetDefinitionScope = $policySetDefinitionSplitted[4]
+ $policySetDefintionScope = "/$($policySetDefinitionSplitted[1])/$($policySetDefinitionSplitted[2])/$($policySetDefinitionSplitted[3])/$($hlpPolicySetDefinitionScope)"
+ $policySetDefintionScopeMgSub = 'Mg'
+ $policySetDefintionScopeId = $hlpPolicySetDefinitionScope
+ }
+ else {
+ $hlpPolicySetDefinitionScope = $policySetDefinitionSplitted[2]
+ $policySetDefintionScope = "/$($policySetDefinitionSplitted[1])/$($hlpPolicySetDefinitionScope)"
+ $policySetDefintionScopeMgSub = 'Sub'
+ $policySetDefintionScopeId = $hlpPolicySetDefinitionScope
+
+ }
+ Write-Host " $scopeDisplayName ($scopeId); policyAssignment '$($L1mgmtGroupSubPolicyAssignment.Id)' policyDefinition (PolicySet) could not be found: '$($policySetDefinitionId)'"
+ }
+
+ $PolicyAssignmentScope = $L1mgmtGroupSubPolicyAssignment.Properties.Scope
+ if ($PolicyAssignmentScope -like '/providers/Microsoft.Management/managementGroups/*') {
+ $PolicyAssignmentScopeMgSubRg = 'Mg'
+ }
+ else {
+ $splitPolicyAssignmentScope = ($PolicyAssignmentScope).Split('/')
+ switch (($splitPolicyAssignmentScope).Count - 1) {
+ #sub
+ 2 {
+ $PolicyAssignmentScopeMgSubRg = 'Sub'
+ }
+ 4 {
+ $PolicyAssignmentScopeMgSubRg = 'Rg'
+ }
+ Default {
+ $PolicyAssignmentScopeMgSubRg = 'unknown'
+ }
+ }
+ }
+
+ $PolicyAssignmentId = ($L1mgmtGroupSubPolicyAssignment.Id).ToLower()
+ $PolicyAssignmentName = $L1mgmtGroupSubPolicyAssignment.Name
+ $PolicyAssignmentDisplayName = $L1mgmtGroupSubPolicyAssignment.Properties.DisplayName
+ if (($L1mgmtGroupSubPolicyAssignment.Properties.Description).length -eq 0) {
+ $PolicyAssignmentDescription = 'no description given'
+ }
+ else {
+ $PolicyAssignmentDescription = $L1mgmtGroupSubPolicyAssignment.Properties.Description
+ }
+
+ if ($L1mgmtGroupSubPolicyAssignment.identity) {
+ $PolicyAssignmentIdentity = $L1mgmtGroupSubPolicyAssignment.identity.principalId
+ }
+ else {
+ $PolicyAssignmentIdentity = 'n/a'
+ }
+
+ $assignedBy = 'n/a'
+ $createdBy = ''
+ $createdOn = ''
+ $updatedBy = ''
+ $updatedOn = ''
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata) {
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.assignedBy) {
+ $assignedBy = $L1mgmtGroupSubPolicyAssignment.properties.metadata.assignedBy
+ }
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.createdBy) {
+ $createdBy = $L1mgmtGroupSubPolicyAssignment.properties.metadata.createdBy
+ }
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.createdOn) {
+ $createdOn = $L1mgmtGroupSubPolicyAssignment.properties.metadata.createdOn
+ }
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.updatedBy) {
+ $updatedBy = $L1mgmtGroupSubPolicyAssignment.properties.metadata.updatedBy
+ }
+ if ($L1mgmtGroupSubPolicyAssignment.properties.metadata.updatedOn) {
+ $updatedOn = $L1mgmtGroupSubPolicyAssignment.properties.metadata.updatedOn
+ }
+ }
+
+ if (($L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message) {
+ $nonComplianceMessage = ($L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message
+ }
+ else {
+ $nonComplianceMessage = ''
+ }
+
+ $formatedPolicyAssignmentParameters = ''
+ $hlp = $L1mgmtGroupSubPolicyAssignment.Properties.Parameters
+ if (-not [string]::IsNullOrEmpty($hlp)) {
+ $arrayPolicyAssignmentParameters = @()
+ $arrayPolicyAssignmentParameters = foreach ($parameterName in $hlp.PSObject.Properties.Name | Sort-Object) {
+ "$($parameterName)=$($hlp.($parameterName).Value -join "$($CsvDelimiter) ")"
+ }
+ $formatedPolicyAssignmentParameters = $arrayPolicyAssignmentParameters -join "$($CsvDelimiterOpposite) "
+ }
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $childMgDisplayName `
+ -mgId $childMgId `
+ -mgParentId $childMgParentId `
+ -mgParentName $childMgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Subscription $scopeDisplayName `
+ -SubscriptionId $scopeId `
+ -SubscriptionQuotaId $subscriptionQuotaId `
+ -SubscriptionState $subscriptionState `
+ -SubscriptionASCSecureScore $subscriptionASCSecureScore `
+ -SubscriptionTags $subscriptionTags `
+ -SubscriptionTagsCount $subscriptionTagsCount `
+ -Policy $policySetDisplayName `
+ -PolicyAvailability $policyAvailability `
+ -PolicyDescription $policySetDescription `
+ -PolicyVariant $policyVariant `
+ -PolicyType $policySetDefinitionType `
+ -PolicyIsALZ $policySetDefinitionIsALZ `
+ -PolicyCategory $policySetCategory `
+ -PolicyDefinitionIdGuid (($policySetDefinitionId) -replace '.*/') `
+ -PolicyDefinitionId $policySetDefinitionId `
+ -PolicyDefintionScope $policySetDefintionScope `
+ -PolicyDefintionScopeMgSub $policySetDefintionScopeMgSub `
+ -PolicyDefintionScopeId $policySetDefintionScopeId `
+ -PolicyDefinitionsScopedLimit $LimitPOLICYPolicyDefinitionsScopedSubscription `
+ -PolicyDefinitionsScopedCount $PolicyDefinitionsScopedCount `
+ -PolicySetDefinitionsScopedLimit $LimitPOLICYPolicySetDefinitionsScopedSubscription `
+ -PolicySetDefinitionsScopedCount $PolicySetDefinitionsScopedCount `
+ -PolicyAssignmentScope $PolicyAssignmentScope `
+ -PolicyAssignmentScopeMgSubRg $PolicyAssignmentScopeMgSubRg `
+ -PolicyAssignmentScopeName ($PolicyAssignmentScope -replace '.*/', '') `
+ -PolicyAssignmentNotScopes $L1mgmtGroupSubPolicyAssignment.Properties.NotScopes `
+ -PolicyAssignmentId $PolicyAssignmentId `
+ -PolicyAssignmentName $PolicyAssignmentName `
+ -PolicyAssignmentDisplayName $PolicyAssignmentDisplayName `
+ -PolicyAssignmentDescription $PolicyAssignmentDescription `
+ -PolicyAssignmentEnforcementMode $L1mgmtGroupSubPolicyAssignment.Properties.EnforcementMode `
+ -PolicyAssignmentNonComplianceMessages $nonComplianceMessage `
+ -PolicyAssignmentIdentity $PolicyAssignmentIdentity `
+ -PolicyAssignmentLimit $LimitPOLICYPolicyAssignmentsSubscription `
+ -PolicyAssignmentCount $L1mgmtGroupSubPolicyAssignmentsPolicyCount `
+ -PolicyAssignmentAtScopeCount $L1mgmtGroupSubPolicyAssignmentsPolicyAtScopeCount `
+ -PolicyAssignmentParameters $L1mgmtGroupSubPolicyAssignment.Properties.Parameters `
+ -PolicyAssignmentParametersFormated $formatedPolicyAssignmentParameters `
+ -PolicyAssignmentAssignedBy $assignedBy `
+ -PolicyAssignmentCreatedBy $createdBy `
+ -PolicyAssignmentCreatedOn $createdOn `
+ -PolicyAssignmentUpdatedBy $updatedBy `
+ -PolicyAssignmentUpdatedOn $updatedOn `
+ -PolicySetAssignmentLimit $LimitPOLICYPolicySetAssignmentsSubscription `
+ -PolicySetAssignmentCount $L1mgmtGroupSubPolicyAssignmentsPolicySetCount `
+ -PolicySetAssignmentAtScopeCount $L1mgmtGroupSubPolicyAssignmentsPolicySetAtScopeCount `
+ -PolicyAndPolicySetAssignmentAtScopeCount $L1mgmtGroupSubPolicyAssignmentsPolicyAndPolicySetAtScopeCount
+ }
+ }
+ }
+
+ $returnObject = @{}
+ if ($addRowToTableDone) {
+ $returnObject.'addRowToTableDone' = @{}
+ }
+ return $returnObject
+}
+$funcDataCollectionPolicyAssignmentsSub = $function:dataCollectionPolicyAssignmentsSub.ToString()
+
+function dataCollectionRoleDefinitions {
+ [CmdletBinding()]Param(
+ [string]$TargetMgOrSub,
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $subscriptionQuotaId
+ )
+
+ if ($TargetMgOrSub -eq 'Sub') {
+ $currentTask = "Getting Custom Role definitions for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleDefinitions?api-version=2018-07-01&`$filter=type eq 'CustomRole'"
+ }
+ if ($TargetMgOrSub -eq 'MG') {
+ $currentTask = "Getting Custom Role definitions for Management Group: '$($scopeDisplayName)' ('$scopeId')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleDefinitions?api-version=2018-07-01&`$filter=type eq 'CustomRole'"
+ }
+ $method = 'GET'
+ $scopeCustomRoleDefinitions = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ foreach ($scopeCustomRoleDefinition in $scopeCustomRoleDefinitions) {
+ if (-not $($htCacheDefinitionsRole).($scopeCustomRoleDefinition.name)) {
+
+ if (
+ (
+ $scopeCustomRoleDefinition.properties.permissions.Actions -contains 'Microsoft.Authorization/roleassignments/write' -or
+ $scopeCustomRoleDefinition.properties.permissions.Actions -contains 'Microsoft.Authorization/roleassignments/*' -or
+ $scopeCustomRoleDefinition.properties.permissions.Actions -contains 'Microsoft.Authorization/*/write' -or
+ $scopeCustomRoleDefinition.properties.permissions.Actions -contains 'Microsoft.Authorization/*' -or
+ $scopeCustomRoleDefinition.properties.permissions.Actions -contains '*/write' -or
+ $scopeCustomRoleDefinition.properties.permissions.Actions -contains '*'
+ ) -and (
+ $scopeCustomRoleDefinition.properties.permissions.NotActions -notcontains 'Microsoft.Authorization/roleassignments/write' -and
+ $scopeCustomRoleDefinition.properties.permissions.NotActions -notcontains 'Microsoft.Authorization/roleassignments/*' -and
+ $scopeCustomRoleDefinition.properties.permissions.NotActions -notcontains 'Microsoft.Authorization/*/write' -and
+ $scopeCustomRoleDefinition.properties.permissions.NotActions -notcontains 'Microsoft.Authorization/*' -and
+ $scopeCustomRoleDefinition.properties.permissions.NotActions -notcontains '*/write' -and
+ $scopeCustomRoleDefinition.properties.permissions.NotActions -notcontains '*'
+ )
+ ) {
+ $roleCapable4RoleAssignmentsWrite = $true
+ }
+ else {
+ $roleCapable4RoleAssignmentsWrite = $false
+ }
+
+ $htTemp = @{}
+ $htTemp.Id = $($scopeCustomRoleDefinition.name)
+ $htTemp.Name = $($scopeCustomRoleDefinition.properties.roleName)
+ $htTemp.IsCustom = $true
+ $htTemp.AssignableScopes = $($scopeCustomRoleDefinition.properties.AssignableScopes)
+ $htTemp.Actions = $($scopeCustomRoleDefinition.properties.permissions.Actions)
+ $htTemp.NotActions = $($scopeCustomRoleDefinition.properties.permissions.NotActions)
+ $htTemp.DataActions = $($scopeCustomRoleDefinition.properties.permissions.DataActions)
+ $htTemp.NotDataActions = $($scopeCustomRoleDefinition.properties.permissions.NotDataActions)
+ $htTemp.Json = $scopeCustomRoleDefinition
+ $htTemp.RoleCanDoRoleAssignments = $roleCapable4RoleAssignmentsWrite
+ ($script:htCacheDefinitionsRole).($scopeCustomRoleDefinition.name) = $htTemp
+
+ #namingValidation
+ if (-not [string]::IsNullOrEmpty($scopeCustomRoleDefinition.properties.roleName)) {
+ $namingValidationResult = NamingValidation -toCheck $scopeCustomRoleDefinition.properties.roleName
+ if ($namingValidationResult.Count -gt 0) {
+ $script:htNamingValidation.Role.($scopeCustomRoleDefinition.name) = @{}
+ $script:htNamingValidation.Role.($scopeCustomRoleDefinition.name).roleNameInvalidChars = ($namingValidationResult -join '')
+ $script:htNamingValidation.Role.($scopeCustomRoleDefinition.name).roleName = $scopeCustomRoleDefinition.properties.roleName
+ }
+ }
+ }
+ }
+}
+$funcDataCollectionRoleDefinitions = $function:dataCollectionRoleDefinitions.ToString()
+
+function dataCollectionRoleAssignmentsMG {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $hierarchyLevel,
+ $mgParentId,
+ $mgParentName,
+ $mgAscSecureScoreResult
+ )
+
+ $addRowToTableDone = $false
+ #PIM MGRoleAssignmentScheduleInstances
+ if ($htDoARMRoleAssignmentScheduleInstances.Do -eq $true) {
+ $currentTask = "Getting ARM RoleAssignment ScheduleInstances for Management Group: '$($scopeDisplayName)' ('$($scopeId)')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentScheduleInstances?api-version=2020-10-01"
+ $method = 'GET'
+ $roleAssignmentScheduleInstancesFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($roleAssignmentScheduleInstancesFromAPI -eq 'RoleAssignmentScheduleInstancesError' -or $roleAssignmentScheduleInstancesFromAPI -eq 'AadPremiumLicenseRequired') {
+ if ($roleAssignmentScheduleInstancesFromAPI -eq 'AadPremiumLicenseRequired') {
+ Write-Host " -> Setting 'htDoARMRoleAssignmentScheduleInstances.Do' to false (AadPremiumLicenseRequired)"
+ $script:htDoARMRoleAssignmentScheduleInstances.Do = $false
+ }
+ }
+ else {
+ $roleAssignmentScheduleInstances = ($roleAssignmentScheduleInstancesFromAPI.where( { ($_.properties.roleAssignmentScheduleId -replace '.*/') -ne ($_.properties.originRoleAssignmentId -replace '.*/') }))
+ $roleAssignmentScheduleInstancesCount = $roleAssignmentScheduleInstances.Count
+ if ($roleAssignmentScheduleInstancesCount -gt 0) {
+ #$htRoleAssignmentsPIM = @{}
+ foreach ($roleAssignmentScheduleInstance in $roleAssignmentScheduleInstances) {
+ $script:htRoleAssignmentsPIM.($roleAssignmentScheduleInstance.properties.originRoleAssignmentId.tolower()) = $roleAssignmentScheduleInstance.properties
+ }
+ }
+ }
+ }
+
+ #RoleAssignment API MG
+ $currentTask = "Getting Role assignments API for Management Group: '$($scopeDisplayName)' ('$($scopeId)')"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01"
+ $method = 'GET'
+ $roleAssignmentsFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($roleAssignmentsFromAPI.Count -gt 0) {
+ $principalsToResolve = @()
+ $principalsToResolve = foreach ($ra in $roleAssignmentsFromAPI.properties | Sort-Object -Property principalId -Unique) {
+ if (-not $htPrincipals.($ra.principalId)) {
+ $ra.principalId
+ }
+ }
+
+ if ($principalsToResolve.Count -gt 0) {
+ ResolveObjectIds -objectIds $principalsToResolve
+ }
+ }
+
+ $L0mgmtGroupRoleAssignments = $roleAssignmentsFromAPI
+
+ $L0mgmtGroupRoleAssignmentsLimitUtilization = (($L0mgmtGroupRoleAssignments.properties.where( { $_.scope -eq "/providers/Microsoft.Management/managementGroups/$($scopeId)" } ))).count
+ if (-not $htMgAtScopeRoleAssignments.($scopeId)) {
+ $script:htMgAtScopeRoleAssignments.($scopeId) = @{}
+ $script:htMgAtScopeRoleAssignments.($scopeId).AssignmentsCount = $L0mgmtGroupRoleAssignmentsLimitUtilization
+ }
+
+ if ($azAPICallConf['htParameters'].LargeTenant -eq $true -or $azAPICallConf['htParameters'].RBACAtScopeOnly -eq $true) {
+ $L0mgmtGroupRoleAssignments = $L0mgmtGroupRoleAssignments.where( { $_.properties.scope -eq "/providers/Microsoft.Management/managementGroups/$($scopeId)" } )
+ }
+ else {
+ #tenantLevelRoleAssignments
+ if (-not $htMgAtScopeRoleAssignments.'tenantLevelRoleAssignments') {
+ $tenantLevelRoleAssignmentsCount = (($L0mgmtGroupRoleAssignments.where( { $_.id -like '/providers/Microsoft.Authorization/roleAssignments/*' } ))).count
+ $script:htMgAtScopeRoleAssignments.'tenantLevelRoleAssignments' = @{}
+ $script:htMgAtScopeRoleAssignments.'tenantLevelRoleAssignments'.AssignmentsCount = $tenantLevelRoleAssignmentsCount
+ }
+ }
+ foreach ($L0mgmtGroupRoleAssignment in $L0mgmtGroupRoleAssignments) {
+ $roleAssignmentId = ($L0mgmtGroupRoleAssignment.id).ToLower()
+
+ if ($htRoleAssignmentsPIM.($roleAssignmentId)) {
+ $hlperPim = $htRoleAssignmentsPIM.($roleAssignmentId)
+ $pim = 'true'
+ $pimAssignmentType = $hlperPim.assignmentType
+ $pimSlotStart = $($hlperPim.startDateTime)
+ if ($hlperPim.endDateTime) {
+ $pimSlotEnd = $($hlperPim.endDateTime)
+ }
+ else {
+ $pimSlotEnd = 'eternity'
+ }
+ }
+ else {
+ $pim = 'false'
+ $pimAssignmentType = ''
+ $pimSlotStart = ''
+ $pimSlotEnd = ''
+ }
+
+ if (-not $htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace '.*/')) {
+ $script:htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace '.*/') = @{}
+ $script:htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace '.*/').assignment = $L0mgmtGroupRoleAssignment
+ }
+
+ $roleDefinitionId = $L0mgmtGroupRoleAssignment.properties.roleDefinitionId
+ $roleDefinitionIdGuid = $roleDefinitionId -replace '.*/'
+
+ if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) {
+ $roleAssignmentsRoleDefinition = ''
+ $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'"
+ }
+ else {
+ $roleAssignmentsRoleDefinition = ($htCacheDefinitionsRole).($roleDefinitionIdGuid)
+ $roleDefinitionName = $roleAssignmentsRoleDefinition.Name
+ }
+
+ $doIt = $false
+ if ($L0mgmtGroupRoleAssignment.properties.scope -eq "/providers/Microsoft.Management/managementGroups/$($scopeId)" -and $L0mgmtGroupRoleAssignment.properties.scope -ne "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)") {
+ $doIt = $true
+ }
+ if ($scopeId -eq $ManagementGroupId) {
+ $doIt = $true
+ }
+
+ if ($doIt) {
+ #assignment
+ $splitAssignment = ($roleAssignmentId).Split('/')
+ $arrayRoleAssignment = [System.Collections.ArrayList]@()
+ $null = $arrayRoleAssignment.Add([PSCustomObject]@{
+ RoleAssignmentId = $roleAssignmentId
+ Scope = $L0mgmtGroupRoleAssignment.properties.scope
+ DisplayName = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).displayName
+ SignInName = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName
+ RoleDefinitionName = $roleDefinitionName
+ RoleDefinitionId = $L0mgmtGroupRoleAssignment.properties.roleDefinitionId -replace '.*/'
+ ObjectId = $L0mgmtGroupRoleAssignment.properties.principalId
+ ObjectType = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).type
+ PIM = $pim
+ })
+
+ $htTemp = @{}
+ $htTemp.Assignment = $arrayRoleAssignment
+
+ if ($roleAssignmentId -like '/providers/Microsoft.Authorization/roleAssignments/*') {
+ $htTemp.AssignmentScopeTenMgSubRgRes = 'Tenant'
+ $htTemp.AssignmentScopeId = 'Tenant'
+ }
+ else {
+ $htTemp.AssignmentScopeTenMgSubRgRes = 'Mg'
+ $htTemp.AssignmentScopeId = [string]$splitAssignment[4]
+ }
+ ($script:htCacheAssignmentsRole).($roleAssignmentId) = $htTemp
+ }
+
+ if (($htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).displayName).length -eq 0) {
+ $roleAssignmentIdentityDisplayname = 'n/a'
+ }
+ else {
+ if ($htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).type -eq 'User') {
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $false) {
+ $roleAssignmentIdentityDisplayname = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).displayName
+ }
+ else {
+ $roleAssignmentIdentityDisplayname = 'scrubbed'
+ }
+ }
+ else {
+ $roleAssignmentIdentityDisplayname = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).displayName
+ }
+ }
+ if (-not $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName) {
+ $roleAssignmentIdentitySignInName = 'n/a'
+ }
+ else {
+ if ($htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).type -eq 'User') {
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $false) {
+ $roleAssignmentIdentitySignInName = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName
+ }
+ else {
+ $roleAssignmentIdentitySignInName = 'scrubbed'
+ }
+ }
+ else {
+ $roleAssignmentIdentitySignInName = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName
+ }
+ }
+ $roleAssignmentIdentityObjectId = $L0mgmtGroupRoleAssignment.properties.principalId
+ $roleAssignmentIdentityObjectType = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).type
+ $roleAssignmentScope = $L0mgmtGroupRoleAssignment.properties.scope
+ $roleAssignmentScopeName = $roleAssignmentScope -replace '.*/'
+ $roleAssignmentScopeType = 'MG'
+
+ $roleSecurityCustomRoleOwner = 0
+ if ($roleAssignmentsRoleDefinition.Actions -eq '*' -and (($roleAssignmentsRoleDefinition.NotActions)).length -eq 0 -and $roleAssignmentsRoleDefinition.IsCustom -eq $True) {
+ $roleSecurityCustomRoleOwner = 1
+ }
+ $roleSecurityOwnerAssignmentSP = 0
+ if (($roleAssignmentsRoleDefinition.Id -eq '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' -and $roleAssignmentIdentityObjectType -eq 'ServicePrincipal') -or ($roleAssignmentsRoleDefinition.Actions -eq '*' -and (($roleAssignmentsRoleDefinition.NotActions)).length -eq 0 -and $roleAssignmentsRoleDefinition.IsCustom -eq $True -and $roleAssignmentIdentityObjectType -eq 'ServicePrincipal')) {
+ $roleSecurityOwnerAssignmentSP = 1
+ }
+
+ $createdBy = ''
+ $createdOn = ''
+ $createdOnUnformatted = $null
+ $updatedBy = ''
+ $updatedOn = ''
+
+ if ($L0mgmtGroupRoleAssignment.properties.createdBy) {
+ $createdBy = $L0mgmtGroupRoleAssignment.properties.createdBy
+ }
+ if ($L0mgmtGroupRoleAssignment.properties.createdOn) {
+ $createdOn = $L0mgmtGroupRoleAssignment.properties.createdOn
+ }
+ if ($L0mgmtGroupRoleAssignment.properties.updatedBy) {
+ $updatedBy = $L0mgmtGroupRoleAssignment.properties.updatedBy
+ }
+ if ($L0mgmtGroupRoleAssignment.properties.updatedOn) {
+ $updatedOn = $L0mgmtGroupRoleAssignment.properties.updatedOn
+ }
+ $createdOnUnformatted = $L0mgmtGroupRoleAssignment.properties.createdOn
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $scopeDisplayName `
+ -mgId $scopeId `
+ -mgParentId $mgParentId `
+ -mgParentName $mgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -RoleDefinitionId $roleDefinitionIdGuid `
+ -RoleDefinitionName $roleDefinitionName `
+ -RoleIsCustom $roleAssignmentsRoleDefinition.IsCustom `
+ -RoleAssignableScopes ($roleAssignmentsRoleDefinition.AssignableScopes -join "$CsvDelimiterOpposite ") `
+ -RoleActions ($roleAssignmentsRoleDefinition.Actions -join "$CsvDelimiterOpposite ") `
+ -RoleNotActions ($roleAssignmentsRoleDefinition.NotActions -join "$CsvDelimiterOpposite ") `
+ -RoleDataActions ($roleAssignmentsRoleDefinition.DataActions -join "$CsvDelimiterOpposite ") `
+ -RoleNotDataActions ($roleAssignmentsRoleDefinition.NotDataActions -join "$CsvDelimiterOpposite ") `
+ -RoleCanDoRoleAssignments $roleAssignmentsRoleDefinition.RoleCanDoRoleAssignments `
+ -RoleAssignmentIdentityDisplayname $roleAssignmentIdentityDisplayname `
+ -RoleAssignmentIdentitySignInName $roleAssignmentIdentitySignInName `
+ -RoleAssignmentIdentityObjectId $roleAssignmentIdentityObjectId `
+ -RoleAssignmentIdentityObjectType $roleAssignmentIdentityObjectType `
+ -RoleAssignmentId $roleAssignmentId `
+ -RoleAssignmentScope $roleAssignmentScope `
+ -RoleAssignmentScopeName $roleAssignmentScopeName `
+ -RoleAssignmentScopeType $roleAssignmentScopeType `
+ -RoleAssignmentCreatedBy $createdBy `
+ -RoleAssignmentCreatedOn $createdOn `
+ -RoleAssignmentCreatedOnUnformatted $createdOnUnformatted `
+ -RoleAssignmentUpdatedBy $updatedBy `
+ -RoleAssignmentUpdatedOn $updatedOn `
+ -RoleAssignmentsLimit $LimitRBACRoleAssignmentsManagementGroup `
+ -RoleAssignmentsCount $L0mgmtGroupRoleAssignmentsLimitUtilization `
+ -RoleSecurityCustomRoleOwner $roleSecurityCustomRoleOwner `
+ -RoleSecurityOwnerAssignmentSP $roleSecurityOwnerAssignmentSP `
+ -RoleAssignmentPIM $pim `
+ -RoleAssignmentPIMAssignmentType $pimAssignmentType `
+ -RoleAssignmentPIMSlotStart $pimSlotStart `
+ -RoleAssignmentPIMSlotEnd $pimSlotEnd
+ }
+
+ $returnObject = @{}
+ if ($addRowToTableDone) {
+ $returnObject.'addRowToTableDone' = @{}
+ }
+ return $returnObject
+}
+$funcDataCollectionRoleAssignmentsMG = $function:dataCollectionRoleAssignmentsMG.ToString()
+
+function dataCollectionRoleAssignmentsSub {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ $hierarchyLevel,
+ $childMgDisplayName,
+ $childMgId,
+ $childMgParentId,
+ $childMgParentName,
+ $mgAscSecureScoreResult,
+ $subscriptionQuotaId,
+ $subscriptionState,
+ $subscriptionASCSecureScore,
+ $subscriptionTags,
+ $subscriptionTagsCount
+ )
+
+ $addRowToTableDone = $false
+ #Usage
+ $currentTask = "Getting Role assignments usage metrics for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentsUsageMetrics?api-version=2019-08-01-preview"
+ $method = 'GET'
+ $roleAssignmentsUsage = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -listenOn 'Content' -caller 'CustomDataCollection'
+
+ $script:htSubscriptionsRoleAssignmentLimit.($scopeId) = $roleAssignmentsUsage.roleAssignmentsLimit
+
+ #PIM SubscriptionRoleAssignmentScheduleInstances
+ if ($htDoARMRoleAssignmentScheduleInstances.Do -eq $true) {
+ $currentTask = "Getting ARM RoleAssignment ScheduleInstances for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentScheduleInstances?api-version=2020-10-01"
+ $method = 'GET'
+ $roleAssignmentScheduleInstancesFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ if ($roleAssignmentScheduleInstancesFromAPI -eq 'RoleAssignmentScheduleInstancesError' -or $roleAssignmentScheduleInstancesFromAPI -eq 'AadPremiumLicenseRequired') {
+ # this should not be required at sub level as the error would already have occured at mg level
+ # if ($roleAssignmentScheduleInstancesFromAPI -eq 'AadPremiumLicenseRequired') {
+ # Write-Host " Setting 'htDoARMRoleAssignmentScheduleInstances.Do' to false (AadPremiumLicenseRequired)"
+ # $script:htDoARMRoleAssignmentScheduleInstances.Do = $false
+ # }
+ }
+ else {
+ $roleAssignmentScheduleInstances = ($roleAssignmentScheduleInstancesFromAPI.where( { ($_.properties.roleAssignmentScheduleId -replace '.*/') -ne ($_.properties.originRoleAssignmentId -replace '.*/') }))
+ $roleAssignmentScheduleInstancesCount = $roleAssignmentScheduleInstances.Count
+ if ($roleAssignmentScheduleInstancesCount -gt 0) {
+ #$htRoleAssignmentsPIM = @{}
+ foreach ($roleAssignmentScheduleInstance in $roleAssignmentScheduleInstances) {
+ $script:htRoleAssignmentsPIM.($roleAssignmentScheduleInstance.properties.originRoleAssignmentId.tolower()) = $roleAssignmentScheduleInstance.properties
+ }
+ }
+ }
+ }
+
+ #RoleAssignment API Sub
+ $currentTask = "Getting Role assignments API for Subscription: '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01"
+ $method = 'GET'
+ $roleAssignmentsFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection'
+
+ $baseRoleAssignments = [System.Collections.ArrayList]@()
+ if ($roleAssignmentsFromAPI.Count -gt 0) {
+ foreach ($roleAssignmentFromAPI in $roleAssignmentsFromAPI) {
+
+ if ($roleAssignmentFromAPI.id -match "/subscriptions/$($scopeId)/") {
+ if (-not $htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentFromAPI.id -replace '.*/')) {
+ $null = $baseRoleAssignments.Add($roleAssignmentFromAPI)
+ }
+ else {
+ $null = $baseRoleAssignments.Add($htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentFromAPI.id -replace '.*/').assignment)
+ }
+ }
+ else {
+ $null = $baseRoleAssignments.Add($roleAssignmentFromAPI)
+ }
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC -eq $true) {
+ $relevantRAs = $baseRoleAssignments.where( { $_.id -notmatch "/subscriptions/$($scopeId)/resourcegroups/" } )
+ }
+ else {
+ $relevantRAs = $baseRoleAssignments
+ }
+ if ($relevantRAs.Count -gt 0) {
+ $principalsToResolve = @()
+ $principalsToResolve = foreach ($ra in $relevantRAs.properties | Sort-Object -Property principalId -Unique) {
+ if (-not $htPrincipals.($ra.principalId)) {
+ $ra.principalId
+ }
+ }
+
+ if ($principalsToResolve.Count -gt 0) {
+ ResolveObjectIds -objectIds $principalsToResolve
+ }
+ }
+
+
+ $L1mgmtGroupSubRoleAssignments = $baseRoleAssignments
+
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC -eq $true) {
+ foreach ($L1mgmtGroupSubRoleAssignmentOnRg in $L1mgmtGroupSubRoleAssignments.where( { $_.id -match "/subscriptions/$($scopeId)/resourcegroups/" } )) {
+ if (-not ($htCacheAssignmentsRBACOnResourceGroupsAndResources).($L1mgmtGroupSubRoleAssignmentOnRg.id)) {
+
+ $roleDefinitionId = $L1mgmtGroupSubRoleAssignmentOnRg.properties.roleDefinitionId
+ $roleDefinitionIdGuid = $roleDefinitionId -replace '.*/'
+
+ if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) {
+ $roleAssignmentsRoleDefinition = ''
+ $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'"
+ }
+ else {
+ $roleAssignmentsRoleDefinition = ($htCacheDefinitionsRole).($roleDefinitionIdGuid)
+ $roleDefinitionName = $roleAssignmentsRoleDefinition.Name
+ }
+
+ #assignment
+ $arrayRoleAssignment = [System.Collections.ArrayList]@()
+ $null = $arrayRoleAssignment.Add([PSCustomObject]@{
+ RoleAssignmentId = $L1mgmtGroupSubRoleAssignmentOnRg.id
+ Scope = $L1mgmtGroupSubRoleAssignmentOnRg.properties.scope
+ RoleDefinitionName = $roleDefinitionName
+ RoleDefinitionId = $L1mgmtGroupSubRoleAssignmentOnRg.properties.roleDefinitionId -replace '.*/'
+ ObjectId = $L1mgmtGroupSubRoleAssignmentOnRg.properties.principalId
+ })
+
+ ($script:htCacheAssignmentsRBACOnResourceGroupsAndResources).($L1mgmtGroupSubRoleAssignmentOnRg.id) = $arrayRoleAssignment
+ }
+ }
+ }
+
+ if ($azAPICallConf['htParameters'].LargeTenant -eq $true -or $azAPICallConf['htParameters'].RBACAtScopeOnly -eq $true) {
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC -eq $false) {
+ $assignmentsScope = $L1mgmtGroupSubRoleAssignments
+ }
+ else {
+ $assignmentsScope = $L1mgmtGroupSubRoleAssignments.where( { $_.properties.Scope -eq "/subscriptions/$($scopeId)" } )
+ }
+ }
+ else {
+ if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC -eq $false) {
+ $assignmentsScope = $L1mgmtGroupSubRoleAssignments
+ }
+ else {
+ $assignmentsScope = $L1mgmtGroupSubRoleAssignments.where( { $_.id -notmatch "/subscriptions/$($scopeId)/resourcegroups/" } )
+ }
+ }
+
+ foreach ($L1mgmtGroupSubRoleAssignment in $assignmentsScope) {
+
+ $roleAssignmentId = ($L1mgmtGroupSubRoleAssignment.id).ToLower()
+ $roleDefinitionId = $L1mgmtGroupSubRoleAssignment.properties.roleDefinitionId
+ $roleDefinitionIdGuid = $roleDefinitionId -replace '.*/'
+
+ if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) {
+ $roleAssignmentsRoleDefinition = ''
+ $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'"
+ }
+ else {
+ $roleAssignmentsRoleDefinition = ($htCacheDefinitionsRole).($roleDefinitionIdGuid)
+ $roleDefinitionName = $roleAssignmentsRoleDefinition.Name
+ }
+
+ $roleAssignmentIdentityObjectId = $L1mgmtGroupSubRoleAssignment.properties.principalId
+ $roleAssignmentIdentityObjectType = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).type
+ $roleAssignmentScope = $L1mgmtGroupSubRoleAssignment.properties.scope
+ $roleAssignmentScopeName = $roleAssignmentScope -replace '.*/'
+
+ if ($roleAssignmentScope -like '/subscriptions/*' -and $roleAssignmentScope -notlike '/subscriptions/*/resourcegroups/*') {
+ $roleAssignmentScopeType = 'Sub'
+ $roleAssignmentScopeRG = ''
+ $roleAssignmentScopeRes = ''
+ }
+ if ($roleAssignmentScope -like '/subscriptions/*/resourcegroups/*' -and $roleAssignmentScope -notlike '/subscriptions/*/resourcegroups/*/providers*') {
+ $roleAssignmentScopeType = 'RG'
+ $roleAssignmentScopeSplit = $roleAssignmentScope.Split('/')
+ $roleAssignmentScopeRG = $roleAssignmentScopeSplit[4]
+ $roleAssignmentScopeRes = ''
+ }
+ if ($roleAssignmentScope -like '/subscriptions/*/resourcegroups/*/providers*') {
+ $roleAssignmentScopeType = 'Res'
+ $roleAssignmentScopeSplit = $roleAssignmentScope.Split('/')
+ $roleAssignmentScopeRG = $roleAssignmentScopeSplit[4]
+ $roleAssignmentScopeRes = $roleAssignmentScopeSplit[8]
+ }
+
+ if ($htRoleAssignmentsPIM.($roleAssignmentId)) {
+ $hlperPim = $htRoleAssignmentsPIM.($roleAssignmentId)
+ $pim = 'true'
+ $pimAssignmentType = $hlperPim.assignmentType
+ $pimSlotStart = $($hlperPim.startDateTime)
+ if ($hlperPim.endDateTime) {
+ $pimSlotEnd = $($hlperPim.endDateTime)
+ }
+ else {
+ $pimSlotEnd = 'eternity'
+ }
+ }
+ else {
+ $pim = 'false'
+ $pimAssignmentType = ''
+ $pimSlotStart = ''
+ $pimSlotEnd = ''
+ }
+
+ if ($roleAssignmentId -like "/subscriptions/$($scopeId)/*") {
+
+ #assignment
+ $splitAssignment = ($roleAssignmentId).Split('/')
+ $arrayRoleAssignment = [System.Collections.ArrayList]@()
+ $null = $arrayRoleAssignment.Add([PSCustomObject]@{
+ RoleAssignmentId = $roleAssignmentId
+ Scope = $L1mgmtGroupSubRoleAssignment.properties.scope
+ DisplayName = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).displayName
+ SignInName = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).signInName
+ RoleDefinitionName = $roleDefinitionName
+ RoleDefinitionId = $L1mgmtGroupSubRoleAssignment.properties.roleDefinitionId -replace '.*/'
+ ObjectId = $L1mgmtGroupSubRoleAssignment.properties.principalId
+ ObjectType = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).type
+ PIM = $pim
+ })
+
+ $htTemp = @{}
+ $htTemp.Assignment = $arrayRoleAssignment
+
+ $htTemp.AssignmentScopeTenMgSubRgRes = $roleAssignmentScopeType
+ if ($roleAssignmentScopeType -eq 'Sub') {
+ $htTemp.AssignmentScopeId = [string]$splitAssignment[2]
+ }
+ if ($roleAssignmentScopeType -eq 'RG') {
+ $htTemp.AssignmentScopeId = "$($splitAssignment[2])/$($splitAssignment[4])"
+ }
+ if ($roleAssignmentScopeType -eq 'Res') {
+ $htTemp.AssignmentScopeId = "$($splitAssignment[2])/$($splitAssignment[4])/$($splitAssignment[8])"
+ $htTemp.ResourceType = "$($splitAssignment[6])-$($splitAssignment[7])"
+ }
+ ($script:htCacheAssignmentsRole).($roleAssignmentId) = $htTemp
+ }
+
+
+ if (($htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).displayName).length -eq 0) {
+ $roleAssignmentIdentityDisplayname = 'n/a'
+ }
+ else {
+ if ($htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).type -eq 'User') {
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $false) {
+ $roleAssignmentIdentityDisplayname = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).displayName
+ }
+ else {
+ $roleAssignmentIdentityDisplayname = 'scrubbed'
+ }
+ }
+ else {
+ $roleAssignmentIdentityDisplayname = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).displayName
+ }
+ }
+ if (-not $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).signInName) {
+ $roleAssignmentIdentitySignInName = 'n/a'
+ }
+ else {
+ if ($htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).type -eq 'User') {
+ if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $false) {
+ $roleAssignmentIdentitySignInName = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).signInName
+ }
+ else {
+ $roleAssignmentIdentitySignInName = 'scrubbed'
+ }
+ }
+ else {
+ $roleAssignmentIdentitySignInName = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).signInName
+ }
+ }
+
+ $roleSecurityCustomRoleOwner = 0
+ if (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).Actions -eq '*' -and ((($htCacheDefinitionsRole).$($roleDefinitionIdGuid).NotActions)).length -eq 0 -and ($htCacheDefinitionsRole).$($roleDefinitionIdGuid).IsCustom -eq $True) {
+ $roleSecurityCustomRoleOwner = 1
+ }
+ $roleSecurityOwnerAssignmentSP = 0
+ if ((($htCacheDefinitionsRole).$($roleDefinitionIdGuid).Id -eq '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' -and $roleAssignmentIdentityObjectType -eq 'ServicePrincipal') -or (($htCacheDefinitionsRole).$($roleDefinitionIdGuid).Actions -eq '*' -and ((($htCacheDefinitionsRole).$($roleDefinitionIdGuid).NotActions)).length -eq 0 -and ($htCacheDefinitionsRole).$($roleDefinitionIdGuid).IsCustom -eq $True -and $roleAssignmentIdentityObjectType -eq 'ServicePrincipal')) {
+ $roleSecurityOwnerAssignmentSP = 1
+ }
+
+ $createdBy = ''
+ $createdOn = ''
+ $createdOnUnformatted = $null
+ $updatedBy = ''
+ $updatedOn = ''
+
+ if ($L1mgmtGroupSubRoleAssignment.properties.createdBy) {
+ $createdBy = $L1mgmtGroupSubRoleAssignment.properties.createdBy
+ }
+ if ($L1mgmtGroupSubRoleAssignment.properties.createdOn) {
+ $createdOn = $L1mgmtGroupSubRoleAssignment.properties.createdOn
+ }
+ if ($L1mgmtGroupSubRoleAssignment.properties.updatedBy) {
+ $updatedBy = $L1mgmtGroupSubRoleAssignment.properties.updatedBy
+ }
+ if ($L1mgmtGroupSubRoleAssignment.properties.updatedOn) {
+ $updatedOn = $L1mgmtGroupSubRoleAssignment.properties.updatedOn
+ }
+ $createdOnUnformatted = $L1mgmtGroupSubRoleAssignment.properties.createdOn
+
+ $addRowToTableDone = $true
+ addRowToTable `
+ -level $hierarchyLevel `
+ -mgName $childMgDisplayName `
+ -mgId $childMgId `
+ -mgParentId $childMgParentId `
+ -mgParentName $childMgParentName `
+ -mgASCSecureScore $mgAscSecureScoreResult `
+ -Subscription $scopeDisplayName `
+ -SubscriptionId $scopeId `
+ -SubscriptionQuotaId $subscriptionQuotaId `
+ -SubscriptionState $subscriptionState `
+ -SubscriptionASCSecureScore $subscriptionASCSecureScore `
+ -SubscriptionTags $subscriptionTags `
+ -SubscriptionTagsCount $subscriptionTagsCount `
+ -RoleDefinitionId $roleDefinitionIdGuid `
+ -RoleDefinitionName $roleDefinitionName `
+ -RoleIsCustom $roleAssignmentsRoleDefinition.IsCustom `
+ -RoleAssignableScopes ($roleAssignmentsRoleDefinition.AssignableScopes -join "$CsvDelimiterOpposite ") `
+ -RoleActions ($roleAssignmentsRoleDefinition.Actions -join "$CsvDelimiterOpposite ") `
+ -RoleNotActions ($roleAssignmentsRoleDefinition.NotActions -join "$CsvDelimiterOpposite ") `
+ -RoleDataActions ($roleAssignmentsRoleDefinition.DataActions -join "$CsvDelimiterOpposite ") `
+ -RoleNotDataActions ($roleAssignmentsRoleDefinition.NotDataActions -join "$CsvDelimiterOpposite ") `
+ -RoleCanDoRoleAssignments $roleAssignmentsRoleDefinition.RoleCanDoRoleAssignments `
+ -RoleAssignmentIdentityDisplayname $roleAssignmentIdentityDisplayname `
+ -RoleAssignmentIdentitySignInName $roleAssignmentIdentitySignInName `
+ -RoleAssignmentIdentityObjectId $roleAssignmentIdentityObjectId `
+ -RoleAssignmentIdentityObjectType $roleAssignmentIdentityObjectType `
+ -RoleAssignmentId $roleAssignmentId `
+ -RoleAssignmentScope $roleAssignmentScope `
+ -RoleAssignmentScopeName $roleAssignmentScopeName `
+ -RoleAssignmentScopeRG $roleAssignmentScopeRG `
+ -RoleAssignmentScopeRes $roleAssignmentScopeRes `
+ -RoleAssignmentScopeType $roleAssignmentScopeType `
+ -RoleAssignmentCreatedBy $createdBy `
+ -RoleAssignmentCreatedOn $createdOn `
+ -RoleAssignmentCreatedOnUnformatted $createdOnUnformatted `
+ -RoleAssignmentUpdatedBy $updatedBy `
+ -RoleAssignmentUpdatedOn $updatedOn `
+ -RoleAssignmentsLimit $roleAssignmentsUsage.roleAssignmentsLimit `
+ -RoleAssignmentsCount $roleAssignmentsUsage.roleAssignmentsCurrentCount `
+ -RoleSecurityCustomRoleOwner $roleSecurityCustomRoleOwner `
+ -RoleSecurityOwnerAssignmentSP $roleSecurityOwnerAssignmentSP `
+ -RoleAssignmentPIM $pim `
+ -RoleAssignmentPIMAssignmentType $pimAssignmentType `
+ -RoleAssignmentPIMSlotStart $pimSlotStart `
+ -RoleAssignmentPIMSlotEnd $pimSlotEnd
+ }
+
+ $returnObject = @{}
+ if ($addRowToTableDone) {
+ $returnObject.'addRowToTableDone' = @{}
+ }
+ return $returnObject
+}
+$funcDataCollectionRoleAssignmentsSub = $function:dataCollectionRoleAssignmentsSub.ToString()
+
+function dataCollectionClassicAdministratorsSub {
+ [CmdletBinding()]Param(
+ [string]$scopeId,
+ [string]$scopeDisplayName,
+ [string]$subscriptionMgPath,
+ $subscriptionQuotaId
+ )
+
+ $apiEndPoint = $azAPICallConf['azAPIEndpointUrls'].ARM
+ $api = "/subscriptions/$($scopeId)/providers/Microsoft.Authorization/classicAdministrators"
+ $apiVersion = '?api-version=2015-07-01'
+ $uri = $apiEndPoint + $api + $apiVersion
+ $azAPICallPayload = @{
+ uri = $uri
+ method = 'GET'
+ currentTask = "classicAdministrators '$($scopeDisplayName)' ('$scopeId') [quotaId:'$subscriptionQuotaId']"
+ AzAPICallConfiguration = $azAPICallConf
+ }
+
+ $AzApiCallResult = AzAPICall @azAPICallPayload
+ if ($AzApiCallResult -ne 'ClassicAdministratorListFailed') {
+ $arrayClassicAdministrators = [System.Collections.ArrayList]@()
+ foreach ($roleAll in $AzApiCallResult) {
+ $splitPropertiesRole = $roleAll.properties.role.Split(';')
+ foreach ($role in $splitPropertiesRole) {
+ $null = $arrayClassicAdministrators.Add([PSCustomObject]@{
+ Subscription = $scopeDisplayName
+ SubscriptionId = $scopeId
+ SubscriptionMgPath = $subscriptionMgPath
+ Identity = $roleAll.properties.emailAddress
+ Role = $role
+ Id = $roleAll.id
+ })
+ }
+ }
+ $script:htClassicAdministrators.($scopeId) = @{}
+ $script:htClassicAdministrators.($scopeId).ClassicAdministrators = $arrayClassicAdministrators
+ }
+}
+$funcDataCollectionClassicAdministratorsSub = $function:dataCollectionClassicAdministratorsSub.ToString()
+
+#endregion functions4DataCollection
+#region HTML
+function HierarchyMgHTML($mgChild) {
+ $mgDetails = $htMgDetails.($mgChild).details
+ $mgName = $mgDetails.mgName
+ $mgId = $mgDetails.MgId
+
+ if ($mgId -eq ($azAPICallConf['checkContext']).Tenant.Id) {
+ if ($mgId -eq $defaultManagementGroupId) {
+ $class = "class=`"tenantRootGroup mgnonradius defaultMG`""
+ }
+ else {
+ $class = "class=`"tenantRootGroup mgnonradius`""
+ }
+ }
+ else {
+ if ($mgId -eq $defaultManagementGroupId) {
+ $class = "class=`"mgnonradius defaultMG`""
+ }
+ else {
+ $class = "class=`"mgnonradius`""
+ }
+ $liclass = ''
+ $liId = ''
+ }
+ if ($mgName -eq $mgId) {
+ $mgNameAndOrId = $mgName -replace '<', '<' -replace '>', '>'
+ }
+ else {
+ $mgNameAndOrId = "$($mgName -replace '<', '<' -replace '>', '>')$mgId "
+ }
+
+ $mgPolicyAssignmentCount = 0
+ if ($htMgAtScopePolicyAssignments.($mgId)) {
+ $mgPolicyAssignmentCount = $htMgAtScopePolicyAssignments.($mgId).AssignmentsCount
+ }
+ $mgPolicyPolicySetScopedCount = 0
+ if ($htMgAtScopePoliciesScoped.($mgId)) {
+ $mgPolicyPolicySetScopedCount = $htMgAtScopePoliciesScoped.($mgId).ScopedCount
+ }
+ $mgIdRoleAssignmentCount = 0
+ if ($htMgAtScopeRoleAssignments.($mgId)) {
+ $mgIdRoleAssignmentCount = $htMgAtScopeRoleAssignments.($mgId).AssignmentsCount
+ }
+ $script:html += @"
+
+
+
+
+
+
+
$($mgNameAndOrId)
+
+
+
+"@
+ $childMgs = $htMgDetails.($mgId).mgChildren
+ if (($childMgs).count -gt 0) {
+ $script:html += @'
+
+'@
+ foreach ($childMg in $childMgs) {
+ HierarchyMgHTML -mgChild $childMg
+ }
+ HierarchySubForMgHTML -mgChild $mgId
+ $script:html += @'
+
+
+'@
+ }
+ else {
+ HierarchySubForMgUlHTML -mgChild $mgId
+ $script:html += @'
+
+'@
+ }
+}
+
+function HierarchySubForMgHTML($mgChild) {
+ $subscriptions = $htMgDetails.($mgChild).Subscriptions.SubScriptionId
+ $subscriptionsCnt = ($subscriptions).count
+ $subscriptionsOutOfScopelinked = $outOfScopeSubscriptions.where( { $_.ManagementGroupId -eq $mgChild } )
+ $subscriptionsOutOfScopelinkedCnt = ($subscriptionsOutOfScopelinked).count
+ Write-Host " Building HierarchyMap for MG '$mgChild', $($subscriptionsCnt) Subscriptions"
+ if ($subscriptionsCnt -gt 0 -or $subscriptionsOutOfScopelinkedCnt -gt 0) {
+ if ($subscriptionsCnt -gt 0 -and $subscriptionsOutOfScopelinkedCnt -gt 0) {
+ $script:html += @"
+ $(($subscriptions).count)x
$(($subscriptionsOutOfScopelinked).count)x
+"@
+ }
+ if ($subscriptionsCnt -gt 0 -and $subscriptionsOutOfScopelinkedCnt -eq 0) {
+ $script:html += @"
+ $(($subscriptions).count)x
+"@
+ }
+ if ($subscriptionsCnt -eq 0 -and $subscriptionsOutOfScopelinkedCnt -gt 0) {
+ $script:html += @"
+ $(($subscriptionsOutOfScopelinked).count)x
+"@
+ }
+ }
+}
+
+function HierarchySubForMgUlHTML($mgChild) {
+ $subscriptions = $htMgDetails.($mgChild).Subscriptions.SubScriptionId
+ $subscriptionsCnt = ($subscriptions).count
+ $subscriptionsOutOfScopelinked = $outOfScopeSubscriptions.where( { $_.ManagementGroupId -eq $mgChild } )
+ $subscriptionsOutOfScopelinkedCnt = ($subscriptionsOutOfScopelinked).count
+ Write-Host " Building HierarchyMap for MG '$mgChild', $($subscriptionsCnt) Subscriptions"
+ if ($subscriptionsCnt -gt 0 -or $subscriptionsOutOfScopelinkedCnt -gt 0) {
+ if ($subscriptionsCnt -gt 0 -and $subscriptionsOutOfScopelinkedCnt -gt 0) {
+ $script:html += @"
+
+"@
+ }
+ if ($subscriptionsCnt -gt 0 -and $subscriptionsOutOfScopelinkedCnt -eq 0) {
+ $script:html += @"
+
+"@
+ }
+ if ($subscriptionsCnt -eq 0 -and $subscriptionsOutOfScopelinkedCnt -gt 0) {
+ $script:html += @"
+
+"@
+ }
+ }
+}
+
+function processScopeInsights($mgChild, $mgChildOf) {
+ $mgDetails = $htMgDetails.($mgChild).details
+ $mgName = $mgDetails.mgName
+ $mgLevel = $mgDetails.Level
+ $mgId = $mgDetails.MgId
+
+ if (-not $NoScopeInsights) {
+ if ($mgId -eq $defaultManagementGroupId) {
+ $classDefaultMG = 'defaultMG'
+ }
+ else {
+ $classDefaultMG = ''
+ }
+
+ switch ($mgLevel) {
+ '0' { $levelSpacing = '| L0 – ' }
+ '1' { $levelSpacing = '| – L1 – ' }
+ '2' { $levelSpacing = '| – – L2 – ' }
+ '3' { $levelSpacing = '| – – – L3 – ' }
+ '4' { $levelSpacing = '| – – – – L4 – ' }
+ '5' { $levelSpacing = '| – – – – – L5 – ' }
+ '6' { $levelSpacing = '| – – – – – – L6 – ' }
+ }
+
+ $mgPath = $htManagementGroupsMgPath.($mgChild).pathDelimited
+
+ $mgLinkedSubsCount = ((($optimizedTableForPathQuery.where( { $_.MgId -eq $mgChild -and -not [String]::IsNullOrEmpty($_.SubscriptionId) } )).SubscriptionId | Get-Unique)).count
+ $subscriptionsOutOfScopelinkedCount = ($outOfScopeSubscriptions.where( { $_.ManagementGroupId -eq $mgChild } )).count
+ if ($mgLinkedSubsCount -gt 0 -and $subscriptionsOutOfScopelinkedCount -eq 0) {
+ $subInfo = " $mgLinkedSubsCount"
+ }
+ if ($mgLinkedSubsCount -gt 0 -and $subscriptionsOutOfScopelinkedCount -gt 0) {
+ $subInfo = " $mgLinkedSubsCount $subscriptionsOutOfScopelinkedCount"
+ }
+ if ($mgLinkedSubsCount -eq 0 -and $subscriptionsOutOfScopelinkedCount -gt 0) {
+ $subInfo = " $subscriptionsOutOfScopelinkedCount"
+ }
+ if ($mgLinkedSubsCount -eq 0 -and $subscriptionsOutOfScopelinkedCount -eq 0) {
+ $subInfo = " "
+ }
+
+ if ($mgName -eq $mgId) {
+ $mgNameAndOrId = "$($mgName -replace '<', '<' -replace '>', '>')"
+ }
+ else {
+ $mgNameAndOrId = "$($mgName -replace '<', '<' -replace '>', '>') ($mgId)"
+ }
+
+ $script:html += @"
+$levelSpacing $mgNameAndOrId $subInfo
+
+
+ Highlight Management Group in HierarchyMap
+"@
+ if ($mgId -eq $defaultManagementGroupId) {
+ $script:html += @'
+ Default Management Group docs
+'@
+ }
+ $script:html += @"
+Management Group Name: $($mgName -replace '<', '<' -replace '>', '>')
+Management Group Id: $mgId
+Management Group Path: $mgPath
+"@
+ }
+ processScopeInsightsMgOrSub -mgOrSub 'mg' -mgchild $mgId
+ processScopeInsightsMGSubs -mgChild $mgId
+ $childMgs = $htMgDetails.($mgId).mgChildren
+ if (($childMgs).count -gt 0) {
+ foreach ($childMg in $childMgs) {
+ processScopeInsights -mgChild $childMg -mgChildOf $mgId
+ }
+ }
+}
+
+function processScopeInsightsMGSubs($mgChild) {
+ $subscriptions = $htMgDetails.($mgChild).Subscriptions
+ $subscriptionLinkedCount = ($subscriptions).count
+ $subscriptionsOutOfScopelinked = $outOfScopeSubscriptions.where( { $_.ManagementGroupId -eq $mgChild } )
+ $subscriptionsOutOfScopelinkedCount = ($subscriptionsOutOfScopelinked).count
+ if ($subscriptionsOutOfScopelinkedCount -gt 0) {
+ $subscriptionsOutOfScopelinkedDetail = "($($subscriptionsOutOfScopelinkedCount) out-of-scope)"
+ }
+ else {
+ $subscriptionsOutOfScopelinkedDetail = ''
+ }
+ Write-Host " Building ScopeInsights MG '$mgChild', $subscriptionLinkedCount Subscriptions"
+
+ if ($subscriptionLinkedCount -gt 0) {
+ if (-not $NoScopeInsights) {
+ $script:html += @"
+
+
+ $subscriptionLinkedCount Subscriptions linked $subscriptionsOutOfScopelinkedDetail
+
+"@
+ }
+ foreach ($subEntry in $subscriptions | Sort-Object -Property subscription, subscriptionId) {
+ #$subPath = $htSubscriptionsMgPath.($subEntry.subscriptionId).pathDelimited
+ if ($subscriptionLinkedCount -gt 1) {
+ if (-not $NoScopeInsights) {
+ $script:html += @"
+
$($subEntry.subscription -replace '<', '<' -replace '>', '>') ($($subEntry.subscriptionId))
+
+"@
+ }
+ }
+ #exactly 1
+ else {
+ if (-not $NoScopeInsights) {
+ $script:html += @"
+
$($subEntry.subscription -replace '<', '<' -replace '>', '>') ($($subEntry.subscriptionId))
+"@
+ }
+ }
+ if (-not $NoScopeInsights) {
+ $script:html += @"
+
+ Highlight Subscription in HierarchyMap
+"@
+ }
+
+ if (-not $azAPICallConf['htParameters'].ManagementGroupsOnly) {
+ processScopeInsightsMgOrSub -mgOrSub 'sub' -subscriptionId $subEntry.subscriptionId -subscriptionsMgId $mgChild
+ }
+
+ if (-not $NoScopeInsights) {
+ $script:html += @'
+
+'@
+ }
+ if ($subscriptionLinkedCount -gt 1) {
+ if (-not $NoScopeInsights) {
+ $script:html += @'
+
+'@
+ }
+ }
+ }
+ if (-not $NoScopeInsights) {
+ $script:html += @'
+
+'@
+ }
+
+ }
+ else {
+ if (-not $NoScopeInsights) {
+ $script:html += @"
+
+
+ $subscriptionLinkedCount Subscriptions linked $subscriptionsOutOfScopelinkedDetail
+"@
+ }
+ }
+ if (-not $NoScopeInsights) {
+ $script:html += @'
+
+
+
+
+
+'@
+ }
+}
+#endregion HTML
#endregion Functions
$funcAddRowToTable = $function:addRowToTable.ToString()
From 05995199a9eab308c1340fa4cd38e56f2ff7ead5 Mon Sep 17 00:00:00 2001
From: Kai Schulz
Date: Fri, 15 Dec 2023 14:42:31 +0100
Subject: [PATCH 3/5] final test
---
pwsh/AzGovVizParallel.ps1 | 22 ++--------------------
version.json | 2 +-
2 files changed, 3 insertions(+), 21 deletions(-)
diff --git a/pwsh/AzGovVizParallel.ps1 b/pwsh/AzGovVizParallel.ps1
index 154805ac..e042db2d 100644
--- a/pwsh/AzGovVizParallel.ps1
+++ b/pwsh/AzGovVizParallel.ps1
@@ -12461,16 +12461,7 @@ function processStorageAccountAnalysis {
}
else {
try {
- # ? https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/issues/218#issuecomment-1854516882
- if($saProperties.gettype().Name -eq 'Byte[]') {
- $byteArray = [byte[]]$saProperties
- $saProperties = [System.Text.Encoding]::UTF8.GetString($byteArray)
- }
-
- # $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
- # $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
- $xmlSaProperties = [xml]($saProperties -replace '^.*?<', '<') # Universal fix for all PS versions
-
+ $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 3))
if ($xmlSaProperties.StorageServiceProperties.StaticWebsite) {
if ($xmlSaProperties.StorageServiceProperties.StaticWebsite.Enabled -eq $true) {
$staticWebsitesState = $true
@@ -12503,16 +12494,7 @@ function processStorageAccountAnalysis {
}
if ($listContainersSuccess -eq $true) {
- # ? https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/issues/218#issuecomment-1854516882
- if($listContainers.gettype().Name -eq 'Byte[]') {
- $byteArray = [byte[]]$listContainers
- $listContainers = [System.Text.Encoding]::UTF8.GetString($byteArray)
- }
-
- # $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
- # $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
- $xmlListContainers = [xml]($listContainers -replace '^.*?<', '<') # Universal fix for all PS versions
-
+ $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 3))
$containersCount = $xmlListContainers.EnumerationResults.Containers.Container.Count
foreach ($container in $xmlListContainers.EnumerationResults.Containers.Container) {
diff --git a/version.json b/version.json
index 3f21333e..ca651092 100644
--- a/version.json
+++ b/version.json
@@ -1,3 +1,3 @@
{
- "ProductVersion": "6.3.4"
+ "ProductVersion": "6.3.5"
}
\ No newline at end of file
From 8813de63d0d59183391cb9a1490c09be0cdd7217 Mon Sep 17 00:00:00 2001
From: Kai Schulz
Date: Fri, 15 Dec 2023 16:20:48 +0100
Subject: [PATCH 4/5] Function edit
---
pwsh/AzGovVizParallel.ps1 | 20 +++++++++++++++++--
.../processStorageAccountAnalysis.ps1 | 20 +++++++++++++++++--
2 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/pwsh/AzGovVizParallel.ps1 b/pwsh/AzGovVizParallel.ps1
index e042db2d..97df6c26 100644
--- a/pwsh/AzGovVizParallel.ps1
+++ b/pwsh/AzGovVizParallel.ps1
@@ -12461,7 +12461,15 @@ function processStorageAccountAnalysis {
}
else {
try {
- $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 3))
+ # ? https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/issues/218#issuecomment-1854516882
+ if ($saProperties.gettype().Name -eq 'Byte[]') {
+ $byteArray = [byte[]]$saProperties
+ $saProperties = [System.Text.Encoding]::UTF8.GetString($byteArray)
+ }
+
+ # $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
+ # $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
+ $xmlSaProperties = [xml]($saProperties -replace '^.*?<', '<') # Universal fix for all PS versions
if ($xmlSaProperties.StorageServiceProperties.StaticWebsite) {
if ($xmlSaProperties.StorageServiceProperties.StaticWebsite.Enabled -eq $true) {
$staticWebsitesState = $true
@@ -12494,7 +12502,15 @@ function processStorageAccountAnalysis {
}
if ($listContainersSuccess -eq $true) {
- $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 3))
+ # ? https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/issues/218#issuecomment-1854516882
+ if ($listContainers.gettype().Name -eq 'Byte[]') {
+ $byteArray = [byte[]]$listContainers
+ $listContainers = [System.Text.Encoding]::UTF8.GetString($byteArray)
+ }
+
+ # $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
+ # $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
+ $xmlListContainers = [xml]($listContainers -replace '^.*?<', '<') # Universal fix for all PS versions
$containersCount = $xmlListContainers.EnumerationResults.Containers.Container.Count
foreach ($container in $xmlListContainers.EnumerationResults.Containers.Container) {
diff --git a/pwsh/dev/functions/processStorageAccountAnalysis.ps1 b/pwsh/dev/functions/processStorageAccountAnalysis.ps1
index 7177bfbf..3ed16835 100644
--- a/pwsh/dev/functions/processStorageAccountAnalysis.ps1
+++ b/pwsh/dev/functions/processStorageAccountAnalysis.ps1
@@ -59,7 +59,15 @@ function processStorageAccountAnalysis {
}
else {
try {
- $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 3))
+ # ? https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/issues/218#issuecomment-1854516882
+ if ($saProperties.gettype().Name -eq 'Byte[]') {
+ $byteArray = [byte[]]$saProperties
+ $saProperties = [System.Text.Encoding]::UTF8.GetString($byteArray)
+ }
+
+ # $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
+ # $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
+ $xmlSaProperties = [xml]($saProperties -replace '^.*?<', '<') # Universal fix for all PS versions
if ($xmlSaProperties.StorageServiceProperties.StaticWebsite) {
if ($xmlSaProperties.StorageServiceProperties.StaticWebsite.Enabled -eq $true) {
$staticWebsitesState = $true
@@ -92,7 +100,15 @@ function processStorageAccountAnalysis {
}
if ($listContainersSuccess -eq $true) {
- $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 3))
+ # ? https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/issues/218#issuecomment-1854516882
+ if ($listContainers.gettype().Name -eq 'Byte[]') {
+ $byteArray = [byte[]]$listContainers
+ $listContainers = [System.Text.Encoding]::UTF8.GetString($byteArray)
+ }
+
+ # $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
+ # $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
+ $xmlListContainers = [xml]($listContainers -replace '^.*?<', '<') # Universal fix for all PS versions
$containersCount = $xmlListContainers.EnumerationResults.Containers.Container.Count
foreach ($container in $xmlListContainers.EnumerationResults.Containers.Container) {
From 9b1588613e05b2dbebb43cfd4868b222d3581156 Mon Sep 17 00:00:00 2001
From: Kai Schulz
Date: Fri, 15 Dec 2023 16:23:18 +0100
Subject: [PATCH 5/5] AzAPICallVersion = '1.1.85'
---
pwsh/AzGovVizParallel.ps1 | 2 +-
pwsh/dev/devAzGovVizParallel.ps1 | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/pwsh/AzGovVizParallel.ps1 b/pwsh/AzGovVizParallel.ps1
index 97df6c26..6b01c6bc 100644
--- a/pwsh/AzGovVizParallel.ps1
+++ b/pwsh/AzGovVizParallel.ps1
@@ -372,7 +372,7 @@ Param
# <--- AzAPICall related parameters #consult the AzAPICall GitHub repository for details aka.ms/AzAPICall
[string]
- $AzAPICallVersion = '1.1.84',
+ $AzAPICallVersion = '1.1.85',
[switch]
$DebugAzAPICall,
diff --git a/pwsh/dev/devAzGovVizParallel.ps1 b/pwsh/dev/devAzGovVizParallel.ps1
index 2387c3b7..cfbc48c4 100644
--- a/pwsh/dev/devAzGovVizParallel.ps1
+++ b/pwsh/dev/devAzGovVizParallel.ps1
@@ -372,7 +372,7 @@ Param
# <--- AzAPICall related parameters #consult the AzAPICall GitHub repository for details aka.ms/AzAPICall
[string]
- $AzAPICallVersion = '1.1.84',
+ $AzAPICallVersion = '1.1.85',
[switch]
$DebugAzAPICall,