From a5da5719cd0b88725d03d8e0c6e977e8e0bbf8c1 Mon Sep 17 00:00:00 2001 From: Julian Hayward Date: Tue, 26 Apr 2022 20:09:48 +0200 Subject: [PATCH] v6_major_20220425_1 --- .azuredevops/pipelines/AzGovViz.pipeline.yml | 2 +- .azuredevops/pipelines/AzGovViz.variables.yml | 8 +- .devcontainer/devcontainer.json | 26 +- .github/workflows/AzGovViz.yml | 24 +- .github/workflows/AzGovViz_OIDC.yml | 29 +- .pipelines/AzGovViz.yml | 6 +- .pipelines/README.md | 3 + README.md | 93 +- history.md | 12 + pwsh/AzGovVizParallel.ps1 | 41704 ++++++++-------- pwsh/dev/README.md | 1 + pwsh/dev/buildAzGovVizParallel.ps1 | 19 + pwsh/dev/devAzGovVizParallel.ps1 | 1883 + pwsh/dev/functions/addHtParameters.ps1 | 38 + pwsh/dev/functions/addIndexNumberToArray.ps1 | 9 + pwsh/dev/functions/addRowToTable.ps1 | 195 + pwsh/dev/functions/buildJSON.ps1 | 435 + pwsh/dev/functions/buildMD.ps1 | 170 + pwsh/dev/functions/buildPolicyAllJSON.ps1 | 53 + pwsh/dev/functions/buildTree.ps1 | 276 + pwsh/dev/functions/cacheBuiltIn.ps1 | 211 + pwsh/dev/functions/createTagList.ps1 | 25 + .../dataCollectionFunctions.ps1 | 2864 ++ pwsh/dev/functions/detailSubscriptions.ps1 | 70 + pwsh/dev/functions/exportBaseCSV.ps1 | 16 + pwsh/dev/functions/getConsumption.ps1 | 616 + .../functions/getDefaultManagementGroup.ps1 | 20 + pwsh/dev/functions/getEntities.ps1 | 89 + pwsh/dev/functions/getFileNaming.ps1 | 24 + pwsh/dev/functions/getGroupmembers.ps1 | 141 + pwsh/dev/functions/getMDfCSecureScoreMG.ps1 | 55 + .../getResourceDiagnosticsCapability.ps1 | 133 + pwsh/dev/functions/getSubscriptions.ps1 | 17 + pwsh/dev/functions/getTenantDetails.ps1 | 25 + pwsh/dev/functions/handleCloudEnvironment.ps1 | 9 + pwsh/dev/functions/html/htmlFunctions.ps1 | 350 + pwsh/dev/functions/namingValidation.ps1 | 16 + pwsh/dev/functions/prepareData.ps1 | 32 + pwsh/dev/functions/processAADGroups.ps1 | 105 + pwsh/dev/functions/processApplications.ps1 | 133 + pwsh/dev/functions/processDataCollection.ps1 | 1044 + .../functions/processDefinitionInsights.ps1 | 996 + pwsh/dev/functions/processDiagramMermaid.ps1 | 90 + .../dev/functions/processHierarchyMapOnly.ps1 | 24 + .../functions/processManagedIdentities.ps1 | 26 + .../functions/processScopeInsightsMgOrSub.ps1 | 3155 ++ pwsh/dev/functions/processTenantSummary.ps1 | 11926 +++++ .../functions/removeInvalidFileNameChars.ps1 | 19 + pwsh/dev/functions/resolveObjectIds.ps1 | 151 + pwsh/dev/functions/runInfo.ps1 | 330 + pwsh/dev/functions/selectMg.ps1 | 21 + pwsh/dev/functions/setBaseVariablesMG.ps1 | 15 + pwsh/dev/functions/setOutput.ps1 | 28 + pwsh/dev/functions/setTranscript.ps1 | 52 + pwsh/dev/functions/showMemoryUsage.ps1 | 40 + pwsh/dev/functions/stats.ps1 | 165 + pwsh/dev/functions/testPowerShellVersion.ps1 | 49 + pwsh/dev/functions/validateAccess.ps1 | 146 + pwsh/prerequisites.ps1 | 181 +- 59 files changed, 47156 insertions(+), 21239 deletions(-) create mode 100644 .pipelines/README.md create mode 100644 pwsh/dev/README.md create mode 100644 pwsh/dev/buildAzGovVizParallel.ps1 create mode 100644 pwsh/dev/devAzGovVizParallel.ps1 create mode 100644 pwsh/dev/functions/addHtParameters.ps1 create mode 100644 pwsh/dev/functions/addIndexNumberToArray.ps1 create mode 100644 pwsh/dev/functions/addRowToTable.ps1 create mode 100644 pwsh/dev/functions/buildJSON.ps1 create mode 100644 pwsh/dev/functions/buildMD.ps1 create mode 100644 pwsh/dev/functions/buildPolicyAllJSON.ps1 create mode 100644 pwsh/dev/functions/buildTree.ps1 create mode 100644 pwsh/dev/functions/cacheBuiltIn.ps1 create mode 100644 pwsh/dev/functions/createTagList.ps1 create mode 100644 pwsh/dev/functions/dataCollection/dataCollectionFunctions.ps1 create mode 100644 pwsh/dev/functions/detailSubscriptions.ps1 create mode 100644 pwsh/dev/functions/exportBaseCSV.ps1 create mode 100644 pwsh/dev/functions/getConsumption.ps1 create mode 100644 pwsh/dev/functions/getDefaultManagementGroup.ps1 create mode 100644 pwsh/dev/functions/getEntities.ps1 create mode 100644 pwsh/dev/functions/getFileNaming.ps1 create mode 100644 pwsh/dev/functions/getGroupmembers.ps1 create mode 100644 pwsh/dev/functions/getMDfCSecureScoreMG.ps1 create mode 100644 pwsh/dev/functions/getResourceDiagnosticsCapability.ps1 create mode 100644 pwsh/dev/functions/getSubscriptions.ps1 create mode 100644 pwsh/dev/functions/getTenantDetails.ps1 create mode 100644 pwsh/dev/functions/handleCloudEnvironment.ps1 create mode 100644 pwsh/dev/functions/html/htmlFunctions.ps1 create mode 100644 pwsh/dev/functions/namingValidation.ps1 create mode 100644 pwsh/dev/functions/prepareData.ps1 create mode 100644 pwsh/dev/functions/processAADGroups.ps1 create mode 100644 pwsh/dev/functions/processApplications.ps1 create mode 100644 pwsh/dev/functions/processDataCollection.ps1 create mode 100644 pwsh/dev/functions/processDefinitionInsights.ps1 create mode 100644 pwsh/dev/functions/processDiagramMermaid.ps1 create mode 100644 pwsh/dev/functions/processHierarchyMapOnly.ps1 create mode 100644 pwsh/dev/functions/processManagedIdentities.ps1 create mode 100644 pwsh/dev/functions/processScopeInsightsMgOrSub.ps1 create mode 100644 pwsh/dev/functions/processTenantSummary.ps1 create mode 100644 pwsh/dev/functions/removeInvalidFileNameChars.ps1 create mode 100644 pwsh/dev/functions/resolveObjectIds.ps1 create mode 100644 pwsh/dev/functions/runInfo.ps1 create mode 100644 pwsh/dev/functions/selectMg.ps1 create mode 100644 pwsh/dev/functions/setBaseVariablesMG.ps1 create mode 100644 pwsh/dev/functions/setOutput.ps1 create mode 100644 pwsh/dev/functions/setTranscript.ps1 create mode 100644 pwsh/dev/functions/showMemoryUsage.ps1 create mode 100644 pwsh/dev/functions/stats.ps1 create mode 100644 pwsh/dev/functions/testPowerShellVersion.ps1 create mode 100644 pwsh/dev/functions/validateAccess.ps1 diff --git a/.azuredevops/pipelines/AzGovViz.pipeline.yml b/.azuredevops/pipelines/AzGovViz.pipeline.yml index 09a86ff5..1576371c 100644 --- a/.azuredevops/pipelines/AzGovViz.pipeline.yml +++ b/.azuredevops/pipelines/AzGovViz.pipeline.yml @@ -35,7 +35,7 @@ variables: # trigger: # branches: # include: -# - master #CHECK: branch 'master' is applicable? - delete me :) +# - master #CHECK branch 'master' is applicable? - delete me :) jobs: - job: AzGovViz diff --git a/.azuredevops/pipelines/AzGovViz.variables.yml b/.azuredevops/pipelines/AzGovViz.variables.yml index d07a5e40..8fb0ed53 100644 --- a/.azuredevops/pipelines/AzGovViz.variables.yml +++ b/.azuredevops/pipelines/AzGovViz.variables.yml @@ -221,10 +221,10 @@ variables: # Integer | Default = 5 | example: value: 10 value: - # Will show memory usage //pending release - #- name: ShowMemoryUsage - # # Switch | example: value: true - # value: + # Will show memory usage + - name: ShowMemoryUsage + # Switch | example: value: true + value: # Dynamic Variables - Do Not Modify Anything Below this line! - name: ExcludedResourceTypesDiagnosticsCapable diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d0efdf22..76728eab 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,13 +1,15 @@ { - "name": "AzGovViz", - "dockerFile": "Dockerfile", - "settings": { - "terminal.integrated.defaultProfile.linux": "pwsh" - }, - "extensions": [ - "ms-vscode.powershell", - "analytic-signal.preview-html", - "bierner.markdown-mermaid" - ], - "forwardPorts": [] -} \ No newline at end of file + "name": "AzGovViz", + "dockerFile": "Dockerfile", + "settings": { + "terminal.integrated.defaultProfile.linux": "pwsh" + }, + "extensions": [ + "ms-vscode.powershell", + "analytic-signal.preview-html", + "bierner.markdown-mermaid", + "streetsidesoftware.code-spell-checker", + "yzhang.markdown-all-in-one" + ], + "forwardPorts": [] +} diff --git a/.github/workflows/AzGovViz.yml b/.github/workflows/AzGovViz.yml index 378c633c..6d5accfe 100644 --- a/.github/workflows/AzGovViz.yml +++ b/.github/workflows/AzGovViz.yml @@ -1,13 +1,16 @@ -# AzGovViz v6_major_20220116_2 +# AzGovViz v6_major_20220319_1 name: AzGovViz env: - outputpath: wiki - ManagementGroupId: + OutputPath: wiki + ManagementGroupId: + ScriptDir: pwsh #example: 'my folder\pwsh' or 'my folder/pwsh' + ScriptPrereqFile: prerequisites.ps1 + ScriptFile: AzGovVizParallel.ps1 on: #schedule: - # - cron: '45 4,16 * * *' + # - cron: '30 4 * * *' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -17,6 +20,7 @@ jobs: runs-on: ubuntu-latest steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout uses: actions/checkout@v2 @@ -32,19 +36,13 @@ jobs: # "clientId": "", # "clientSecret": "" # } - - - name: Check prerequisites - uses: azure/powershell@v1 - with: - inlineScript: | - .\pwsh\prerequisites.ps1 - azPSVersion: "latest" - name: Run AzGovViz uses: azure/powershell@v1 with: inlineScript: | - .\pwsh\AzGovVizParallel.ps1 -ManagementGroupId ${env:ManagementGroupId} -outputpath ${env:outputpath} + . .\$($env:ScriptDir)\$($env:ScriptPrereqFile) + . .\$($env:ScriptDir)\$($env:ScriptFile) -ManagementGroupId ${env:ManagementGroupId} -ScriptPath ${env:ScriptDir} -OutputPath ${env:OutputPath} azPSVersion: "latest" - name: Push AzGovViz output to repository @@ -54,4 +52,4 @@ jobs: git config pull.rebase false git add --all git commit -m "$GITHUB_WORKFLOW $GITHUB_JOB" - git push \ No newline at end of file + git push diff --git a/.github/workflows/AzGovViz_OIDC.yml b/.github/workflows/AzGovViz_OIDC.yml index 7854af85..82316067 100644 --- a/.github/workflows/AzGovViz_OIDC.yml +++ b/.github/workflows/AzGovViz_OIDC.yml @@ -1,13 +1,16 @@ -# AzGovViz v6_major_20220116_2 +# AzGovViz v6_major_20220319_1 name: AzGovViz_OIDC env: - outputpath: wiki - ManagementGroupId: + OutputPath: wiki + ManagementGroupId: + ScriptDir: pwsh #example: 'my folder\pwsh' or 'my folder/pwsh' + ScriptPrereqFile: prerequisites.ps1 + ScriptFile: AzGovVizParallel.ps1 on: #schedule: - # - cron: '45 5,17 * * *' + # - cron: '30 5 * * *' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -28,23 +31,17 @@ jobs: - name: Connect Azure OIDC uses: azure/login@v1 with: - client-id: ${{secrets.CLIENT_ID}} - tenant-id: ${{secrets.TENANT_ID}} - subscription-id: ${{secrets.SUBSCRIPTION_ID}} + client-id: ${{secrets.CLIENT_ID}} #create this secret + tenant-id: ${{secrets.TENANT_ID}} #create this secret + subscription-id: ${{secrets.SUBSCRIPTION_ID}} #create this secret enable-AzPSSession: true - - name: Check prerequisites - uses: azure/powershell@v1 - with: - inlineScript: | - .\pwsh\prerequisites.ps1 - azPSVersion: "latest" - - name: Run AzGovViz uses: azure/powershell@v1 with: inlineScript: | - .\pwsh\AzGovVizParallel.ps1 -ManagementGroupId ${env:ManagementGroupId} -SubscriptionId4AzContext ${{secrets.SUBSCRIPTION_ID}} -outputpath ${env:outputpath} + . .\$($env:ScriptDir)\$($env:ScriptPrereqFile) + . .\$($env:ScriptDir)\$($env:ScriptFile) -ManagementGroupId ${env:ManagementGroupId} -SubscriptionId4AzContext ${{secrets.SUBSCRIPTION_ID}} -ScriptPath ${env:ScriptDir} -OutputPath ${env:OutputPath} azPSVersion: "latest" - name: Push AzGovViz output to repository @@ -54,4 +51,4 @@ jobs: git config pull.rebase false git add --all git commit -m "$GITHUB_WORKFLOW $GITHUB_JOB" - git push \ No newline at end of file + git push diff --git a/.pipelines/AzGovViz.yml b/.pipelines/AzGovViz.yml index f7a95042..d62e7958 100644 --- a/.pipelines/AzGovViz.yml +++ b/.pipelines/AzGovViz.yml @@ -1,4 +1,4 @@ -# AzGovViz v6_major_20220114_1 +# AzGovViz v6_major_20220319_1 # First things first: # 1. edit line 61 and line 62 # 2. check line 76 and 87 if branch 'master' is applicable @@ -73,7 +73,7 @@ schedules: always: true branches: include: - - master #CHECK: branch 'master' is applicable? - delete me :) + - master #CHECK branch 'master' is applicable? - delete me :) #Running AzOps? Run AzGovViz after 'AzOps - Push' .. #AzOps Accellerator https://github.com/Azure/AzOps-Accelerator @@ -107,7 +107,7 @@ jobs: scriptType: filePath pwsh: true scriptPath: '$(System.DefaultWorkingDirectory)/$(ScriptDir)/$(Script)' - scriptArguments: '-ManagementGroupId $(ManagementGroupId) -OutputPath $(WikiDir) -CsvDelimiter "$(CsvDelimiter)" -SubscriptionQuotaIdWhitelist $(SubscriptionQuotaIdWhitelist) -ExludedResourceTypesDiagnosticsCapable $(ExludedResourceTypesDiagnosticsCapable)' + scriptArguments: '-ManagementGroupId $(ManagementGroupId) -ScriptPath $(ScriptDir) -OutputPath $(WikiDir) -CsvDelimiter "$(CsvDelimiter)" -SubscriptionQuotaIdWhitelist $(SubscriptionQuotaIdWhitelist) -ExludedResourceTypesDiagnosticsCapable $(ExludedResourceTypesDiagnosticsCapable)' azurePowerShellVersion: latestVersion displayName: 'Run AzGovViz v6' - powershell: | diff --git a/.pipelines/README.md b/.pipelines/README.md new file mode 100644 index 00000000..8b01c80a --- /dev/null +++ b/.pipelines/README.md @@ -0,0 +1,3 @@ +# [Deprecated] `AzGovViz.yml` + +This version of the pipeline will be deprecated. Check the new [pipeline YAML](../.azuredevops/pipelines) approach (`AzGovViz.pipeline.yml` & `AzGovViz.variables.yml`)! \ No newline at end of file diff --git a/README.md b/README.md index b7d946ed..869a0995 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Listed as [security monitoring tool](https://docs.microsoft.com/en-us/azure/arch * [Required permissions in Azure Active Directory](#required-permissions-in-azure-active-directory) * [PowerShell](#powershell) * [Parameters](#parameters) + * [API](#api) * [Integrate with AzOps](#integrate-with-azops) * [Stats](#stats) * [Security](#security) @@ -57,24 +58,22 @@ Listed as [security monitoring tool](https://docs.microsoft.com/en-us/azure/arch ## Release history -__Changes__ (2022-Jan-31 / Major) - -* New __TenantSummary | RBAC__ feature - insights on all Role definitions that are capable to write Role assignments -* __TenantSummary | Subscriptions, Resources & Defender | Subscriptions__ report (new) [Role assignment limits](https://docs.microsoft.com/en-us/azure/role-based-access-control/troubleshooting#azure-role-assignments-limit) -* Handling orphaned Policy assignments (scope Management Group) -* Datacollection for Management Groups process in batches (batch per Management Group level) -* Update Dockerfile -* Update API version for Resources, ResourceGroups and Subscriptions -* Further enrich _PolicyDefinitions and _PolicySetDefinitions CSV outputs -* HTML file performance optimization -* Include instructions for GitHub Actions in the __[Setup Guide](setup.md)__ -* New [demo](https://www.azadvertizer.net/azgovvizv4/demo/AzGovViz_demo.html) uploaded +__Changes__ (2022-Apr-25 / Major) + +* New JSON output *_PolicyAll.json - Contains all relations of Policy/Set definitions and Policy assignments +* New parameter `-ShowMemoryUsage` - Shows memory usage at memory intense sections of the scripts, this shall help you determine if the the worker is well sized for AzGovViz +* Leveraging AzAPICall PowerShell module. The AzAPICall function has been removed from the AzGovViz code base and has been published as a module to the [PoweShell Gallery](https://www.powershellgallery.com/packages/AzAPICall) ([GitHub](https://aka.ms/AzAPICall)) +* Foreach -parallel import the AzAPICall module instead of $using: +* Optimize GitHub Actions workflows (YAML) +* Added list of [APIs](#api) that are polled by AzGovViz +* Microsoft Graph `v1.0/directoryObjects/getByIds` do batching is exceeds 1000 identities +* Performance optimization * Bugfixes -Passed tests: Powershell Core 7.2.1 on Windows -Passed tests: Powershell Core 7.2.1 Azure DevOps hosted agent ubuntu-18.04 -Passed tests: Powershell Core 7.2.1 Github Actions hosted agent ubuntu-latest -Passed tests: Powershell Core 7.2.1 GitHub Codespaces mcr.microsoft.com/powershell:latest +Passed tests: Powershell Core 7.2.2 on Windows +Passed tests: Powershell Core 7.2.2 Azure DevOps hosted agent ubuntu-18.04 +Passed tests: Powershell Core 7.2.2 Github Actions hosted agent ubuntu-latest +Passed tests: Powershell Core 7.2.2 GitHub Codespaces mcr.microsoft.com/powershell:latest Passed tests: AzureCloud, AzureUSGovernment, AzureChinaCloud [Release history](history.md) @@ -401,6 +400,11 @@ Screenshot Azure Portal * ~~Az.Resources~~ * ~~Az.ResourceGraph~~ * [Install the Azure Az PowerShell module](https://docs.microsoft.com/en-us/powershell/azure/install-az-ps) +* Requires PowerShell Module 'AzAPICall'. +Running in Azure DevOps or GitHub Actions the AzAPICall PowerShell module will be installed automatically. +AzAPICall resources: + * [![PowerShell Gallery Version (including pre-releases)](https://img.shields.io/powershellgallery/v/AzAPICall?include_prereleases&label=PowerShell%20Gallery)](https://www.powershellgallery.com/packages/AzAPICall) + * [GitHub Repository](https://aka.ms/AzAPICall) * Usage/command * `.\AzGovVizParallel.ps1 -ManagementGroupId ` @@ -446,6 +450,63 @@ Screenshot Azure Portal * `-StatsOptOut` - Opt out sending [stats](#stats) * `-NoSingleSubscriptionOutput` - Single __Scope Insights__ output per Subscription should not be created * `-ManagementGroupsOnly` - Collect data only for Management Groups (Subscription data such as e.g. Policy assignments etc. will not be collected) + * `-ShowMemoryUsage` - Shows memory usage at memory intense sections of the scripts, this shall help you determine if the the worker is well sized for AzGovViz + +### API + +AzGovViz polls the following APIs + +| Endpoint | API version | API name | +| --- | --- | --- | +| MS Graph | beta | /groups/`aadGroupId`/transitiveMembers | +| MS Graph | v1.0 | /applications | +| MS Graph | v1.0 | /directoryObjects/getByIds | +| MS Graph | v1.0 | /users | +| MS Graph | v1.0 | /groups | +| MS Graph | v1.0 | /servicePrincipals | +| ARM |2021-05-01-preview | /`resourceId`/providers/Microsoft.Insights/diagnosticSettingsCategories | +| ARM |2018-11-01-preview | /`scopeId`/providers/Microsoft.Blueprint/blueprints/`blueprintName` | +| ARM |2021-06-01 | /providers/Microsoft.Authorization/policyDefinitions | +| ARM |2021-06-01 | /providers/Microsoft.Authorization/policySetDefinitions | +| ARM |2018-07-01 | /providers/Microsoft.Authorization/roleDefinitions | +| ARM |2020-02-01 | /providers/Microsoft.Management/getEntities | +| ARM |2021-06-01 | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.Authorization/policyAssignments | +| ARM |2021-06-01 | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.Authorization/policyDefinitions | +| ARM |2020-07-01-preview | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.Authorization/policyExemptions | +| ARM |2021-06-01 | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.Authorization/policySetDefinitions | +| ARM |2015-07-01 | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.Authorization/roleAssignments | +| ARM |2020-10-01-preview | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.Authorization/roleAssignmentSchedules | +| ARM |2015-07-01 | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.Authorization/roleDefinitions | +| ARM |2018-11-01-preview | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.Blueprint/blueprints | +| ARM |2019-11-01 | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.CostManagement/query | +| ARM |2020-01-01-preview | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/microsoft.insights/diagnosticSettings | +| ARM |2019-10-01 | /providers/Microsoft.Management/managementGroups/`managementGroupId`/providers/Microsoft.PolicyInsights/policyStates/latest/summarize | +| ARM |2020-05-01 | /providers/Microsoft.Management/managementGroups/`managementGroupId` | +| ARM |2020-02-01 | /providers/Microsoft.Management/managementGroups/`tenantId`/settings | +| ARM |2020-05-01 | /providers/Microsoft.Management/managementGroups | +| ARM |2021-03-01 | /providers/Microsoft.ResourceGraph/resources | +| ARM |2016-09-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/locks | +| ARM |2021-06-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/policyAssignments | +| ARM |2021-06-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/policyDefinitions | +| ARM |2020-07-01-preview | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/policyExemptions | +| ARM |2021-06-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/policySetDefinitions | +| ARM |2015-07-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/roleAssignments | +| ARM |2020-10-01-preview | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/roleAssignmentSchedules | +| ARM |2019-08-01-preview | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/roleAssignmentsUsageMetrics | +| ARM |2015-07-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Authorization/roleDefinitions | +| ARM |2018-11-01-preview | /subscriptions/`subscriptionId`/providers/Microsoft.Blueprint/blueprintAssignments | +| ARM |2018-11-01-preview | /subscriptions/`subscriptionId`/providers/Microsoft.Blueprint/blueprints | +| ARM |2019-11-01 | /subscriptions/`subscriptionId`/providers/Microsoft.CostManagement/query | +| ARM |2021-05-01-preview | /subscriptions/`subscriptionId`/providers/Microsoft.Insights/diagnosticSettings | +| ARM |2019-10-01 | /subscriptions/`subscriptionId`/providers/Microsoft.PolicyInsights/policyStates/latest/summarize | +| ARM |2020-06-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Resources/tags/default | +| ARM |2018-06-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Security/pricings | +| ARM |2020-01-01 | /subscriptions/`subscriptionId`/providers/Microsoft.Security/securescores | +| ARM |2019-10-01 | /subscriptions/`subscriptionId`/providers | +| ARM |2021-04-01 | /subscriptions/`subscriptionId`/resourcegroups | +| ARM |2021-04-01 | /subscriptions/`subscriptionId`/resources | +| ARM |2020-01-01 | /subscriptions | +| ARM |2020-01-01 | /tenants | ## Integrate with AzOps diff --git a/history.md b/history.md index ad47b7f5..d86a8a02 100644 --- a/history.md +++ b/history.md @@ -4,6 +4,18 @@ ### AzGovViz version 6 +__Changes__ (2022-Apr-25 / Major) + +* New JSON output *_PolicyAll.json - Contains all relations of Policy/Set definitions and Policy assignments +* New parameter `-ShowMemoryUsage` - Shows memory usage at memory intense sections of the scripts, this shall help you determine if the the worker is well sized for AzGovViz +* Leveraging AzAPICall PowerShell module. The AzAPICall function has been removed from the AzGovViz code base and has been published as a module to the [PoweShell Gallery](https://www.powershellgallery.com/packages/AzAPICall) ([GitHub](https://aka.ms/AzAPICall)) +* Foreach -parallel import the AzAPICall module instead of $using: +* Optimize GitHub Actions workflows (YAML) +* Added list of [APIs](#api) that are polled by AzGovViz +* Microsoft Graph `v1.0/directoryObjects/getByIds` do batching is exceeds 1000 identities +* Performance optimization +* Bugfixes + __Changes__ (2022-Jan-31 / Major) * New __TenantSummary | RBAC__ feature - insights on all Role definitions that are capable to write Role assignments diff --git a/pwsh/AzGovVizParallel.ps1 b/pwsh/AzGovVizParallel.ps1 index 338af232..50d00fa4 100644 --- a/pwsh/AzGovVizParallel.ps1 +++ b/pwsh/AzGovVizParallel.ps1 @@ -67,8 +67,8 @@ .PARAMETER DoTranscript Log the console output -.PARAMETER AzureDevOpsWikiHierarchyDirection - Define the direction the Hierarchy should be built in Azure DevOps TD (default) = TopDown (Horizontal), LR = LeftRight (Vertical) +.PARAMETER MermaidDirection + Define the direction the Mermaid based HierarchyMap should be built TD (default) = TopDown (Horizontal), LR = LeftRight (Vertical) .PARAMETER SubscriptionId4AzContext Define the Subscription Id to use for AzContext (default is to use a random Subscription Id) @@ -189,8 +189,8 @@ Define if you want to log the console output PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -DoTranscript - Define the direction the Hierarchy should be built in Azure DevOps WokiAsCode (Markdown) TD = TopDown (Horizontal), LR = LeftRight (Vertical) - PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -AzureDevOpsWikiHierarchyDirection "LR" + Define the direction the Mermaid based HierarchyMap should be built in Markdown TD = TopDown (Horizontal), LR = LeftRight (Vertical) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -MermaidDirection "LR" Define the Subscription Id to use for AzContext (default is to use a random Subscription Id) PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -SubscriptionId4AzContext "" @@ -273,1514 +273,303 @@ [CmdletBinding()] Param ( - [string]$Product = "AzGovViz", - [string]$ProductVersion = "v6_minor_20220212_1", - [string]$GithubRepository = "aka.ms/AzGovViz", - [string]$ManagementGroupId, - [switch]$AzureDevOpsWikiAsCode, #deprecated - Based on environment variables the script will detect the code run platform - [switch]$DebugAzAPICall, - [switch]$NoCsvExport, - [string]$CsvDelimiter = ";", - [switch]$CsvExportUseQuotesAsNeeded, - [string]$OutputPath, - [switch]$DoNotShowRoleAssignmentsUserData, - [switch]$HierarchyMapOnly, - [switch]$NoASCSecureScore, - [switch]$NoMDfCSecureScore, - [switch]$NoResourceProvidersDetailed, - [int]$LimitCriticalPercentage = 80, - [array]$SubscriptionQuotaIdWhitelist = @("undefined"), - [switch]$NoPolicyComplianceStates, - [switch]$NoResourceDiagnosticsPolicyLifecycle, - [switch]$NoAADGroupsResolveMembers, - [int]$AADServicePrincipalExpiryWarningDays = 14, - [switch]$NoAzureConsumption, #obsolete - [switch]$DoAzureConsumption, - [int]$AzureConsumptionPeriod = 1, - [switch]$NoAzureConsumptionReportExportToCSV, - [switch]$DoTranscript, - [int]$HtmlTableRowsLimit = 20000, #HTML TenantSummary may become unresponsive depending on client device performance. A recommendation will be shown to use the CSV file instead of opening the TF table - [int]$ThrottleLimit = 10, - [array]$ExludedResourceTypesDiagnosticsCapable = @("microsoft.web/certificates"), - [switch]$DoNotIncludeResourceGroupsOnPolicy, - [switch]$DoNotIncludeResourceGroupsAndResourcesOnRBAC, - [parameter(ValueFromPipeline)][ValidateSet("TD", "LR")][string]$AzureDevOpsWikiHierarchyDirection = "TD", - [string]$SubscriptionId4AzContext = "undefined", - [int]$ChangeTrackingDays = 14, - [string]$FileTimeStampFormat = "yyyyMMdd_HHmmss", - [switch]$NoJsonExport, - [switch]$JsonExportExcludeResourceGroups, - [switch]$JsonExportExcludeResources, - [switch]$LargeTenant, - [switch]$NoScopeInsights, - [int]$AADGroupMembersLimit = 500, - [switch]$PolicyAtScopeOnly, - [switch]$RBACAtScopeOnly, - [switch]$NoResources, - [switch]$StatsOptOut, - [switch]$NoSingleSubscriptionOutput, - [switch]$ManagementGroupsOnly, + [string] + $Product = 'AzGovViz', - #https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#role-based-access-control-limits - [int]$LimitRBACCustomRoleDefinitionsTenant = 5000, - [int]$LimitRBACRoleAssignmentsManagementGroup = 500, - #[int]$LimitRBACRoleAssignmentsSubscription = 2000, #will be retrieved programatically + [string] + $AzAPICallVersion = '1.1.11', - #https://docs.microsoft.com/en-us/azure/governance/policy/overview#maximum-count-of-azure-policy-objects - [int]$LimitPOLICYPolicyAssignmentsManagementGroup = 200, - [int]$LimitPOLICYPolicyAssignmentsSubscription = 200, - [int]$LimitPOLICYPolicyDefinitionsScopedManagementGroup = 500, - [int]$LimitPOLICYPolicyDefinitionsScopedSubscription = 500, - [int]$LimitPOLICYPolicySetAssignmentsManagementGroup = 200, - [int]$LimitPOLICYPolicySetAssignmentsSubscription = 200, - [int]$LimitPOLICYPolicySetDefinitionsScopedTenant = 2500, - [int]$LimitPOLICYPolicySetDefinitionsScopedManagementGroup = 200, - [int]$LimitPOLICYPolicySetDefinitionsScopedSubscription = 200, + [string] + $ProductVersion = 'v6_major_20220425_1', - #https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#subscription-limits - [int]$LimitResourceGroups = 980, - [int]$LimitTagsSubscription = 50 -) + [string] + $GithubRepository = 'aka.ms/AzGovViz', -$Error.clear() -$ErrorActionPreference = "Stop" -#removeNoise -$ProgressPreference = 'SilentlyContinue' -Set-Item Env:\SuppressAzurePowerShellBreakingChangeWarnings "true" + [string] + $ScriptPath = 'pwsh', #e.g. 'myfolder\pwsh' -#region CheckCodeRunPlatform -if ($env:GITHUB_SERVER_URL -and $env:CODESPACES) { - $checkCodeRunPlatform = "GitHubCodespaces" -} -elseif ($env:REMOTE_CONTAINERS) { - $checkCodeRunPlatform = "RemoteContainers" -} -elseif ($env:SYSTEM_TEAMPROJECTID -and $env:BUILD_REPOSITORY_ID) { - $checkCodeRunPlatform = "AzureDevOps" - $onAzureDevOps = $true - $onAzureDevOpsOrGitHubActions = $true -} -elseif ($PSPrivateMetadata) { - $checkCodeRunPlatform = "AzureAutomation" -} -elseif ($env:GITHUB_ACTIONS) { - $checkCodeRunPlatform = "GitHubActions" - $onAzureDevOpsOrGitHubActions = $true -} -elseif ($env:ACC_IDLE_TIME_LIMIT -and $env:AZURE_HTTP_USER_AGENT -and $env:AZUREPS_HOST_ENVIRONMENT) { - $checkCodeRunPlatform = "CloudShell" -} -else { - $checkCodeRunPlatform = "Console" -} -#endregion CheckCodeRunPlatform + [string] + $ManagementGroupId, -#region file -if (-not [IO.Path]::IsPathRooted($outputPath)) { - $outputPath = Join-Path -Path (Get-Location).Path -ChildPath $outputPath -} -$outputPath = Join-Path -Path $outputPath -ChildPath '.' -$outputPath = [IO.Path]::GetFullPath($outputPath) -if (-not (test-path $outputPath)) { - Write-Host "path $outputPath does not exist - please create it!" -ForegroundColor Red - Throw "Error - AzGovViz: check the last console output for details" -} -else { - Write-Host "Output/Files will be created in path '$outputPath'" -} -$DirectorySeparatorChar = [IO.Path]::DirectorySeparatorChar + [switch] + $AzureDevOpsWikiAsCode, #deprecated - Based on environment variables the script will detect the code run platform -#fileTimestamp -try { - $fileTimestamp = (Get-Date -Format $FileTimeStampFormat) -} -catch { - Write-Host "fileTimestamp format: '$($FileTimeStampFormat)' invalid; continue with default format: 'yyyyMMdd_HHmmss'" -ForegroundColor Red - $FileTimeStampFormat = "yyyyMMdd_HHmmss" - $fileTimestamp = (Get-Date -Format $FileTimeStampFormat) -} + [switch] + $DebugAzAPICall, -#region DoTranscript -if ($DoTranscript) { - if ($ManagementGroupId) { - if ($onAzureDevOpsOrGitHubActions -eq $true) { - if ($HierarchyMapOnly -eq $true) { - $fileNameTranscript = "AzGovViz_HierarchyMapOnly_$($ManagementGroupId)_Log.txt" - } - elseif ($ManagementGroupsOnly -eq $true) { - $fileNameTranscript = "AzGovViz_ManagementGroupsOnly_$($ManagementGroupId)_Log.txt" - } - else { - $fileNameTranscript = "AzGovViz_$($ManagementGroupId)_Log.txt" - } - } - else { - if ($HierarchyMapOnly -eq $true) { - $fileNameTranscript = "AzGovViz_HierarchyMapOnly_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)_Log.txt" - } - elseif ($ManagementGroupsOnly -eq $true) { - $fileNameTranscript = "AzGovViz_ManagementGroupsOnly_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)_Log.txt" - } - else { - $fileNameTranscript = "AzGovViz_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupId)_Log.txt" - } - } - } - else { - if ($onAzureDevOpsOrGitHubActions -eq $true) { - if ($HierarchyMapOnly -eq $true) { - $fileNameTranscript = "AzGovViz_HierarchyMapOnly_Log.txt" - } - elseif ($ManagementGroupsOnly -eq $true) { - $fileNameTranscript = "AzGovViz_ManagementGroupsOnly_Log.txt" - } - else { - $fileNameTranscript = "AzGovViz_Log.txt" - } - } - else { - if ($HierarchyMapOnly -eq $true) { - $fileNameTranscript = "AzGovViz_HierarchyMapOnly_$($ProductVersion)_$($fileTimestamp)_Log.txt" - } - elseif ($ManagementGroupsOnly -eq $true) { - $fileNameTranscript = "AzGovViz_ManagementGroupsOnly_$($ProductVersion)_$($fileTimestamp)_Log.txt" - } - else { - $fileNameTranscript = "AzGovViz_$($ProductVersion)_$($fileTimestamp)_Log.txt" - } - } - } - Write-Host "Writing transcript: $($outputPath)$($DirectorySeparatorChar)$($fileNameTranscript)" - Start-Transcript -Path "$($outputPath)$($DirectorySeparatorChar)$($fileNameTranscript)" -} -#endregion DoTranscript + [switch] + $NoCsvExport, -#endregion file + [string] + [parameter(ValueFromPipeline)][ValidateSet(';', ',')][string]$CsvDelimiter = ';', -#time -$executionDateTimeInternationalReadable = Get-Date -Format "dd-MMM-yyyy HH:mm:ss" -$currentTimeZone = (Get-TimeZone).Id + [switch] + $CsvExportUseQuotesAsNeeded, -#start -$startAzGovViz = Get-Date -$startTime = Get-Date -Format "dd-MMM-yyyy HH:mm:ss" -$startTimeUTC = ((Get-Date).ToUniversalTime()).ToString("dd-MMM-yyyy HH:mm:ss") -Write-Host "Start AzGovViz $($startTime) (#$($ProductVersion))" -Write-Host "CheckCodeRunPlatform: running in $($checkCodeRunPlatform)" + [string] + $OutputPath, -if ($DebugAzAPICall) { - write-host "AzAPICall debug enabled" -ForegroundColor Cyan -} -else { - write-host "AzAPICall debug disabled" -ForegroundColor Cyan -} + [switch] + $DoNotShowRoleAssignmentsUserData, -#region ChinaBilling -$checkContext = Get-AzContext -ErrorAction Stop -Write-Host "Environment: $($checkContext.Environment.Name)" -if ($DoAzureConsumption) { - #cloudEnvironment specific - if ($checkContext.Environment.Name -eq "AzureChinaCloud") { - Write-Host "Azure Billing not supported in AzureChinaCloud, skipping Consumption.." - $DoAzureConsumption = $false - } -} -#endregion ChinaBilling - -#region htParameters (all switch params used in foreach-object -parallel) -if ($LargeTenant -eq $true) { - $NoScopeInsights = $true - $NoResourceProvidersDetailed = $true - $PolicyAtScopeOnly = $true - $RBACAtScopeOnly = $true -} + [switch] + $HierarchyMapOnly, -if ($ManagementGroupsOnly) { - $NoSingleSubscriptionOutput = $true -} + [Alias('NoASCSecureScore')] + [switch] + $NoMDfCSecureScore, -if ($HierarchyMapOnly) { - $NoJsonExport = $true -} + [switch] + $NoResourceProvidersDetailed, -if ($NoASCSecureScore -or $NoMDfCSecureScore) { - $NoMDfCSecureScore = $true -} + [int] + $LimitCriticalPercentage = 80, -$htParameters = @{ - ProductVersion = $ProductVersion - AzCloudEnv = $checkContext.Environment.Name - GithubRepository = $GithubRepository - onAzureDevOps = [bool]$onAzureDevOps - onAzureDevOpsOrGitHubActions = [bool]$onAzureDevOpsOrGitHubActions - DebugAzAPICall = [bool]$DebugAzAPICall - DoNotShowRoleAssignmentsUserData = [bool]$DoNotShowRoleAssignmentsUserData - HierarchyMapOnly = [bool]$HierarchyMapOnly - NoMDfCSecureScore = [bool]$NoMDfCSecureScore - PolicyAtScopeOnly = [bool]$PolicyAtScopeOnly - RBACAtScopeOnly = [bool]$RBACAtScopeOnly - NoResourceProvidersDetailed = [bool]$NoResourceProvidersDetailed - NoPolicyComplianceStates = [bool]$NoPolicyComplianceStates - DoNotIncludeResourceGroupsOnPolicy = [bool]$DoNotIncludeResourceGroupsOnPolicy - DoNotIncludeResourceGroupsAndResourcesOnRBAC = [bool]$DoNotIncludeResourceGroupsAndResourcesOnRBAC - LargeTenant = [bool]$LargeTenant - NoJsonExport = [bool]$NoJsonExport - NoResources = [bool]$NoResources - DoAzureConsumption = [bool]$DoAzureConsumption - ManagementGroupsOnly = [bool]$ManagementGroupsOnly -} -#endregion htParameters + [array] + $SubscriptionQuotaIdWhitelist = @('undefined'), -#region PowerShellEditionAnVersionCheck -Write-Host "Checking powershell edition and version" -$requiredPSVersion = "7.0.3" -$splitRequiredPSVersion = $requiredPSVersion.split('.') -$splitRequiredPSVersionMajor = $splitRequiredPSVersion[0] -$splitRequiredPSVersionMinor = $splitRequiredPSVersion[1] -$splitRequiredPSVersionPatch = $splitRequiredPSVersion[2] + [switch] + $NoPolicyComplianceStates, -$thisPSVersion = ($PSVersionTable.PSVersion) -$thisPSVersionMajor = ($thisPSVersion).Major -$thisPSVersionMinor = ($thisPSVersion).Minor -$thisPSVersionPatch = ($thisPSVersion).Patch + [switch] + $NoResourceDiagnosticsPolicyLifecycle, -$psVersionCheckResult = "letsCheck" + [switch] + $NoAADGroupsResolveMembers, -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))" -} + [int] + $AADServicePrincipalExpiryWarningDays = 14, -if ($psVersionCheckResult -eq "passed") { - Write-Host " PS check $psVersionCheckResult : $($psVersionCheck); (minimum supported version '$requiredPSVersion')" - Write-Host " PS Edition: $($PSVersionTable.PSEdition)" - Write-Host " PS Version: $($PSVersionTable.PSVersion)" -} -else { - Write-Host " PS check $psVersionCheckResult : $($psVersionCheck)" - Write-Host " PS Edition: $($PSVersionTable.PSEdition)" - Write-Host " PS Version: $($PSVersionTable.PSVersion)" - Write-Host " This AzGovViz version only supports Powershell 'Core' version '$($requiredPSVersion)' or higher" - Write-Host " Get Powershell: https://github.com/PowerShell/PowerShell#get-powershell" - Write-Host " Installing PowerShell on Windows: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows" - Write-Host " Installing PowerShell on Linux: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux" - Throw "Error - AzGovViz: check the last console output for details" -} -#endregion PowerShellEditionAnVersionCheck + [switch] + $NoAzureConsumption, #obsolete -#region testAzModules -$testCommands = @('Get-AzContext') -$azModules = @('Az.Accounts') + [switch] + $DoAzureConsumption, -Write-Host "Testing required Az modules cmdlets" -foreach ($testCommand in $testCommands) { - if (-not (Get-Command $testCommand -ErrorAction Ignore)) { - Write-Host " AzModule test failed: cmdlet $testCommand not available - make sure the modules $($azModules -join ", ") are installed" - Write-Host " Install the Azure Az PowerShell module: https://docs.microsoft.com/en-us/powershell/azure/install-az-ps" - Throw "Error - AzGovViz: check the last console output for details" - } - else { - Write-Host " AzModule test passed: Az ps module supporting cmdlet $testCommand installed" -ForegroundColor Green - } -} + [int] + $AzureConsumptionPeriod = 1, -Write-Host "Collecting Az modules versions" -foreach ($azModule in $azModules) { - $azModuleVersion = (Get-InstalledModule -name "$azModule" -ErrorAction Ignore).Version - if ($azModuleVersion) { - Write-Host " Az Module $azModule Version: $azModuleVersion" - $resolvedAzModuleVersion = $azModuleVersion - } - else { - Write-Host " Az Module $azModule Version: could not be assessed" - $resolvedAzModuleVersion = "could not be assessed" - } -} -#endregion testAzModules + [switch] + $NoAzureConsumptionReportExportToCSV, -#region checkAzContext -Write-Host "Checking Az Context" -if (-not $checkContext) { - Write-Host " Context test failed: No context found. Please connect to Azure (run: Connect-AzAccount) and re-run AzGovViz" -ForegroundColor Red - Throw "Error - AzGovViz: check the last console output for details" -} -else { - $accountType = $checkContext.Account.Type - $accountId = $checkContext.Account.Id - Write-Host " Context AccountId: '$($accountId)'" -ForegroundColor Yellow - Write-Host " Context AccountType: '$($accountType)'" -ForegroundColor Yellow - - if ($SubscriptionId4AzContext -ne "undefined") { - if ($checkContext.Subscription.Id -ne $SubscriptionId4AzContext) { - Write-Host " Setting AzContext to SubscriptionId: '$SubscriptionId4AzContext'" -ForegroundColor Yellow - $settingContextMaxRetries = 5 - $settingContextTryCounter = 0 - do { - $settingContextFailed = $false - try { - $null = Set-AzContext -SubscriptionId $SubscriptionId4AzContext -ErrorAction Stop - } - catch { - $settingContextFailed = $true - $settingContextTryCounter++ - $_ - Write-Host "Error setting AzContext - try again in $($settingContextTryCounter * 5) seconds" - start-sleep -seconds ($settingContextTryCounter * 5) - } - } - until($settingContextFailed -eq $false -or $settingContextTryCounter -gt $settingContextMaxRetries) - if ($settingContextTryCounter -gt $settingContextMaxRetries) { - Write-Host "Failed setting AzContext - exit" - Throw "Error - AzGovViz: check the last console output for details" - } + [switch] + $DoTranscript, - $checkContext = Get-AzContext -ErrorAction Stop - Write-Host "AzContext: $($checkContext.Subscription.Name) ($($checkContext.Subscription.Id))" -ForegroundColor Green - } - else { - Write-Host " AzContext: $($checkContext.Subscription.Name) ($($checkContext.Subscription.Id))" -ForegroundColor Green - } - } + [int] + $HtmlTableRowsLimit = 20000, #HTML TenantSummary may become unresponsive depending on client device performance. A recommendation will be shown to use the CSV file instead of opening the TF table - if (-not $checkContext.Subscription) { - $checkContext - Write-Host " Context test failed: Context is not set to any Subscription. Set your context to a subscription by running: Set-AzContext -subscription (run Get-AzSubscription to get the list of available Subscriptions). When done re-run AzGovViz" -ForegroundColor Red - Write-host " If this error occurs you may want to leverage parameter 'SubscriptionId4AzContext' (AzGovVizParallel.ps1 -SubscriptionId4AzContext '')" - Throw "Error - AzGovViz: check the last console output for details" - } - else { - Write-Host " Context test passed: Context OK" -ForegroundColor Green - } -} -#endregion checkAzContext - -#region environmentcheck -$checkAzEnvironments = Get-AzEnvironment -ErrorAction Stop - -#FutureUse -#Graph Endpoints https://docs.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints -#AzureCloud https://graph.microsoft.com -#AzureUSGovernment L4 https://graph.microsoft.us -#AzureUSGovernment L5 (DOD) https://dod-graph.microsoft.us -#AzureChinaCloud https://microsoftgraph.chinacloudapi.cn -#AzureGermanCloud https://graph.microsoft.de - -#AzureEnvironmentRelatedUrls -$htAzureEnvironmentRelatedUrls = @{} -$arrayAzureManagementEndPointUrls = @() -foreach ($checkAzEnvironment in $checkAzEnvironments) { - ($htAzureEnvironmentRelatedUrls).($checkAzEnvironment.Name) = @{} - ($htAzureEnvironmentRelatedUrls).($checkAzEnvironment.Name).ResourceManagerUrl = $checkAzEnvironment.ResourceManagerUrl - $arrayAzureManagementEndPointUrls += $checkAzEnvironment.ResourceManagerUrl - if ($checkAzEnvironment.Name -eq "AzureCloud") { - ($htAzureEnvironmentRelatedUrls).($checkAzEnvironment.Name).MSGraphUrl = "https://graph.microsoft.com" - } - if ($checkAzEnvironment.Name -eq "AzureChinaCloud") { - ($htAzureEnvironmentRelatedUrls).($checkAzEnvironment.Name).MSGraphUrl = "https://microsoftgraph.chinacloudapi.cn" - } - if ($checkAzEnvironment.Name -eq "AzureUSGovernment") { - ($htAzureEnvironmentRelatedUrls).($checkAzEnvironment.Name).MSGraphUrl = "https://graph.microsoft.us" - } - if ($checkAzEnvironment.Name -eq "AzureGermanCloud") { - ($htAzureEnvironmentRelatedUrls).($checkAzEnvironment.Name).MSGraphUrl = "https://graph.microsoft.de" - } -} -#endregion environmentcheck + [int] + $ThrottleLimit = 10, -#region delimiter -if ($CsvDelimiter -eq ";") { - $CsvDelimiterOpposite = "," -} -if ($CsvDelimiter -eq ",") { - $CsvDelimiterOpposite = ";" -} -#endregion delimiter + [Alias('ExludedResourceTypesDiagnosticsCapable')] + [array] + $ExcludedResourceTypesDiagnosticsCapable = @('microsoft.web/certificates'), -#region Function + [switch] + $DoNotIncludeResourceGroupsOnPolicy, -#region jwtdetails -#JWTDetails https://www.powershellgallery.com/packages/JWTDetails/1.0.2 -function GetJWTDetails { - [cmdletbinding()] - param( - [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] - [string]$token - ) + [switch] + $DoNotIncludeResourceGroupsAndResourcesOnRBAC, - if (!$token -contains (".") -or !$token.StartsWith("eyJ")) { Write-Error "Invalid token" -ErrorAction Stop } + [Alias('AzureDevOpsWikiHierarchyDirection')] + [parameter(ValueFromPipeline)][ValidateSet('TD', 'LR')][string]$MermaidDirection = 'TD', - #Token - foreach ($i in 0..1) { - $data = $token.Split('.')[$i].Replace('-', '+').Replace('_', '/') - switch ($data.Length % 4) { - 0 { break } - 2 { $data += '==' } - 3 { $data += '=' } - } - } + [string] + $SubscriptionId4AzContext = 'undefined', - $decodedToken = [System.Text.Encoding]::UTF8.GetString([convert]::FromBase64String($data)) | ConvertFrom-Json - Write-Verbose "JWT Token:" - Write-Verbose $decodedToken + [int] + $ChangeTrackingDays = 14, - #Signature - foreach ($i in 0..2) { - $sig = $token.Split('.')[$i].Replace('-', '+').Replace('_', '/') - switch ($sig.Length % 4) { - 0 { break } - 2 { $sig += '==' } - 3 { $sig += '=' } - } - } - Write-Verbose "JWT Signature:" - Write-Verbose $sig - $decodedToken | Add-Member -Type NoteProperty -Name "sig" -Value $sig + [string] + $FileTimeStampFormat = 'yyyyMMdd_HHmmss', - #Convert Expiry time to PowerShell DateTime - $orig = (Get-Date -Year 1970 -Month 1 -Day 1 -hour 0 -Minute 0 -Second 0 -Millisecond 0) - $timeZone = Get-TimeZone - $utcTime = $orig.AddSeconds($decodedToken.exp) - $offset = $timeZone.GetUtcOffset($(Get-Date)).TotalMinutes #Daylight saving needs to be calculated - $localTime = $utcTime.AddMinutes($offset) # Return local time, + [switch] + $NoJsonExport, - $decodedToken | Add-Member -Type NoteProperty -Name "expiryDateTime" -Value $localTime + [switch] + $JsonExportExcludeResourceGroups, - #Time to Expiry - $timeToExpiry = ($localTime - (Get-Date)) - $decodedToken | Add-Member -Type NoteProperty -Name "timeToExpiry" -Value $timeToExpiry + [switch] + $JsonExportExcludeResources, - return $decodedToken -} -$funcGetJWTDetails = $function:GetJWTDetails.ToString() -#endregion jwtdetails - -#region createbearertoken -function CreateBearerToken($targetEndPoint) { - Write-Host "+Processing new bearer token request ($targetEndPoint)" - if ($targetEndPoint -eq "ManagementAPI") { - $azureRmProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile; - $profileClient = New-Object Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient($azureRmProfile); - $catchResult = "letscheck" - try { - $newBearerAccessTokenRequest = ($profileClient.AcquireAccessToken($checkContext.Subscription.TenantId)) - } - catch { - $catchResult = $_ - } - } - if ($targetEndPoint -eq "MSGraphAPI") { - $contextForMSGraphToken = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext - $catchResult = "letscheck" - try { - $newBearerAccessTokenRequest = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($contextForMSGraphToken.Account, $contextForMSGraphToken.Environment, $contextForMSGraphToken.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)") - } - catch { - $catchResult = $_ - } - } - if ($catchResult -ne "letscheck") { - Write-Host "-ERROR processing new bearer token request ($targetEndPoint): $catchResult" -ForegroundColor Red - Write-Host "Likely your Azure credentials have not been set up or have expired, please run 'Connect-AzAccount' to set up your Azure credentials." - Write-Host "It could also well be that there are multiple context in cache, please run 'Clear-AzContext' and then run 'Connect-AzAccount'." - Throw "Error - AzGovViz: check the last console output for details" - } - $dateTimeTokenCreated = (Get-Date -Format "MM/dd/yyyy HH:mm:ss") - if ($targetEndPoint -eq "ManagementAPI") { - $script:htBearerAccessToken.AccessTokenManagement = $newBearerAccessTokenRequest.AccessToken - } - if ($targetEndPoint -eq "MSGraphAPI") { - $script:htBearerAccessToken.AccessTokenMSGraph = $newBearerAccessTokenRequest.AccessToken - } - $bearerDetails = GetJWTDetails -token $newBearerAccessTokenRequest.AccessToken - $bearerAccessTokenExpiryDateTime = $bearerDetails.expiryDateTime - $bearerAccessTokenTimeToExpiry = $bearerDetails.timeToExpiry - Write-Host "+Bearer token ($targetEndPoint): [tokenRequestProcessed: '$dateTimeTokenCreated']; [expiryDateTime: '$bearerAccessTokenExpiryDateTime']; [timeUntilExpiry: '$bearerAccessTokenTimeToExpiry']" -} -$funcCreateBearerToken = $function:CreateBearerToken.ToString() -$htBearerAccessToken = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} -#endregion createbearertoken + [switch] + $LargeTenant, -#region namingValidation -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 -} -$funcNamingValidation = $function:NamingValidation.ToString() -#endregion namingValidation + [switch] + $NoScopeInsights, -#region resolveObjectIds -function ResolveObjectIds($objectIds) { + [int] + $AADGroupMembersLimit = 500, - $arrayObjectIdsToCheck = @() - $arrayObjectIdsToCheck = foreach ($objectToCheckIfAlreadyResolved in $objectIds) { - if (-not $htPrincipals.($objectToCheckIfAlreadyResolved)) { - $objectToCheckIfAlreadyResolved - } - else { - #Write-Host "$objectToCheckIfAlreadyResolved already resolved" - } - } + [switch] + $PolicyAtScopeOnly, - if ($arrayObjectIdsToCheck.Count -gt 0) { + [switch] + $RBACAtScopeOnly, - $counterBatch = [PSCustomObject] @{ Value = 0 } - $batchSize = 1000 - $ObjectBatch = $arrayObjectIdsToCheck | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) } - $ObjectBatchCount = ($ObjectBatch | Measure-Object).Count - $batchCnt = 0 + [switch] + $NoResources, - foreach ($batch in $ObjectBatch) { - $batchCnt++ - $objectsToProcess = '"{0}"' -f ($batch.Group -join '","') - $currentTask = " Resolving ObjectIds - Batch #$batchCnt/$($ObjectBatchCount) ($(($batch.Group).Count)" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/beta/directoryObjects/getByIds" - $method = "POST" - $body = @" - { - "ids":[$($objectsToProcess)] - } -"@ - $resolveObjectIds = AzAPICall -uri $uri -method $method -body $body -currentTask $currentTask + [switch] + $StatsOptOut, - 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 $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" - } - } - } - } - } - } -} -$funcResolveObjectIds = $function:ResolveObjectIds.ToString() -#endregion resolveObjectIds - -#API -#region azapicall -function AzAPICall($uri, $method, $currentTask, $body, $listenOn, $getConsumption, $getGroup, $getGroupMembersCount, $getApp, $caller, $consistencyLevel, $getCount, $getPolicyCompliance, $getMgAscSecureScore, $getRoleAssignmentSchedules, $getDiagnosticSettingsMg, $validateAccess, $getMDfC) { - $tryCounter = 0 - $tryCounterUnexpectedError = 0 - $retryAuthorizationFailed = 5 - $retryAuthorizationFailedCounter = 0 - $apiCallResultsCollection = [System.Collections.ArrayList]@() - $initialUri = $uri - $restartDueToDuplicateNextlinkCounter = 0 - if ($htParameters.DebugAzAPICall -eq $true) { - if ($caller -like "CustomDataCollection*") { - $debugForeGroundColors = @('DarkBlue', 'DarkGreen', 'DarkCyan', 'Cyan', 'DarkMagenta', 'DarkYellow', 'Blue', 'Magenta', 'Yellow', 'Green') - $debugForeGroundColorsCount = $debugForeGroundColors.Count - $randomNumber = Get-Random -Minimum 0 -Maximum ($debugForeGroundColorsCount - 1) - $debugForeGroundColor = $debugForeGroundColors[$randomNumber] - } - else { - $debugForeGroundColor = "Cyan" - } - } - - do { - if ($arrayAzureManagementEndPointUrls | Where-Object { $uri -match $_ }) { - $targetEndpoint = "ManagementAPI" - $bearerToUse = $htBearerAccessToken.AccessTokenManagement - } - else { - $targetEndpoint = "MSGraphAPI" - $bearerToUse = $htBearerAccessToken.AccessTokenMSGraph - } - - # - $unexpectedError = $false - - $Header = @{ - "Content-Type" = "application/json"; - "Authorization" = "Bearer $bearerToUse" - } - if ($consistencyLevel) { - $Header = @{ - "Content-Type" = "application/json"; - "Authorization" = "Bearer $bearerToUse"; - "ConsistencyLevel" = "$consistencyLevel" - } - } - - $startAPICall = Get-Date - try { - if ($body) { - $azAPIRequest = Invoke-WebRequest -Uri $uri -Method $method -body $body -Headers $Header -ContentType "application/json" -UseBasicParsing - } - else { - $azAPIRequest = Invoke-WebRequest -Uri $uri -Method $method -Headers $Header -UseBasicParsing - } - } - catch { - try { - $catchResultPlain = $_.ErrorDetails.Message - if ($catchResultPlain) { - $catchResult = $catchResultPlain | ConvertFrom-Json -ErrorAction Stop - } - } - catch { - $catchResult = $catchResultPlain - $tryCounterUnexpectedError++ - $unexpectedError = $true - } - } - $endAPICall = Get-Date - $durationAPICall = NEW-TIMESPAN -Start $startAPICall -End $endAPICall - - #API Call Tracking - $tstmp = (Get-Date -Format "yyyyMMddHHmmssms") - $null = $script:arrayAPICallTracking.Add([PSCustomObject]@{ - CurrentTask = $currentTask - TargetEndpoint = $targetEndpoint - Uri = $uri - Method = $method - TryCounter = $tryCounter - TryCounterUnexpectedError = $tryCounterUnexpectedError - RetryAuthorizationFailedCounter = $retryAuthorizationFailedCounter - RestartDueToDuplicateNextlinkCounter = $restartDueToDuplicateNextlinkCounter - TimeStamp = $tstmp - Duration = $durationAPICall.TotalSeconds - }) - - if ($caller -eq "CustomDataCollection") { - $null = $script:arrayAPICallTrackingCustomDataCollection.Add([PSCustomObject]@{ - CurrentTask = $currentTask - TargetEndpoint = $targetEndpoint - Uri = $uri - Method = $method - TryCounter = $tryCounter - TryCounterUnexpectedError = $tryCounterUnexpectedError - RetryAuthorizationFailedCounter = $retryAuthorizationFailedCounter - RestartDueToDuplicateNextlinkCounter = $restartDueToDuplicateNextlinkCounter - TimeStamp = $tstmp - Duration = $durationAPICall.TotalSeconds - }) - } - - $tryCounter++ - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUGTASK: attempt#$($tryCounter) processing: $($currenttask) uri: '$($uri)'" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: attempt#$($tryCounter) processing: $($currenttask) uri: '$($uri)'" } - } - - if ($unexpectedError -eq $false) { - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: unexpectedError: false" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: unexpectedError: false" } - } - if ($azAPIRequest.StatusCode -ne 200) { - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: apiStatusCode: $($azAPIRequest.StatusCode)" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: apiStatusCode: $($azAPIRequest.StatusCode)" } - } - if ($catchResult.error.code -like "*GatewayTimeout*" -or - $catchResult.error.code -like "*BadGatewayConnection*" -or - $catchResult.error.code -like "*InvalidGatewayHost*" -or - $catchResult.error.code -like "*ServerTimeout*" -or - $catchResult.error.code -like "*ServiceUnavailable*" -or - $catchResult.code -like "*ServiceUnavailable*" -or - $catchResult.error.code -like "*MultipleErrorsOccurred*" -or - $catchResult.code -like "*InternalServerError*" -or - $catchResult.error.code -like "*InternalServerError*" -or - $catchResult.error.code -like "*RequestTimeout*" -or - $catchResult.error.code -like "*AuthorizationFailed*" -or - $catchResult.error.code -like "*ExpiredAuthenticationToken*" -or - $catchResult.error.code -like "*Authentication_ExpiredToken*" -or - ($getPolicyCompliance -and $catchResult.error.code -like "*ResponseTooLarge*") -or - ($getPolicyCompliance -and -not $catchResult.error.code) -or - $catchResult.error.code -like "*InvalidAuthenticationToken*" -or - ( - ($getConsumption -and $catchResult.error.code -eq 404) -or - ($getConsumption -and $catchResult.error.code -eq "AccountCostDisabled") -or - ($getConsumption -and $catchResult.error.message -like "*does not have any valid subscriptions*") -or - ($getConsumption -and $catchResult.error.code -eq "Unauthorized") -or - ($getConsumption -and $catchResult.error.code -eq "BadRequest" -and $catchResult.error.message -like "*The offer*is not supported*" -and $catchResult.error.message -notlike "*The offer MS-AZR-0110P is not supported*") -or - ($getConsumption -and $catchResult.error.code -eq "BadRequest" -and $catchResult.error.message -like "Invalid query definition*") -or - ($getConsumption -and $catchResult.error.code -eq "NotFound" -and $catchResult.error.message -like "*have valid WebDirect/AIRS offer type*") -or - ($getConsumption -and $catchResult.error.code -eq "NotFound" -and $catchResult.error.message -like "Cost management data is not supported for subscription(s)*") -or - ($getConsumption -and $catchResult.error.code -eq "IndirectCostDisabled") - ) -or - $catchResult.error.message -like "*The offer MS-AZR-0110P is not supported*" -or - ($getApp -and $catchResult.error.code -like "*Request_ResourceNotFound*") -or - ($getApp -and $catchResult.error.code -like "*Authorization_RequestDenied*") -or - ($getGroup -and $catchResult.error.code -like "*Request_ResourceNotFound*") -or - ($getGroupMembersCount -and $catchResult.error.code -like "*Request_ResourceNotFound*") -or - ($getGroupMembersCount -and $catchResult.error.message -like "*count is not currently supported*") -or - $catchResult.error.code -like "*UnknownError*" -or - $catchResult.error.code -like "*BlueprintNotFound*" -or - $catchResult.error.code -eq "500" -or - $catchResult.error.code -eq "ResourceRequestsThrottled" -or - $catchResult.error.code -eq "429" -or - ($getMgAscSecureScore -and $catchResult.error.code -eq "BadRequest") -or - ($getRoleAssignmentSchedules -and $catchResult.error.code -eq "ResourceNotOnboarded") -or - ($getRoleAssignmentSchedules -and $catchResult.error.code -eq "TenantNotOnboarded") -or - ($getRoleAssignmentSchedules -and $catchResult.error.code -eq "InvalidResourceType") -or - ($getDiagnosticSettingsMg -and $catchResult.error.code -eq "InvalidResourceType") -or - ($catchResult.error.code -eq "InsufficientPermissions") -or - $catchResult.error.code -eq "ClientCertificateValidationFailure" -or - ($validateAccess -and $catchResult.error.code -eq "Authorization_RequestDenied") -or - $catchResult.error.code -eq "GatewayAuthenticationFailed" -or - $catchResult.message -eq "An error has occurred." -or - ($getMDfC -and $catchResult.error.code -eq "Subscription Not Registered") -or - $catchResult.error.code -eq "GeneralError" - ) { - if (($getPolicyCompliance -and $catchResult.error.code -like "*ResponseTooLarge*") -or ($getPolicyCompliance -and -not $catchResult.error.code)) { - if ($getPolicyCompliance -and $catchResult.error.code -like "*ResponseTooLarge*") { - Write-Host "Info: $currentTask - (StatusCode: '$($azAPIRequest.StatusCode)') Response too large, skipping this scope." - return "ResponseTooLarge" - } - if ($getPolicyCompliance -and -not $catchResult.error.code) { - #seems API now returns null instead of 'ResponseTooLarge' - Write-Host "Info: $currentTask - (StatusCode: '$($azAPIRequest.StatusCode)') Response empty - handle like 'Response too large', skipping this scope." - return "ResponseTooLarge" - } - } - - if ($catchResult.error.message -like "*The offer MS-AZR-0110P is not supported*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - seems we´re hitting a malicious endpoint .. try again in $tryCounter second(s)" - Start-Sleep -Seconds $tryCounter - } - - if ($catchResult.error.code -like "*GatewayTimeout*" -or $catchResult.error.code -like "*BadGatewayConnection*" -or $catchResult.error.code -like "*InvalidGatewayHost*" -or $catchResult.error.code -like "*ServerTimeout*" -or $catchResult.error.code -like "*ServiceUnavailable*" -or $catchResult.code -like "*ServiceUnavailable*" -or $catchResult.error.code -like "*MultipleErrorsOccurred*" -or $catchResult.code -like "*InternalServerError*" -or $catchResult.error.code -like "*InternalServerError*" -or $catchResult.error.code -like "*RequestTimeout*" -or $catchResult.error.code -like "*UnknownError*" -or $catchResult.error.code -eq "500") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - try again in $tryCounter second(s)" - Start-Sleep -Seconds $tryCounter - } - - if ($catchResult.error.code -like "*AuthorizationFailed*") { - if ($validateAccess) { - #Write-Host "$currentTask failed ('$($catchResult.error.code)' | '$($catchResult.error.message)')" -ForegroundColor DarkRed - return "failed" - } - else { - if ($retryAuthorizationFailedCounter -gt $retryAuthorizationFailed) { - Write-Host "- - - - - - - - - - - - - - - - - - - - " - Write-Host "!Please report at $($htParameters.GithubRepository) and provide the following dump" -ForegroundColor Yellow - Write-Host "$currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' - $retryAuthorizationFailed retries failed - EXIT" - Write-Host "" - Write-Host "Parameters:" - foreach ($htParameter in ($htParameters.Keys | Sort-Object)) { - Write-Host "$($htParameter):$($htParameters.($htParameter))" - } - Throw "Error - AzGovViz: check the last console output for details" - } - else { - if ($retryAuthorizationFailedCounter -gt 2) { - Start-Sleep -Seconds 5 - } - if ($retryAuthorizationFailedCounter -gt 3) { - Start-Sleep -Seconds 10 - } - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' - not reasonable, retry #$retryAuthorizationFailedCounter of $retryAuthorizationFailed" - $retryAuthorizationFailedCounter ++ - } - } - - } - - if ($catchResult.error.code -like "*ExpiredAuthenticationToken*" -or $catchResult.error.code -like "*Authentication_ExpiredToken*" -or $catchResult.error.code -like "*InvalidAuthenticationToken*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' - requesting new bearer token ($targetEndpoint)" - CreateBearerToken -targetEndPoint $targetEndpoint - } - - if ( - ($getConsumption -and $catchResult.error.code -eq 404) -or - ($getConsumption -and $catchResult.error.code -eq "AccountCostDisabled") -or - ($getConsumption -and $catchResult.error.message -like "*does not have any valid subscriptions*") -or - ($getConsumption -and $catchResult.error.code -eq "Unauthorized") -or - ($getConsumption -and $catchResult.error.code -eq "BadRequest" -and $catchResult.error.message -like "*The offer*is not supported*" -and $catchResult.error.message -notlike "*The offer MS-AZR-0110P is not supported*") -or - ($getConsumption -and $catchResult.error.code -eq "BadRequest" -and $catchResult.error.message -like "Invalid query definition*") - ) { - if ($getConsumption -and $catchResult.error.code -eq 404) { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) seems Subscriptions was created only recently - skipping" - return $apiCallResultsCollection - } - - if ($getConsumption -and $catchResult.error.code -eq "AccountCostDisabled") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) seems Access to cost data has been disabled for this Account - skipping CostManagement" - return "AccountCostDisabled" - } - - if ($getConsumption -and $catchResult.error.message -like "*does not have any valid subscriptions*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) seems there are no valid Subscriptions present - skipping CostManagement" - return "NoValidSubscriptions" - } - - if ($getConsumption -and $catchResult.error.code -eq "Unauthorized") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) Unauthorized - handling as exception" - return "Unauthorized" - } - - if ($getConsumption -and $catchResult.error.code -eq "BadRequest" -and $catchResult.error.message -like "*The offer*is not supported*" -and $catchResult.error.message -notlike "*The offer MS-AZR-0110P is not supported*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) Unauthorized - handling as exception" - return "OfferNotSupported" - } - - if ($getConsumption -and $catchResult.error.code -eq "BadRequest" -and $catchResult.error.message -like "Invalid query definition*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) Unauthorized - handling as exception" - return "InvalidQueryDefinition" - } - - if ($getConsumption -and $catchResult.error.code -eq "NotFound" -and $catchResult.error.message -like "*have valid WebDirect/AIRS offer type*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) Unauthorized - handling as exception" - return "NonValidWebDirectAIRSOfferType" - } - - if ($getConsumption -and $catchResult.error.code -eq "NotFound" -and $catchResult.error.message -like "Cost management data is not supported for subscription(s)*") { - return "NotFoundNotSupported" - } - - if ($getConsumption -and $catchResult.error.code -eq "IndirectCostDisabled") { - return "IndirectCostDisabled" - } - } - - if (($getGroup) -and $catchResult.error.code -like "*Request_ResourceNotFound*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) uncertain Group status - skipping for now :)" - return "Request_ResourceNotFound" - } + [switch] + $NoSingleSubscriptionOutput, - if (($getGroupMembersCount) -and $catchResult.error.code -like "*Request_ResourceNotFound*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) uncertain Group status - skipping for now :)" - return "Request_ResourceNotFound" - } - - if ($getGroupMembersCount -and $catchResult.error.message -like "*count is not currently supported*") { - $maxTries = 7 - $sleepSec = @(1, 3, 5, 7, 10, 12, 20, 30, 40, 45)[$tryCounter] - if ($tryCounter -gt $maxTries) { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' - exit" - Throw "Error - AzGovViz: check the last console output for details" - } - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' sleeping $($sleepSec) seconds" - start-sleep -Seconds $sleepSec - } + [switch] + $ManagementGroupsOnly, - if ($getApp -and $catchResult.error.code -like "*Request_ResourceNotFound*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) uncertain ServicePrincipal status - skipping for now :)" - return "Request_ResourceNotFound" - } + [string] + $DirectorySeparatorChar = [IO.Path]::DirectorySeparatorChar, - if ($currentTask -eq "Checking AAD UserType" -and $catchResult.error.code -like "*Authorization_RequestDenied*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) cannot get the executing user´s userType information (member/guest) - proceeding as 'unknown'" - return "unknown" - } + [switch] + $ShowMemoryUsage, - if ($getApp -and $catchResult.error.code -like "*Authorization_RequestDenied*") { - if ($htParameters.userType -eq "Guest") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) - skip Application (Secrets & Certificates)" - return "skipApplications" - } - else { - Write-Host "- - - - - - - - - - - - - - - - - - - - " - Write-Host "!Please report at $($htParameters.GithubRepository) and provide the following dump" -ForegroundColor Yellow - Write-Host "$currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) - EXIT" - Write-Host "" - Write-Host "Parameters:" - foreach ($htParameter in ($htParameters.Keys | Sort-Object)) { - Write-Host "$($htParameter):$($htParameters.($htParameter))" - } - Throw "Error - AzGovViz: check the last console output for details" - } - } + #https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#role-based-access-control-limits + [int] + $LimitRBACCustomRoleDefinitionsTenant = 5000, - if ($catchResult.error.code -like "*BlueprintNotFound*") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) seems Blueprint definition is gone - skipping for now :)" - return "BlueprintNotFound" - } - if ($catchResult.error.code -eq "ResourceRequestsThrottled" -or $catchResult.error.code -eq "429") { - $sleepSeconds = 11 - if ($catchResult.error.code -eq "ResourceRequestsThrottled") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' - throttled! sleeping $sleepSeconds seconds" - start-sleep -Seconds $sleepSeconds - } - if ($catchResult.error.code -eq "429") { - if ($catchResult.error.message -like "*60 seconds*") { - $sleepSeconds = 60 - } - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' - throttled! sleeping $sleepSeconds seconds" - start-sleep -Seconds $sleepSeconds - } + [int] + $LimitRBACRoleAssignmentsManagementGroup = 500, - } + #https://docs.microsoft.com/en-us/azure/governance/policy/overview#maximum-count-of-azure-policy-objects + [int] + $LimitPOLICYPolicyAssignmentsManagementGroup = 200, - if ($getMgAscSecureScore -and $catchResult.error.code -eq "BadRequest") { - $sleepSec = @(1, 1, 2, 3, 5, 7, 9, 10, 13, 15, 20, 25, 30, 45, 60, 60, 60, 60)[$tryCounter] - $maxTries = 15 - if ($tryCounter -gt $maxTries) { - Write-Host " $currentTask - capitulation after $maxTries attempts" - return "capitulation" - } - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - try again (trying $maxTries times) in $sleepSec second(s)" - Start-Sleep -Seconds $sleepSec - } + [int] + $LimitPOLICYPolicyAssignmentsSubscription = 200, - if (($getRoleAssignmentSchedules -and $catchResult.error.code -eq "ResourceNotOnboarded") -or ($getRoleAssignmentSchedules -and $catchResult.error.code -eq "TenantNotOnboarded") -or ($getRoleAssignmentSchedules -and $catchResult.error.code -eq "InvalidResourceType")) { - if ($getRoleAssignmentSchedules -and $catchResult.error.code -eq "ResourceNotOnboarded") { - return "ResourceNotOnboarded" - } - if ($getRoleAssignmentSchedules -and $catchResult.error.code -eq "TenantNotOnboarded") { - return "TenantNotOnboarded" - } - if ($getRoleAssignmentSchedules -and $catchResult.error.code -eq "InvalidResourceType") { - return "InvalidResourceType" - } - } + [int] + $LimitPOLICYPolicyDefinitionsScopedManagementGroup = 500, - if ($getDiagnosticSettingsMg -and $catchResult.error.code -eq "InvalidResourceType") { - return "InvalidResourceType" - } + [int] + $LimitPOLICYPolicyDefinitionsScopedSubscription = 500, - if ($catchResult.error.code -eq "InsufficientPermissions" -or $catchResult.error.code -eq "ClientCertificateValidationFailure" -or $catchResult.error.code -eq "GatewayAuthenticationFailed" -or $catchResult.message -eq "An error has occurred." -or $catchResult.error.code -eq "GeneralError") { - $maxTries = 7 - $sleepSec = @(1, 3, 5, 7, 10, 12, 20, 30, 40, 45)[$tryCounter] - if ($tryCounter -gt $maxTries) { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' - exit" - Throw "Error - AzGovViz: check the last console output for details" - } - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' sleeping $($sleepSec) seconds" - start-sleep -Seconds $sleepSec - } + [int] + $LimitPOLICYPolicySetAssignmentsManagementGroup = 200, - if ($validateAccess -and $catchResult.error.code -eq "Authorization_RequestDenied") { - Write-Host "$currentTask failed ('$($catchResult.error.code)' | '$($catchResult.error.message)')" -ForegroundColor DarkRed - return "failed" - } + [int] + $LimitPOLICYPolicySetAssignmentsSubscription = 200, - if ($htParameters.userType -eq "Guest" -and $catchResult.error.code -eq "Authorization_RequestDenied") { - #https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/users-restrict-guest-permissions - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' - exit" - Write-Host "Tenant seems hardened (AAD External Identities / Guest user access = most restrictive) -> https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/users-restrict-guest-permissions" - Write-Host "AAD Role 'Directory readers' is required for your Guest User Account!" - Throw "Error - AzGovViz: check the last console output for details" - } + [int] + $LimitPOLICYPolicySetDefinitionsScopedTenant = 2500, - if ($getMDfC -and $catchResult.error.code -eq "Subscription Not Registered") { - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') '$($catchResult.error.code)' | '$($catchResult.error.message)' skipping Subscription" - return "SubScriptionNotRegistered" - } + [int] + $LimitPOLICYPolicySetDefinitionsScopedManagementGroup = 200, - } - else { - if (-not $catchResult.code -and -not $catchResult.error.code -and -not $catchResult.message -and -not $catchResult.error.message -and -not $catchResult -and $tryCounter -lt 6) { - if ($azAPIRequest.StatusCode -eq 204 -and $getConsumption) { - return $apiCallResultsCollection - } - else { - $sleepSec = @(3, 7, 12, 20, 30, 45, 60)[$tryCounter] - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) try again in $sleepSec second(s)" - Start-Sleep -Seconds $sleepSec - } - } - elseif (-not $catchResult.code -and -not $catchResult.error.code -and -not $catchResult.message -and -not $catchResult.error.message -and $catchResult -and $tryCounter -lt 6) { - $sleepSec = @(3, 7, 12, 20, 30, 45, 60)[$tryCounter] - Write-Host " $currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) try again in $sleepSec second(s)" - Start-Sleep -Seconds $sleepSec - } - else { - if ($htParameters.userType -eq "Guest" -and $catchResult.error.code -eq "Authorization_RequestDenied") { - Write-Host "$currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) (guest : $($htParameters.userType)) - EXIT" - Write-Host "Tenant seems hardened (AAD External Identities / Guest user access = most restrictive) -> https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/users-restrict-guest-permissions" - Write-Host "AAD Role 'Directory readers' is required for your Guest User Account!" - Write-Host "" - } - else { - Write-Host "- - - - - - - - - - - - - - - - - - - - " - Write-Host "!Please report at $($htParameters.GithubRepository) and provide the following dump" -ForegroundColor Yellow - Write-Host "$currentTask - try #$tryCounter; returned: (StatusCode: '$($azAPIRequest.StatusCode)') <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) - EXIT" - Write-Host "" - Write-Host "Parameters:" - foreach ($htParameter in ($htParameters.Keys | Sort-Object)) { - Write-Host "$($htParameter):$($htParameters.($htParameter))" - } - if ($getConsumption) { - Write-Host "If Consumption data is not that important for you, do not use parameter: -DoAzureConsumption (however, please still report the issue - thank you)" - } - } - Throw "Error - AzGovViz: check the last console output for details" - } - } - } - else { - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: apiStatusCode: $($azAPIRequest.StatusCode)" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: apiStatusCode: $($azAPIRequest.StatusCode)" } - } - $azAPIRequestConvertedFromJson = ($azAPIRequest.Content | ConvertFrom-Json) - if ($listenOn -eq "Content") { - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: listenOn=content ($((($azAPIRequestConvertedFromJson)).count))" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: listenOn=content ($((($azAPIRequestConvertedFromJson)).count))" } - } - $null = $apiCallResultsCollection.Add($azAPIRequestConvertedFromJson) - } - elseif ($listenOn -eq "ContentProperties") { - if (($azAPIRequestConvertedFromJson.properties.rows).Count -gt 0) { - foreach ($consumptionline in $azAPIRequestConvertedFromJson.properties.rows) { - $hlper = $htSubscriptionsMgPath.($consumptionline[1]) - $null = $apiCallResultsCollection.Add([PSCustomObject]@{ - "$($azAPIRequestConvertedFromJson.properties.columns.name[0])" = $consumptionline[0] - "$($azAPIRequestConvertedFromJson.properties.columns.name[1])" = $consumptionline[1] - SubscriptionName = $hlper.DisplayName - SubscriptionMgPath = $hlper.ParentNameChainDelimited - "$($azAPIRequestConvertedFromJson.properties.columns.name[2])" = $consumptionline[2] - "$($azAPIRequestConvertedFromJson.properties.columns.name[3])" = $consumptionline[3] - "$($azAPIRequestConvertedFromJson.properties.columns.name[4])" = $consumptionline[4] - "$($azAPIRequestConvertedFromJson.properties.columns.name[5])" = $consumptionline[5] - "$($azAPIRequestConvertedFromJson.properties.columns.name[6])" = $consumptionline[6] - }) - } - } - } - else { - if (($azAPIRequestConvertedFromJson).value) { - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: listenOn=default(value) value exists ($((($azAPIRequestConvertedFromJson).value).count))" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: listenOn=default(value) value exists ($((($azAPIRequestConvertedFromJson).value).count))" } - } - foreach ($entry in $azAPIRequestConvertedFromJson.value) { - $null = $apiCallResultsCollection.Add($entry) - } + [int] + $LimitPOLICYPolicySetDefinitionsScopedSubscription = 200, - if ($getGuests) { - $guestAccountsCount = ($apiCallResultsCollection).Count - if ($guestAccountsCount % 1000 -eq 0) { - write-host " $guestAccountsCount processed" - } - } - } - else { - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: listenOn=default(value) value not exists; return empty array" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: listenOn=default(value) value not exists; return empty array" } - } - } - } + #https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#subscription-limits + [int] + $LimitResourceGroups = 980, - $isMore = $false - if (-not $validateAccess) { - if ($azAPIRequestConvertedFromJson.nextLink) { - $isMore = $true - if ($uri -eq $azAPIRequestConvertedFromJson.nextLink) { - if ($restartDueToDuplicateNextlinkCounter -gt 3) { - Write-Host " $currentTask restartDueToDuplicateNextlinkCounter: #$($restartDueToDuplicateNextlinkCounter) - Please report this error/exit" - Throw "Error - AzGovViz: check the last console output for details" - } - else { - $restartDueToDuplicateNextlinkCounter++ - Write-Host "nextLinkLog: uri is equal to nextLinkUri" - Write-Host "nextLinkLog: uri: $uri" - Write-Host "nextLinkLog: nextLinkUri: $($azAPIRequestConvertedFromJson.nextLink)" - Write-Host "nextLinkLog: re-starting (#$($restartDueToDuplicateNextlinkCounter)) '$currentTask'" - $apiCallResultsCollection = [System.Collections.ArrayList]@() - $uri = $initialUri - Start-Sleep -Seconds 10 - CreateBearerToken -targetEndPoint $targetEndpoint - Start-Sleep -Seconds 10 - } - } - else { - $uri = $azAPIRequestConvertedFromJson.nextLink - } - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: nextLink: $Uri" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: nextLink: $Uri" } - } - } - elseif ($azAPIRequestConvertedFromJson."@oData.nextLink") { - $isMore = $true - if ($uri -eq $azAPIRequestConvertedFromJson."@odata.nextLink") { - if ($restartDueToDuplicateNextlinkCounter -gt 3) { - Write-Host " $currentTask restartDueToDuplicate@odataNextlinkCounter: #$($restartDueToDuplicateNextlinkCounter) - Please report this error/exit" - Throw "Error - AzGovViz: check the last console output for details" - } - else { - $restartDueToDuplicateNextlinkCounter++ - Write-Host "nextLinkLog: uri is equal to @odata.nextLinkUri" - Write-Host "nextLinkLog: uri: $uri" - Write-Host "nextLinkLog: @odata.nextLinkUri: $($azAPIRequestConvertedFromJson."@odata.nextLink")" - Write-Host "nextLinkLog: re-starting (#$($restartDueToDuplicateNextlinkCounter)) '$currentTask'" - $apiCallResultsCollection = [System.Collections.ArrayList]@() - $uri = $initialUri - Start-Sleep -Seconds 10 - CreateBearerToken -targetEndPoint $targetEndpoint - Start-Sleep -Seconds 10 - } - } - else { - $uri = $azAPIRequestConvertedFromJson."@odata.nextLink" - } - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: @oData.nextLink: $Uri" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: @oData.nextLink: $Uri" } - } - } - elseif ($azAPIRequestConvertedFromJson.properties.nextLink) { - $isMore = $true - if ($uri -eq $azAPIRequestConvertedFromJson.properties.nextLink) { - if ($restartDueToDuplicateNextlinkCounter -gt 3) { - Write-Host " $currentTask restartDueToDuplicateNextlinkCounter: #$($restartDueToDuplicateNextlinkCounter) - Please report this error/exit" - Throw "Error - AzGovViz: check the last console output for details" - } - else { - $restartDueToDuplicateNextlinkCounter++ - Write-Host "nextLinkLog: uri is equal to nextLinkUri" - Write-Host "nextLinkLog: uri: $uri" - Write-Host "nextLinkLog: nextLinkUri: $($azAPIRequestConvertedFromJson.properties.nextLink)" - Write-Host "nextLinkLog: re-starting (#$($restartDueToDuplicateNextlinkCounter)) '$currentTask'" - $apiCallResultsCollection = [System.Collections.ArrayList]@() - $uri = $initialUri - Start-Sleep -Seconds 10 - CreateBearerToken -targetEndPoint $targetEndpoint - Start-Sleep -Seconds 10 - } - } - else { - $uri = $azAPIRequestConvertedFromJson.properties.nextLink - } - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: nextLink: $Uri" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: nextLink: $Uri" } - } - } - else { - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: NextLink: none" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: NextLink: none" } - } - } - } - } - } - else { - if ($htParameters.DebugAzAPICall -eq $true -or $tryCounter -gt 3) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: unexpectedError: notFalse" -ForegroundColor $debugForeGroundColor } - if ($htParameters.DebugAzAPICall -eq $false -and $tryCounter -gt 3) { Write-Host " Forced DEBUG: unexpectedError: notFalse" } - } - if ($tryCounterUnexpectedError -lt 13) { - $sleepSec = @(1, 2, 3, 5, 7, 10, 13, 17, 20, 30, 40, 50, , 55, 60)[$tryCounterUnexpectedError] - Write-Host " $currentTask #$tryCounterUnexpectedError 'Unexpected Error' occurred (trying 10 times); sleep $sleepSec seconds" - Write-Host $catchResult - Start-Sleep -Seconds $sleepSec - } - else { - Write-Host " $currentTask #$tryCounterUnexpectedError 'Unexpected Error' occurred (tried 5 times)/exit" - Throw "Error - AzGovViz: check the last console output for details" - } - } - } - until($azAPIRequest.StatusCode -eq 200 -and -not $isMore) - return $apiCallResultsCollection -} -$funcAzAPICall = $function:AzAPICall.ToString() -#endregion azapicall + [int] + $LimitTagsSubscription = 50 +) -#region azapicalldiag -function AzAPICallDiag($uri, $method, $currentTask, $resourceType, $resourceId) { - if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUGTASK: $currentTask" -ForegroundColor Cyan } - $tryCounter = 0 - $tryCounterUnexpectedError = 0 +$Error.clear() +$ErrorActionPreference = 'Stop' +#removeNoise +$ProgressPreference = 'SilentlyContinue' +Set-Item Env:\SuppressAzurePowerShellBreakingChangeWarnings 'true' - do { - if ($arrayAzureManagementEndPointUrls | Where-Object { $uri -match $_ }) { - $targetEndpoint = "ManagementAPI" - $bearerToUse = $htBearerAccessToken.AccessTokenManagement - } - else { - $targetEndpoint = "MSGraphAPI" - $bearerToUse = $htBearerAccessToken.AccessTokenMSGraph - } - - #API Call Tracking - $tstmp = (Get-Date -Format "yyyyMMddHHmmssms") - $null = $script:arrayAPICallTracking.Add([PSCustomObject]@{ - CurrentTask = $currentTask - TargetEndpoint = $targetEndpoint - Uri = $uri - Method = $method - TryCounter = $tryCounter - TryCounterUnexpectedError = 0 - RetryAuthorizationFailedCounter = 0 - RestartDueToDuplicateNextlinkCounter = 0 - TimeStamp = $tstmp - }) +#start +$startAzGovViz = Get-Date +$startTime = Get-Date -Format 'dd-MMM-yyyy HH:mm:ss' +Write-Host "Start AzGovViz $($startTime) (#$($ProductVersion))" - $tryCounter++ - $retryAuthorizationFailed = 5 - $retryAuthorizationFailedCounter = 0 - $unexpectedError = $false - try { - $azAPIRequest = $null - $azAPIRequest = Invoke-WebRequest -uri $uri -Method $method -Headers @{"Content-Type" = "application/json"; "Authorization" = "Bearer $bearerToUse" } -UseBasicParsing - } - catch { - try { - $catchResultPlain = $_.ErrorDetails.Message - $catchResult = $catchResultPlain | ConvertFrom-Json -ErrorAction Stop #SilentlyContinue - } - catch { - $catchResult = $catchResultPlain - $tryCounterUnexpectedError++ - $unexpectedError = $true - } - } - if ($unexpectedError -eq $false) { - if ($azAPIRequest.StatusCode -ne 200) { - if ($catchResult.error.code -like "*GatewayTimeout*" -or $catchResult.error.code -like "*BadGatewayConnection*" -or $catchResult.error.code -like "*InvalidGatewayHost*" -or $catchResult.error.code -like "*ServerTimeout*" -or $catchResult.error.code -like "*ServiceUnavailable*" -or $catchResult.code -like "*ServiceUnavailable*" -or $catchResult.error.code -like "*MultipleErrorsOccurred*" -or $catchResult.code -like "*InternalServerError*" -or $catchResult.error.code -like "*InternalServerError*" -or $catchResult.code -like "*RequestTimeout*" -or $catchResult.error.code -like "*RequestTimeout*" -or $catchResult.error.code -like "*AuthorizationFailed*" -or $catchResult.code -like "*NotSupported*" -or $catchResult.error.code -like "*ExpiredAuthenticationToken*" -or $catchResult.error.code -like "*Authentication_ExpiredToken*" -or $catchResult.error.code -like "*ResourceNotFound*" -or $catchResult.code -like "*ResourceNotFound*" -or $catchResult.error.code -like "*ResourceGroupNotFound*" -or $catchResult.code -like "*ResourceGroupNotFound*" -or $catchResult.error.code -like "*UnknownError*" -or $catchResult.error.code -eq "500") { - if ($catchResult.error.code -like "*GatewayTimeout*" -or $catchResult.error.code -like "*BadGatewayConnection*" -or $catchResult.error.code -like "*InvalidGatewayHost*" -or $catchResult.error.code -like "*ServerTimeout*" -or $catchResult.error.code -like "*ServiceUnavailable*" -or $catchResult.code -like "*ServiceUnavailable*" -or $catchResult.error.code -like "*MultipleErrorsOccurred*" -or $catchResult.code -like "*InternalServerError*" -or $catchResult.error.code -like "*InternalServerError*" -or $catchResult.code -like "*RequestTimeout*" -or $catchResult.error.code -like "*RequestTimeout*" -or $catchResult.error.code -like "*UnknownError*" -or $catchResult.error.code -eq "500") { - Write-Host " $currentTask - try #$tryCounter; returned: <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - try again in $tryCounter second(s)" - Start-Sleep -Seconds $tryCounter - } - if ($catchResult.code -like "*NotSupported*") { - Write-Host " $($catchResult.code) | $($catchResult.message)" - } - if ($catchResult.error.code -like "*AuthorizationFailed*") { - if ($retryAuthorizationFailedCounter -gt $retryAuthorizationFailed) { - Write-Host " $currentTask - try #$tryCounter; returned: '$($catchResult.error.code)' | '$($catchResult.error.message)' - $retryAuthorizationFailed retries failed - investigate that error!" - Throw "Error - AzGovViz: check the last console output for details" - } - else { - if ($retryAuthorizationFailedCounter -gt 2) { - Start-Sleep -Seconds 5 - } - if ($retryAuthorizationFailedCounter -gt 3) { - Start-Sleep -Seconds 10 - } - Write-Host " $currentTask - try #$tryCounter; returned: '$($catchResult.error.code)' | '$($catchResult.error.message)' - not reasonable, retry #$retryAuthorizationFailedCounter of $retryAuthorizationFailed" - $retryAuthorizationFailedCounter ++ - } - } - if ($catchResult.error.code -like "*ExpiredAuthenticationToken*" -or $catchResult.error.code -like "*Authentication_ExpiredToken*") { - Write-Host " $currentTask - try #$tryCounter; returned: '$($catchResult.error.code)' | '$($catchResult.error.message)' - requesting new bearer token" - CreateBearerToken -targetEndPoint $targetEndpoint - } - if ($catchResult.error.code -like "*ResourceNotFound*" -or $catchResult.code -like "*ResourceNotFound*" -or $catchResult.error.code -like "*ResourceGroupNotFound*" -or $catchResult.code -like "*ResourceGroupNotFound*") { - if ($catchResult.error.code -like "*ResourceNotFound*" -or $catchResult.code -like "*ResourceNotFound*") { - Write-Host " ResourceGone | The resourceId '$($resourceId)' seems meanwhile deleted." - } - if ($catchResult.error.code -like "*ResourceGroupNotFound*" -or $catchResult.code -like "*ResourceGroupNotFound*") { - Write-Host " ResourceGone | ResourceGroup not found - the resourceId '$($resourceId)' seems meanwhile deleted." - } - $script:responseJSON = "meanwhile_deleted" - } - } - else { - Write-Host " $currentTask - try #$tryCounter; returned: <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - investigate that error!" - Throw "Error - AzGovViz: check the last console output for details" - } - } - else { - Write-Host " ResourceTypeSupported | The resource type '$($resourcetype)' supports diagnostic settings." - $Script:responseJSON = $azAPIRequest.Content | ConvertFrom-Json - } - } - else { - if ($tryCounterUnexpectedError -lt 6) { - Write-Host " $currentTask #$tryCounterUnexpectedError 'Unexpected Error' occurred (trying 5 times)" - Write-Host $catchResult - Start-Sleep -Seconds $tryCounterUnexpectedError - } - else { - Throw "Error - AzGovViz: check the last console output for details" - } - } - } - until($azAPIRequest.StatusCode -eq 200 -or $catchResult.code -like "*NotSupported*" -or $responseJSON -eq "meanwhile_deleted") +#region Functions +function addHtParameters { + Write-Host 'Add AzGovViz htParameters' + if ($LargeTenant -eq $true) { + $script:NoScopeInsights = $true + $NoResourceProvidersDetailed = $true + $PolicyAtScopeOnly = $true + $RBACAtScopeOnly = $true + } + + if ($ManagementGroupsOnly) { + $script:NoSingleSubscriptionOutput = $true + } + + if ($HierarchyMapOnly) { + $NoJsonExport = $true + } + + $script:azAPICallConf['htParameters'] += [ordered]@{ + DoAzureConsumption = [bool]$DoAzureConsumption + DoNotIncludeResourceGroupsOnPolicy = [bool]$DoNotIncludeResourceGroupsOnPolicy + DoNotIncludeResourceGroupsAndResourcesOnRBAC = [bool]$DoNotIncludeResourceGroupsAndResourcesOnRBAC + DoNotShowRoleAssignmentsUserData = [bool]$DoNotShowRoleAssignmentsUserData + HierarchyMapOnly = [bool]$HierarchyMapOnly + LargeTenant = [bool]$LargeTenant + ManagementGroupsOnly = [bool]$ManagementGroupsOnly + NoJsonExport = [bool]$NoJsonExport + NoMDfCSecureScore = [bool]$NoMDfCSecureScore + NoResourceProvidersDetailed = [bool]$NoResourceProvidersDetailed + NoPolicyComplianceStates = [bool]$NoPolicyComplianceStates + NoResources = [bool]$NoResources + ProductVersion = $ProductVersion + PolicyAtScopeOnly = [bool]$PolicyAtScopeOnly + RBACAtScopeOnly = [bool]$RBACAtScopeOnly + } + Write-Host 'htParameters:' + $azAPICallConf['htParameters'] | format-table -AutoSize | Out-String + Write-Host 'Add AzGovViz htParameters succeeded' -ForegroundColor Green } -$funcAzAPICallDiag = $function:AzAPICallDiag.ToString() -#endregion azapicalldiag - -#region AddIndexNumberToArray -function AddIndexNumberToArray ( +function addIndexNumberToArray ( [Parameter(Mandatory = $True)] [array]$array ) { for ($i = 0; $i -lt ($array).count; $i++) { - Add-Member -InputObject $array[$i] -Name "#" -Value ($i + 1) -MemberType NoteProperty + Add-Member -InputObject $array[$i] -Name '#' -Value ($i + 1) -MemberType NoteProperty } - $array + return $array } -#endregion AddIndexNumberToArray - -#region addRowToTable -function AddRowToTable() { +function addRowToTable() { Param ( [string]$level = 0, - [string]$mgName = "", - [string]$mgId = "", - [string]$mgParentId = "", - [string]$mgParentName = "", - [string]$mgASCSecureScore = "", - [string]$Subscription = "", - [string]$SubscriptionId = "", - [string]$SubscriptionQuotaId = "", - [string]$SubscriptionState = "", - [string]$SubscriptionASCSecureScore = "", - [string]$SubscriptionTags = "", + [string]$mgName = '', + [string]$mgId = '', + [string]$mgParentId = '', + [string]$mgParentName = '', + [string]$mgASCSecureScore = '', + [string]$Subscription = '', + [string]$SubscriptionId = '', + [string]$SubscriptionQuotaId = '', + [string]$SubscriptionState = '', + [string]$SubscriptionASCSecureScore = '', + [string]$SubscriptionTags = '', [int]$SubscriptionTagsCount = 0, - [string]$Policy = "", - [string]$PolicyAvailability = "", - [string]$PolicyDescription = "", - [string]$PolicyVariant = "", - [string]$PolicyType = "", - [string]$PolicyCategory = "", - [string]$PolicyDefinitionIdGuid = "", - [string]$PolicyDefinitionId = "", - [string]$PolicyDefintionScope = "", - [string]$PolicyDefintionScopeMgSub = "", - [string]$PolicyDefintionScopeId = "", + [string]$Policy = '', + [string]$PolicyAvailability = '', + [string]$PolicyDescription = '', + [string]$PolicyVariant = '', + [string]$PolicyType = '', + [string]$PolicyCategory = '', + [string]$PolicyDefinitionIdGuid = '', + [string]$PolicyDefinitionId = '', + [string]$PolicyDefintionScope = '', + [string]$PolicyDefintionScopeMgSub = '', + [string]$PolicyDefintionScopeId = '', [int]$PolicyDefinitionsScopedLimit = 0, [int]$PolicyDefinitionsScopedCount = 0, [int]$PolicySetDefinitionsScopedLimit = 0, [int]$PolicySetDefinitionsScopedCount = 0, - [string]$PolicyDefinitionEffectDefault = "", - [string]$PolicyDefinitionEffectFixed = "", - [string]$PolicyAssignmentScope = "", - [string]$PolicyAssignmentScopeMgSubRg = "", - [string]$PolicyAssignmentScopeName = "", - $PolicyAssignmentNotScopes = "", - [string]$PolicyAssignmentId = "", - [string]$PolicyAssignmentName = "", - [string]$PolicyAssignmentDisplayName = "", - [string]$PolicyAssignmentDescription = "", - [string]$PolicyAssignmentEnforcementMode = "", - $PolicyAssignmentNonComplianceMessages = "", - [string]$PolicyAssignmentIdentity = "", + [string]$PolicyDefinitionEffectDefault = '', + [string]$PolicyDefinitionEffectFixed = '', + [string]$PolicyAssignmentScope = '', + [string]$PolicyAssignmentScopeMgSubRg = '', + [string]$PolicyAssignmentScopeName = '', + $PolicyAssignmentNotScopes = '', + [string]$PolicyAssignmentId = '', + [string]$PolicyAssignmentName = '', + [string]$PolicyAssignmentDisplayName = '', + [string]$PolicyAssignmentDescription = '', + [string]$PolicyAssignmentEnforcementMode = '', + $PolicyAssignmentNonComplianceMessages = '', + [string]$PolicyAssignmentIdentity = '', [int]$PolicyAssignmentLimit = 0, [int]$PolicyAssignmentCount = 0, [int]$PolicyAssignmentAtScopeCount = 0, @@ -1790,50 +579,50 @@ function AddRowToTable() { [int]$PolicySetAssignmentCount = 0, [int]$PolicySetAssignmentAtScopeCount = 0, [int]$PolicyAndPolicySetAssignmentAtScopeCount = 0, - [string]$PolicyAssignmentAssignedBy = "", - [string]$PolicyAssignmentCreatedBy = "", - [string]$PolicyAssignmentCreatedOn = "", - [string]$PolicyAssignmentUpdatedBy = "", - [string]$PolicyAssignmentUpdatedOn = "", - [string]$RoleDefinitionId = "", - [string]$RoleDefinitionName = "", - [string]$RoleAssignmentIdentityDisplayname = "", - [string]$RoleAssignmentIdentitySignInName = "", - [string]$RoleAssignmentIdentityObjectId = "", - [string]$RoleAssignmentIdentityObjectType = "", - [string]$RoleAssignmentId = "", - [string]$RoleAssignmentScope = "", - [string]$RoleAssignmentScopeName = "", - [string]$RoleAssignmentScopeRG = "", - [string]$RoleAssignmentScopeRes = "", - [string]$RoleAssignmentScopeType = "", - [string]$RoleAssignmentCreatedBy = "", - [string]$RoleAssignmentCreatedOn = "", + [string]$PolicyAssignmentAssignedBy = '', + [string]$PolicyAssignmentCreatedBy = '', + [string]$PolicyAssignmentCreatedOn = '', + [string]$PolicyAssignmentUpdatedBy = '', + [string]$PolicyAssignmentUpdatedOn = '', + [string]$RoleDefinitionId = '', + [string]$RoleDefinitionName = '', + [string]$RoleAssignmentIdentityDisplayname = '', + [string]$RoleAssignmentIdentitySignInName = '', + [string]$RoleAssignmentIdentityObjectId = '', + [string]$RoleAssignmentIdentityObjectType = '', + [string]$RoleAssignmentId = '', + [string]$RoleAssignmentScope = '', + [string]$RoleAssignmentScopeName = '', + [string]$RoleAssignmentScopeRG = '', + [string]$RoleAssignmentScopeRes = '', + [string]$RoleAssignmentScopeType = '', + [string]$RoleAssignmentCreatedBy = '', + [string]$RoleAssignmentCreatedOn = '', $RoleAssignmentCreatedOnUnformatted, - [string]$RoleAssignmentUpdatedBy = "", - [string]$RoleAssignmentUpdatedOn = "", - [string]$RoleIsCustom = "", - [string]$RoleAssignableScopes = "", + [string]$RoleAssignmentUpdatedBy = '', + [string]$RoleAssignmentUpdatedOn = '', + [string]$RoleIsCustom = '', + [string]$RoleAssignableScopes = '', [int]$RoleAssignmentsLimit = 0, [int]$RoleAssignmentsCount = 0, - [string]$RoleActions = "", - [string]$RoleNotActions = "", - [string]$RoleDataActions = "", - [string]$RoleNotDataActions = "", + [string]$RoleActions = '', + [string]$RoleNotActions = '', + [string]$RoleDataActions = '', + [string]$RoleNotDataActions = '', $RoleCanDoRoleAssignments, [int]$RoleSecurityCustomRoleOwner = 0, [int]$RoleSecurityOwnerAssignmentSP = 0, - [string]$BlueprintName = "", - [string]$BlueprintId = "", - [string]$BlueprintDisplayName = "", - [string]$BlueprintDescription = "", - [string]$BlueprintScoped = "", - [string]$BlueprintAssignmentVersion = "", - [string]$BlueprintAssignmentId = "", - [string]$RoleAssignmentPIM = "", - [string]$RoleAssignmentPIMAssignmentType = "", - $RoleAssignmentPIMSlotStart = "", - $RoleAssignmentPIMSlotEnd = "" + [string]$BlueprintName = '', + [string]$BlueprintId = '', + [string]$BlueprintDisplayName = '', + [string]$BlueprintDescription = '', + [string]$BlueprintScoped = '', + [string]$BlueprintAssignmentVersion = '', + [string]$BlueprintAssignmentId = '', + [string]$RoleAssignmentPIM = '', + [string]$RoleAssignmentPIMAssignmentType = '', + $RoleAssignmentPIMSlotStart = '', + $RoleAssignmentPIMSlotEnd = '' ) $null = $script:newTable.Add([PSCustomObject]@{ @@ -1933,4705 +722,5060 @@ function AddRowToTable() { RoleAssignmentPIMSlotEnd = $RoleAssignmentPIMSlotEnd }) } -$funcAddRowToTable = $function:AddRowToTable.ToString() -#endregion addRowToTable +function buildJSON { + #$fileTimestamp = Get-Date -Format "yyyyMM-dd HHmmss" + $startJSON = Get-Date + $startBuildHt = Get-Date -#region functions4DataCollection + Write-Host 'Create Hierarchy JSON' + Write-Host ' Create ht for JSON' -function DataCollectionMGSecureScore { - [CmdletBinding()]Param( - [string]$Id - ) + $htJSON = [ordered]@{} + $htJSON.ManagementGroups = [ordered]@{} - $mgAscSecureScoreResult = "" - if ($htParameters.NoMDfCSecureScore -eq $false) { - if ($htMgASCSecureScore.($Id)) { - $mgAscSecureScoreResult = $htMgASCSecureScore.($Id).SecureScore - } - else { - $mgAscSecureScoreResult = "isNullOrEmpty" - } - } - return $mgAscSecureScoreResult -} -$funcDataCollectionMGSecureScore = $function:DataCollectionMGSecureScore.ToString() + $MgIds = ($optimizedTableForPathQuery) | select-object -property level, MgId, MgName, mgParentId, mgParentName | Sort-Object -property level, MgId -unique + $grpScopePolicyDefinitionsCustom = (($htCacheDefinitionsPolicy).values).where( { $_.Type -eq 'Custom' }) | Group-Object ScopeMgSub + $grpMgScopePolicyDefinitionsCustom = ($grpScopePolicyDefinitionsCustom.where( { $_.Name -eq 'Mg' }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId) + $grpSubScopePolicyDefinitionsCustom = ($grpScopePolicyDefinitionsCustom.where( { $_.Name -eq 'Sub' }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId) -function DataCollectionDefenderPlans { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $ChildMgMgPath - ) + $grpScopePolicySetDefinitionsCustom = (($htCacheDefinitionsPolicySet).values).where( { $_.Type -eq 'Custom' }) | Group-Object ScopeMgSub + $grpMgScopePolicySetDefinitionsCustom = $grpScopePolicySetDefinitionsCustom.where( { $_.Name -eq 'Mg' }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId + $grpSubScopePolicySetDefinitionsCustom = $grpScopePolicySetDefinitionsCustom.where( { $_.Name -eq 'Sub' }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId - $currentTask = "Getting Microsoft Defender for Cloud plans for Subscription: '$($scopeDisplayName)' ('$scopeId')" - #https://docs.microsoft.com/en-us/rest/api/securitycenter/pricings - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Security/pricings?api-version=2018-06-01" - $method = "GET" - $defenderPlansResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" -getMDfC $true + $grpScopePolicyAssignments = ($htCacheAssignmentsPolicy).values | Group-Object -Property AssignmentScopeMgSubRg + $grpMgScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq 'Mg' }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId + $grpSubScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq 'Sub' }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId - if ($defenderPlansResult -eq "SubScriptionNotRegistered") { - #Subscription skipped for MDfC - $null = $script:arrayDefenderPlansSubscriptionNotRegistered.Add([PSCustomObject]@{ - subscriptionId = $scopeId - subscriptionName = $scopeDisplayName - subscriptionMgPath = $childMgMgPath - }) - } - 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 - }) + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + if (-not $JsonExportExcludeResourceGroups) { + $grpRGScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq 'RG' }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId + $htSubRGPolicyAssignments = @{} + foreach ($rgpa in $grpRGScopePolicyAssignments) { + $subId = ($rgpa.Name).split('/')[0] + if (-not $htSubRGPolicyAssignments.($subId)) { + $htSubRGPolicyAssignments.($subId) = @{} + } + if (-not $htSubRGPolicyAssignments.($subId).PolicyAssignments) { + $htSubRGPolicyAssignments.($subId).PolicyAssignments = @() + } + $htSubRGPolicyAssignments.($subId).PolicyAssignments += $rgpa.group } } } -} -$funcDataCollectionDefenderPlans = $function:DataCollectionDefenderPlans.ToString() -function DataCollectionDiagnosticsSub { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $ChildMgMgPath, - $ChildMgId - ) - - $currentTask = "getDiagnosticSettingsSub for Subscription: '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/microsoft.insights/diagnosticSettings?api-version=2021-05-01-preview" - $method = "GET" - $getDiagnosticSettingsSub = AzAPICall -uri $uri -method $method -currentTask $currentTask + $grpScopeRoleAssignments = ($htCacheAssignmentsRole).values | Group-Object -Property AssignmentScopeTenMgSubRgRes + $grpTenantScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'Tenant' }).Group | Group-Object -Property AssignmentScopeId + $grpMgScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'Mg' }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId + $grpSubScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'Sub' }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId - 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 - }) + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + if (-not $JsonExportExcludeResourceGroups) { + $grpRGScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'RG' }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId + $htSubRGRoleAssignments = @{} + foreach ($rgra in $grpRGScopeRoleAssignments) { + $subId = ($rgra.Name).split('/')[0] + if (-not $htSubRGRoleAssignments.($subId)) { + $htSubRGRoleAssignments.($subId) = @{} } - } - - $htLogs = @{} - if ($diagnosticSetting.Properties.logs) { - foreach ($logCategory in $diagnosticSetting.properties.logs) { - if ($logCategory.enabled) { - $htLogs.($logCategory.category) = "true" - } - else { - $htLogs.($logCategory.category) = "false" - } + if (-not $htSubRGRoleAssignments.($subId).RoleAssignments) { + $htSubRGRoleAssignments.($subId).RoleAssignments = @() } + $htSubRGRoleAssignments.($subId).RoleAssignments += $rgra.group } - 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 - ) + #res + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + if (-not $JsonExportExcludeResources) { + $grpResScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'Res' }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId + $htSubResRoleAssignments = @{} + foreach ($resra in $grpResScopeRoleAssignments.Group) { + $raSplit = ($resra.Assignment.RoleAssignmentId).split('/') + $splitSubId = $raSplit[2] + $splitRg = $raSplit[4] + if (-not $htSubResRoleAssignments.($splitSubId)) { + $htSubResRoleAssignments.($splitSubId) = @{} + } + if (-not $htSubResRoleAssignments.($splitSubId).($splitRg)) { + $htSubResRoleAssignments.($splitSubId).($splitRg) = @{} - $mgPath = $htManagementGroupsMgPath.($scopeId).pathDelimited - $currentTask = "getDiagnosticSettingsMg '$($scopeDisplayName)' ('$($scopeId)')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($mgdetail.Name)/providers/microsoft.insights/diagnosticSettings?api-version=2020-01-01-preview" - $method = "GET" - $getDiagnosticSettingsMg = AzAPICall -uri $uri -method $method -currentTask $currentTask -getDiagnosticSettingsMg $true + } - 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 - }) - } - } + $resourceName = $resra.AssignmentScopeId.split('/')[2] + if (-not $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)")) { + $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)") = @{} - $htLogs = @{} - if ($diagnosticSetting.Properties.logs) { - foreach ($logCategory in $diagnosticSetting.properties.logs) { - if ($logCategory.enabled) { - $htLogs.($logCategory.category) = "true" } - else { - $htLogs.($logCategory.category) = "false" + if (-not $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments) { + $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments = [ordered]@{} + } + ($htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments.($resra.Assignment.RoleAssignmentId)) = $resra.Assignment } } - - 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 DataCollectionResources { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $ChildMgMgPath - ) - $currentTask = "Getting ResourceTypes for Subscription: '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/resources?`$expand=createdTime,changedTime&api-version=2021-04-01" - $method = "GET" - $resourcesSubscriptionResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + $bluePrintsAssignmentsAtScope = ($htCacheAssignmentsBlueprint).keys | Sort-Object + $bluePrintDefinitions = ($htCacheDefinitionsBlueprint).Keys | Sort-Object + $subscriptions = ($optimizedTableForPathQuery.where( { -not [string]::IsNullOrEmpty($_.subscriptionId) })) | Select-Object mgId, Subscription* | Sort-Object -Property subscriptionId -Unique + foreach ($mg in $MgIds) { - 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 - }) - } + $htJSON.ManagementGroups.($mg.MgId) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).MgId = $mg.MgId + $htJSON.ManagementGroups.($mg.MgId).MgName = $mg.MgName + $htJSON.ManagementGroups.($mg.MgId).mgParentId = $mg.mgParentId + $htJSON.ManagementGroups.($mg.MgId).mgParentName = $mg.mgParentName + $htJSON.ManagementGroups.($mg.MgId).level = $mg.level + $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).RoleAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions = [ordered]@{} - 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 + foreach ($PolDef in (($grpMgScopePolicyDefinitionsCustom).where( { $_.Name -eq $mg.MgId })).group) { + $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom.($PolDef.Id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom.($PolDef.Id) = $PolDef.Json } - } - $startSubResourceIdsThis = Get-Date - foreach ($resource in ($resourcesSubscriptionResult)) { - $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) - }) - - 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] - $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 ".*/" - }) - } - } + foreach ($PolSetDef in (($grpMgScopePolicySetDefinitionsCustom).where( { $_.Name -eq $mg.MgId })).group) { + $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom.($PolSetDef.Id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom.($PolSetDef.Id) = $PolSetDef.Json } - } - $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 - } + foreach ($PolAssignment in ($grpMgScopePolicyAssignments).where( { $_.Name -eq $mg.MgId }).group) { + $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments.($PolAssignment.Assignment.id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments.($PolAssignment.Assignment.id) = $PolAssignment.Assignment } - } -} -$funcDataCollectionResources = $function:DataCollectionResources.ToString() - -function DataCollectionResourceGroups { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName - ) - - #https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2020-06-01 - $currentTask = "Getting ResourceGroups for Subscription: '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/resourcegroups?api-version=2021-04-01" - $method = "GET" - $resourceGroupsSubscriptionResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" - - $null = $script:resourceGroupsAll.Add([PSCustomObject]@{ - subscriptionId = $scopeId - count_ = ($resourceGroupsSubscriptionResult).count - }) - - #resourceGroupTags - if ($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 - } + foreach ($RoleAssignment in ($grpMgScopeRoleAssignments).where( { $_.Name -eq $mg.MgId }).group) { + $htJSON.ManagementGroups.($mg.MgId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = $RoleAssignment.Assignment + } - #resourceAll - if ($htAllTagList.ResourceGroup.ContainsKey($tagName)) { - $script:htAllTagList.ResourceGroup."$tagName" += 1 - } - else { - $script:htAllTagList.ResourceGroup."$tagName" = 1 - } + foreach ($BlueprintDefinition in ($bluePrintDefinitions).where( { $_ -like "/providers/Microsoft.Management/managementGroups/$($mg.MgId)/*" })) { + $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions.($BlueprintDefinition) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions.($BlueprintDefinition) = $BlueprintDefinition + } - #all - if ($htAllTagList.AllScopes.ContainsKey($tagName)) { - $script:htAllTagList.AllScopes."$tagName" += 1 - } - else { - $script:htAllTagList.AllScopes."$tagName" = 1 + if (($htDiagnosticSettingsMgSub).mg.($mg.MgId)) { + foreach ($entry in ($htDiagnosticSettingsMgSub).mg.($mg.MgId).keys | Sort-Object) { + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry) = [ordered]@{} + foreach ($diagset in ($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.keys | Sort-Object) { + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Name = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticSettingName) + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Type = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticTargetType) + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).TargetId = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticTargetId) + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Settings = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticCategories) + } } } - } -} -$funcDataCollectionResourceGroups = $function:DataCollectionResourceGroups.ToString() -function DataCollectionResourceProviders { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayname - ) + foreach ($subscription in $subscriptions) { + if ($subscription.MgId -eq $mg.MgId) { - ($script:htResourceProvidersAll).($scopeId) = @{} - $currentTask = "Getting ResourceProviders for Subscription: '$($scopeDisplayname)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers?api-version=2019-10-01" - $method = "GET" - $resProvResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionName = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionQuotaId = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionState = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionTags = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionName = $subscription.Subscription + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionQuotaId = $subscription.SubscriptionQuotaId + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionState = $subscription.SubscriptionState + if ($htSubscriptionTags.($subscription.SubscriptionId)) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionTags = $htSubscriptionTags.($subscription.SubscriptionId).getEnumerator() | Sort-Object Key -CaseSensitive + } + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings = [ordered]@{} - ($script:htResourceProvidersAll).($scopeId).Providers = $resProvResult | Select-Object namespace, registrationState -} -$funcDataCollectionResourceProviders = $function:DataCollectionResourceProviders.ToString() + foreach ($PolDef in (($grpSubScopePolicyDefinitionsCustom).where( { $_.Name -eq $subscription.subscriptionId })).group) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom.($PolDef.Id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom.($PolDef.Id) = $PolDef.Json + } -function DataCollectionResourceLocks { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayname - ) + foreach ($PolSetDef in (($grpSubScopePolicySetDefinitionsCustom).where( { $_.Name -eq $subscription.subscriptionId })).group) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom.($PolSetDef.Id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom.($PolSetDef.Id) = $PolSetDef.Json + } - $currentTask = "Subscription ResourceLocks '$($scopeDisplayname)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$scopeId/providers/Microsoft.Authorization/locks?api-version=2016-09-01" - $method = "GET" - $requestSubscriptionResourceLocks = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + foreach ($PolAssignment in ($grpSubScopePolicyAssignments).where( { $_.Name -eq $subscription.subscriptionId }).group) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments.($PolAssignment.Assignment.id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments.($PolAssignment.Assignment.id) = $PolAssignment.Assignment + } - $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) { + foreach ($RoleAssignment in ($grpSubScopeRoleAssignments).where( { $_.Name -eq $subscription.subscriptionId }).group) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = $RoleAssignment.Assignment + } - $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++ - } + foreach ($BlueprintDefinition in ($bluePrintDefinitions).where( { $_ -like "/subscriptions/$($subscription.subscriptionId)/*" })) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions.($BlueprintDefinition) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions.($BlueprintDefinition) = $BlueprintDefinition } - #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 - }) - } + + foreach ($BlueprintsAssignment in ($blueprintsAssignmentsAtScope).where( { $_ -like "/subscriptions/$($subscription.subscriptionId)/*" })) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments.($BlueprintsAssignment) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments.($BlueprintsAssignment) = $BlueprintsAssignment } - #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 - }) + + if (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId)) { + foreach ($entry in ($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).keys | Sort-Object) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry) = [ordered]@{} + foreach ($diagset in ($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.keys | Sort-Object) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Name = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticSettingName) + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Type = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticTargetType) + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).TargetId = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticTargetId) + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Settings = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticCategories) + } } } - } - } - - $htTemp.SubscriptionLocksCannotDeleteCount = $locksCannotDeleteSubscriptionCount - $htTemp.SubscriptionLocksReadOnlyCount = $locksReadOnlySubscriptionCount - #resourceGroups - $resourceGroupsLocksCannotDeleteCount = ($arrayResourceGroupsCannotDeleteLock).Count - $htTemp.ResourceGroupsLocksCannotDeleteCount = $resourceGroupsLocksCannotDeleteCount - $resourceGroupsLocksReadOnlyCount = ($arrayResourceGroupsReadOnlyLock).Count - $htTemp.ResourceGroupsLocksReadOnlyCount = $resourceGroupsLocksReadOnlyCount - $htTemp.ResourceGroupsLocksCannotDelete = $arrayResourceGroupsCannotDeleteLock + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + if (-not $JsonExportExcludeResourceGroups) { + $htTemp = @{} + if (-not $htTemp.ResourceGroups) { + $htTemp.ResourceGroups = @{} + } - #resources - $resourcesLocksCannotDeleteCount = ($arrayResourcesCannotDeleteLock).Count - $htTemp.ResourcesLocksCannotDeleteCount = $resourcesLocksCannotDeleteCount + if ($htSubRGPolicyAssignments.($subscription.subscriptionId)) { + foreach ($rgpa in $htSubRGPolicyAssignments.($subscription.subscriptionId).PolicyAssignments) { + $rgName = ($rgpa.AssignmentScopeId).split('/')[1] + if (-not $htTemp.ResourceGroups.($rgName)) { + $htTemp.ResourceGroups.($rgName) = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rgName).PolicyAssignments) { + $htTemp.ResourceGroups.($rgName).PolicyAssignments = [ordered]@{} + } + $htTemp.ResourceGroups.($rgName).PolicyAssignments.($rgpa.Assignment.id) = $rgpa.Assignment + } + } + } + } - $resourcesLocksReadOnlyCount = ($arrayResourcesReadOnlyLock).Count - $htTemp.ResourcesLocksReadOnlyCount = $resourcesLocksReadOnlyCount - $htTemp.ResourcesLocksCannotDelete = $arrayResourcesCannotDeleteLock + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + if (-not $JsonExportExcludeResourceGroups) { + if (-not $htTemp) { + $htTemp = @{} + } + if (-not $htTemp.ResourceGroups) { + $htTemp.ResourceGroups = @{} + } + if ($htSubRGRoleAssignments.($subscription.subscriptionId)) { + foreach ($rgra in $htSubRGRoleAssignments.($subscription.subscriptionId).RoleAssignments) { + $rgName = ($rgra.AssignmentScopeId).split('/')[1] + if (-not $htTemp.ResourceGroups.($rgName)) { + $htTemp.ResourceGroups.($rgName) = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rgName).RoleAssignments) { + $htTemp.ResourceGroups.($rgName).RoleAssignments = [ordered]@{} + } + $htTemp.ResourceGroups.($rgName).RoleAssignments.($rgra.Assignment.RoleAssignmentId) = $rgra.Assignment + } + } + # + if (-not $JsonExportExcludeResources) { + if (-not $htTemp.ResourceGroups) { + $htTemp.ResourceGroups = @{} + } + if ($htSubResRoleAssignments.($subscription.subscriptionId)) { + foreach ($rg in $htSubResRoleAssignments.($subscription.subscriptionId).keys) { + foreach ($res in $htSubResRoleAssignments.($subscription.subscriptionId).($rg).Keys | Sort-Object) { + $rgName = ($resra.AssignmentScopeId).split('/')[1] + if (-not $htTemp.ResourceGroups.($rg)) { + $htTemp.ResourceGroups.($rg) = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rg).Resources) { + $htTemp.ResourceGroups.($rg).Resources = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rg).Resources.($res)) { + $htTemp.ResourceGroups.($rg).Resources.($res) = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments) { + $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments = [ordered]@{} + } + $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments = $htSubResRoleAssignments.($subscription.subscriptionId).($rg).($res).RoleAssignments + } + } + } + } + } + } - $script:htResourceLocks.($scopeId) = $htTemp + if ($htTemp) { + $sortedHt = [ordered]@{} + foreach ($key in ($htTemp.ResourceGroups.keys | Sort-Object)) { + $sortedHt.($key) = $htTemp.ResourceGroups.($key) + } + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).ResourceGroups = $sortedHt + $htTemp = $null + $sortedHt = $null + } + } + } } -} -$funcDataCollectionResourceLocks = $function:DataCollectionResourceLocks.ToString() -function DataCollectionTags { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName - ) + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + if ($ManagementGroupsOnly) { + $JSONPath = "JSON_ManagementGroupsOnly_$($ManagementGroupId)" + } + else { + $JSONPath = "JSON_$($ManagementGroupId)" + } - $currentTask = "Subscription Tags '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$scopeId/providers/Microsoft.Resources/tags/default?api-version=2020-06-01" - $method = "GET" - $requestSubscriptionTags = AzAPICall -uri $uri -method $method -currentTask $currentTask -listenOn "Content" -caller "CustomDataCollection" + if (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)") { + Write-Host ' Cleaning old state (Pipeline only)' + Remove-Item -Recurse -Force "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)" + } + } + else { + if ($ManagementGroupsOnly) { + $JSONPath = "JSON_ManagementGroupsOnly_$($ManagementGroupId)_$($fileTimestamp)" + } + else { + $JSONPath = "JSON_$($ManagementGroupId)_$($fileTimestamp)" + } + Write-Host " Creating new state ($($JSONPath)) (local only))" + } - $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)" + $null = new-item -Name $JSONPath -ItemType directory -path $outputPath - ($script:htSubscriptionTags).($scopeId).($tag.Name) = $tag.Value - $tagName = $tag.Name + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + "The directory '$($JSONPath)' will be rebuilt during the AzDO Pipeline run. __Do not save any files in this directory, files and folders will be deleted!__" | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)ReadMe_important.md" -Encoding utf8 + } - #subscription - if ($htSubscriptionTagList.($scopeId).Subscription.ContainsKey($tagName)) { - $script:htSubscriptionTagList.($scopeId).Subscription."$tagName" += 1 - } - else { - $script:htSubscriptionTagList.($scopeId).Subscription."$tagName" = 1 - } + $null = new-item -Name "$($JSONPath)$($DirectorySeparatorChar)Definitions" -ItemType directory -path $outputPath - #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 " + + $htJSON.RoleDefinitions = [ordered]@{} + $pathRoleDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)RoleDefinitions" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitions)")) { + $null = new-item -Name $pathRoleDefinitions -ItemType directory -path $outputPath + $pathRoleDefinitionCustom = "$($pathRoleDefinitions)$($DirectorySeparatorChar)Custom" + $pathRoleDefinitionBuiltIn = "$($pathRoleDefinitions)$($DirectorySeparatorChar)BuiltIn" + $null = new-item -Name "$($pathRoleDefinitionCustom)" -ItemType directory -path $outputPath + $null = new-item -Name "$($pathRoleDefinitionBuiltIn)" -ItemType directory -path $outputPath } - else { - $subscriptionTagsCount = 0 - $subscriptionTags = "none" + + if (($htCacheDefinitionsRole).Keys.Count -gt 0) { + foreach ($roleDefinition in ($htCacheDefinitionsRole).Keys.where( { ($htCacheDefinitionsRole).($_).IsCustom }) | Sort-Object) { + $htJSON.RoleDefinitions.($roleDefinition) = ($htCacheDefinitionsRole).($roleDefinition).Json.properties + $jsonConverted = ($htCacheDefinitionsRole).($roleDefinition).Json.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitionCustom)$($DirectorySeparatorChar)$(removeInvalidFileNameChars ($htCacheDefinitionsRole).($roleDefinition).Name) ($(($htCacheDefinitionsRole).($roleDefinition).Id)).json" -Encoding utf8 + } + foreach ($roleDefinition in ($htCacheDefinitionsRole).Keys.where( { -not ($htCacheDefinitionsRole).($_).IsCustom })) { + $jsonConverted = ($htCacheDefinitionsRole).($roleDefinition).Json | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitionBuiltIn)$($DirectorySeparatorChar)$(removeInvalidFileNameChars ($htCacheDefinitionsRole).($roleDefinition).Name ) ($(($htCacheDefinitionsRole).($roleDefinition).Id)).json" -Encoding utf8 + } } - $htSubscriptionTagsReturn = @{} - $htSubscriptionTagsReturn.subscriptionTagsCount = $subscriptionTagsCount - $htSubscriptionTagsReturn.subscriptionTags = $subscriptionTags - return $htSubscriptionTagsReturn -} -$funcDataCollectionTags = $function:DataCollectionTags.ToString() -function DataCollectionPolicyComplianceStates { - [CmdletBinding()]Param( - [string]$TargetMgOrSub, - [string]$scopeId, - [string]$scopeDisplayName - ) + $pathPolicyDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicyDefinitions)")) { + $null = new-item -Name $pathPolicyDefinitions -ItemType directory -path $outputPath + $pathPolicyDefinitionBuiltIn = "$($pathPolicyDefinitions)$($DirectorySeparatorChar)BuiltIn" + $null = new-item -Name "$($pathPolicyDefinitionBuiltIn)" -ItemType directory -path $outputPath + } + if (($htCacheDefinitionsPolicy).Keys.Count -gt 0) { + foreach ($policyDefinition in ($htCacheDefinitionsPolicy).Keys.where( { ($htCacheDefinitionsPolicy).($_).Type -eq 'BuiltIn' })) { + $jsonConverted = ($htCacheDefinitionsPolicy).($policyDefinition).Json.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicyDefinitionBuiltIn)$($DirectorySeparatorChar)$(removeInvalidFileNameChars ($htCacheDefinitionsPolicy).($policyDefinition).displayName) ($(($htCacheDefinitionsPolicy).($policyDefinition).Json.name)).json" -Encoding utf8 + } + } - $currentTask = "Policy Compliance $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" - if ($TargetMgOrSub -eq "Sub") { $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.PolicyInsights/policyStates/latest/summarize?api-version=2019-10-01" } - if ($TargetMgOrSub -eq "MG") { $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.PolicyInsights/policyStates/latest/summarize?api-version=2019-10-01" } - $method = "POST" - $policyComplianceResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" -getPolicyCompliance $true - - if ($policyComplianceResult -eq "ResponseTooLarge") { - if ($TargetMgOrSub -eq "Sub") { ($script:htCachePolicyComplianceResponseTooLargeSUB).($scopeId) = @{} } - if ($TargetMgOrSub -eq "MG") { - ($script:htCachePolicyComplianceResponseTooLargeMG).($scopeId) = @{} + $pathPolicySetDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicySetDefinitions)")) { + $null = new-item -Name $pathPolicySetDefinitions -ItemType directory -path $outputPath + $pathPolicySetDefinitionBuiltIn = "$($pathPolicySetDefinitions)$($DirectorySeparatorChar)BuiltIn" + $null = new-item -Name "$($pathPolicySetDefinitionBuiltIn)" -ItemType directory -path $outputPath + } + if (($htCacheDefinitionsPolicySet).Keys.Count -gt 0) { + foreach ($policySetDefinition in ($htCacheDefinitionsPolicySet).Keys.where( { ($htCacheDefinitionsPolicySet).($_).Type -eq 'BuiltIn' })) { + $jsonConverted = ($htCacheDefinitionsPolicySet).($policySetDefinition).Json.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicySetDefinitionBuiltIn)$($DirectorySeparatorChar)$(removeInvalidFileNameChars ($htCacheDefinitionsPolicySet).($policySetDefinition).displayName) ($(($htCacheDefinitionsPolicySet).($policySetDefinition).Json.name)).json" -Encoding utf8 } } - 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 } + $endBuildHt = Get-Date + Write-Host " ht for JSON creation duration: $((NEW-TIMESPAN -Start $startBuildHt -End $endBuildHt).TotalSeconds) seconds" - } - 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 } + $startBuildJSON = Get-Date + Write-Host ' Build JSON' - } - 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 - ) + $null = new-item -Name "$($JSONPath)$($DirectorySeparatorChar)Tenant" -ItemType directory -path $outputPath - if ($htParameters.NoMDfCSecureScore -eq $false) { - $currentTask = "Microsoft Defender for Cloud Secure Score Sub: '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$scopeId/providers/Microsoft.Security/securescores?api-version=2020-01-01" - $method = "GET" - $subASCSecureScoreResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + $htTree = [ordered]@{} + $htTree.'Tenant' = [ordered] @{} + $htTree.Tenant.TenantId = $azAPICallConf['checkContext'].Tenant.Id + $htTree.Tenant.RoleAssignments = [ordered]@{} + foreach ($RoleAssignment in ($grpTenantScopeRoleAssignments).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } }) { - if (($subASCSecureScoreResult).count -gt 0) { - $subscriptionASCSecureScore = "$($subASCSecureScoreResult.properties.score.current) of $($subASCSecureScoreResult.properties.score.max) points" + $htTree.Tenant.RoleAssignments.$($RoleAssignment.Assignment.RoleAssignmentId) = [ordered]@{} + $htTree.Tenant.RoleAssignments.$($RoleAssignment.Assignment.RoleAssignmentId) = $RoleAssignment.Assignment + + if ($RoleAssignment.Assignment.PIM -eq 'true') { + $pim = 'PIM_' } else { - $subscriptionASCSecureScore = "n/a" + $pim = '' } + $jsonConverted = ($RoleAssignment.Assignment | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)Tenant$($DirectorySeparatorChar)ra_$($RoleAssignment.Assignment.ObjectType)_$($pim)$($RoleAssignment.Assignment.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)RoleAssignments$($DirectorySeparatorChar)Tenant" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($RoleAssignment.Assignment.ObjectType)_$($pim)$($RoleAssignment.Assignment.RoleAssignmentId -replace '.*/').json" -Encoding utf8 } - else { - $subscriptionASCSecureScore = "excluded (-NoMDfCSecureScore $($htParameters.NoMDfCSecureScore))" - } - return $subscriptionASCSecureScore -} -$funcDataCollectionASCSecureScoreSub = $function:DataCollectionASCSecureScoreSub.ToString() -function DataCollectionBluePrintDefinitionsMG { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $hierarchyLevel, - $mgParentId, - $mgParentName, - $mgAscSecureScoreResult - ) + $htTree.'Tenant'.'ManagementGroups' = [ordered] @{} + $json = $htTree.'Tenant' - $currentTask = "Blueprint definitions MG '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Blueprint/blueprints?api-version=2018-11-01-preview" - $method = "GET" - $scopeBlueprintDefinitionResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)Assignments")) { + $null = new-item -Name "$($JSONPath)$($DirectorySeparatorChar)Assignments" -ItemType directory -path $outputPath + } - $addRowToTableDone = $false - if (($scopeBlueprintDefinitionResult).count -gt 0) { - foreach ($blueprint in $scopeBlueprintDefinitionResult) { + buildTree -mgId $ManagementGroupId -json $json -prnt "$($JSONPath)$($DirectorySeparatorChar)Tenant" - if (-not $($htCacheDefinitionsBlueprint).($blueprint.Id)) { - ($script:htCacheDefinitionsBlueprint).($blueprint.Id) = @{} - } + $htTree.'Tenant'.'CustomRoleDefinitions' = $htJSON.RoleDefinitions - $blueprintName = $blueprint.name - $blueprintId = $blueprint.Id - $blueprintDisplayName = $blueprint.properties.displayName - $blueprintDescription = $blueprint.properties.description - $blueprintScoped = "/providers/Microsoft.Management/managementGroups/$($scopeId)" + Write-Host " Exporting Tenant JSON '$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)$($fileName).json'" + $htTree | ConvertTo-JSON -Depth 99 | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)$($fileName).json" -Encoding utf8 -Force - $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 - } - } + $endBuildJSON = Get-Date + Write-Host " Building JSON duration: $((NEW-TIMESPAN -Start $startBuildJSON -End $endBuildJSON).TotalSeconds) seconds" - $returnObject = @{} - if ($addRowToTableDone) { - $returnObject."addRowToTableDone" = @{} - } - return $returnObject + $endJSON = Get-Date + Write-Host "Creating Hierarchy JSON duration: $((NEW-TIMESPAN -Start $startJSON -End $endJSON).TotalSeconds) seconds" } -$funcDataCollectionBluePrintDefinitionsMG = $function:DataCollectionBluePrintDefinitionsMG.ToString() - -function DataCollectionBluePrintDefinitionsSub { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $hierarchyLevel, - $childMgDisplayName, - $childMgId, - $childMgParentId, - $childMgParentName, - $mgAscSecureScoreResult, - $subscriptionQuotaId, - $subscriptionState, - $subscriptionASCSecureScore, - $subscriptionTags, - $subscriptionTagsCount - ) - - $currentTask = "Blueprint definitions Sub '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Blueprint/blueprints?api-version=2018-11-01-preview" - $method = "GET" - $scopeBlueprintDefinitionResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" +function buildMD { + Write-Host 'Building Markdown' + #test + Write-Host 'htParameters.onAzureDevOpsOrGitHubActions:' $azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions + Write-Host 'htParameters.codeRunPlatform:' $azAPICallConf['htParameters'].codeRunPlatform + $startBuildMD = Get-Date + $script:arrayMgs = [System.Collections.ArrayList]@() + $script:arraySubs = [System.Collections.ArrayList]@() + $script:arraySubsOos = [System.Collections.ArrayList]@() + $markdown = $null + $script:markdownhierarchyMgs = $null + $script:markdownhierarchySubs = $null + $script:markdownTable = $null + + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions -eq $true) { + if ($azAPICallConf['htParameters'].onAzureDevOps -eq $true) { + $markdown += @" +# AzGovViz - Management Group Hierarchy - $addRowToTableDone = $false - if (($scopeBlueprintDefinitionResult).count -gt 0) { - foreach ($blueprint in $scopeBlueprintDefinitionResult) { +## Hierarchy Diagram (Mermaid) - if (-not $($htCacheDefinitionsBlueprint).($blueprint.Id)) { - ($script:htCacheDefinitionsBlueprint).($blueprint.Id) = @{} - } +::: mermaid + graph $($MermaidDirection.ToUpper());`n +"@ + } + if ($azAPICallConf['htParameters'].onGitHubActions -eq $true) { + $marks = '```' + $markdown += @" +# AzGovViz - Management Group Hierarchy - $blueprintName = $blueprint.name - $blueprintId = $blueprint.Id - $blueprintDisplayName = $blueprint.properties.displayName - $blueprintDescription = $blueprint.properties.description - $blueprintScoped = "/subscriptions/$($scopeId)" +## Hierarchy Diagram (Mermaid) - $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 +$($marks)mermaid + graph $($MermaidDirection.ToUpper());`n +"@ } - } - $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 - ) + else { + $markdown += @" +# AzGovViz - Management Group Hierarchy - $currentTask = "Blueprint assignments '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$scopeId/providers/Microsoft.Blueprint/blueprintAssignments?api-version=2018-11-01-preview" - $method = "GET" - $subscriptionBlueprintAssignmentsResult = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" +$executionDateTimeInternationalReadable ($currentTimeZone) - $addRowToTableDone = $false - if (($subscriptionBlueprintAssignmentsResult).count -gt 0) { - foreach ($subscriptionBlueprintAssignment in $subscriptionBlueprintAssignmentsResult) { +## Hierarchy Diagram (Mermaid) - if (-not ($htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id)) { - ($script:htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id) = @{} - ($script:htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id) = $subscriptionBlueprintAssignment - } +::: mermaid + graph $($MermaidDirection.ToUpper());`n +"@ + } - 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/.*", "" - } + processDiagramMermaid - $currentTask = " Blueprint definitions related to Blueprint assignments '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)$($blueprintScope)/providers/Microsoft.Blueprint/blueprints/$($blueprintName)?api-version=2018-11-01-preview" - $method = "GET" - $subscriptionBlueprintDefinitionResult = AzAPICall -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 - } + $markdown += @" +$markdownhierarchyMgs +$markdownhierarchySubs + classDef mgr fill:#D9F0FF,stroke:#56595E,color:#000000,stroke-width:1px; + classDef subs fill:#EEEEEE,stroke:#56595E,color:#000000,stroke-width:1px; +"@ - $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 - } + if (($arraySubsOos).count -gt 0) { + $markdown += @' + classDef subsoos fill:#FFCBC7,stroke:#56595E,color:#000000,stroke-width:1px; +'@ } - $returnObject = @{} - if ($addRowToTableDone) { - $returnObject."addRowToTableDone" = @{} + $markdown += @" + classDef mgrprnts fill:#FFFFFF,stroke:#56595E,color:#000000,stroke-width:1px; + class $(($arrayMgs | Sort-Object -unique) -join ',') mgr; + class $(($arraySubs | Sort-Object -unique) -join ',') subs; +"@ + + if (($arraySubsOos).count -gt 0) { + $markdown += @" + class $(($arraySubsOos | Sort-Object -unique) -join ',') subsoos; +"@ } - return $returnObject -} -$funcDataCollectionBluePrintAssignmentsSub = $function:DataCollectionBluePrintAssignmentsSub.ToString() -function DataCollectionPolicyExemptions { - [CmdletBinding()]Param( - [string]$TargetMgOrSub, - [string]$scopeId, - [string]$scopeDisplayName - ) + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions -eq $true) { + if ($azAPICallConf['htParameters'].onAzureDevOps -eq $true) { + $markdown += @" +class $mermaidprnts mgrprnts; +::: - $currentTask = "Policy exemptions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" - if ($TargetMgOrSub -eq "Sub") { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyExemptions?api-version=2020-07-01-preview" +"@ + } + if ($azAPICallConf['htParameters'].onGitHubActions -eq $true) { +` + $marks = '```' + $markdown += @" +class $mermaidprnts mgrprnts; +$marks + +"@ + } } - if ($TargetMgOrSub -eq "MG") { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/policyExemptions?api-version=2020-07-01-preview&`$filter=atScope()" + else { + $markdown += @" +class $mermaidprnts mgrprnts; +::: + +"@ } - $method = "GET" - $requestPolicyExemptionAPI = AzAPICall -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 - } + $markdown += @" +## Summary +`n +"@ + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + $markdown += @" +Total Management Groups: $totalMgCount (depth $mgDepth)\`n +"@ + + if (($arraySubsOos).count -gt 0) { + $markdown += @" +Total Subscriptions: $totalSubIncludedAndExcludedCount ($totalSubOutOfScopeCount out-of-scope)\`n +"@ + } + else { + $markdown += @" +Total Subscriptions: $totalSubIncludedAndExcludedCount\`n +"@ } - } -} -$funcDataCollectionPolicyExemptions = $function:DataCollectionPolicyExemptions.ToString() -function DataCollectionPolicyDefinitions { - [CmdletBinding()]Param( - [string]$TargetMgOrSub, - [string]$scopeId, - [string]$scopeDisplayName - ) + $markdown += @" +Total Custom Policy definitions: $tenantCustomPoliciesCount\ +Total Custom PolicySet definitions: $tenantCustompolicySetsCount\ +Total Policy assignments: $($totalPolicyAssignmentsCount)\ +Total Policy assignments ManagementGroups $($totalPolicyAssignmentsCountMg)\ +Total Policy assignments Subscriptions $($totalPolicyAssignmentsCountSub)\ +Total Policy assignments ResourceGroups: $($totalPolicyAssignmentsCountRg)\ +Total Custom Role definitions: $totalRoleDefinitionsCustomCount\ +Total Role assignments: $totalRoleAssignmentsCount\ +Total Role assignments (Tenant): $totalRoleAssignmentsCountTen\ +Total Role assignments (ManagementGroups): $totalRoleAssignmentsCountMG\ +Total Role assignments (Subscriptions): $totalRoleAssignmentsCountSub\ +Total Role assignments (ResourceGroups and Resources): $totalRoleAssignmentsResourceGroupsAndResourcesCount\ +Total Blueprint definitions: $totalBlueprintDefinitionsCount\ +Total Blueprint assignments: $totalBlueprintAssignmentsCount\ +Total Resources: $totalResourceCount\ +Total Resource Types: $totalResourceTypesCount +"@ - $currentTask = "Policy definitions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" - if ($TargetMgOrSub -eq "Sub") { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'" - } - if ($TargetMgOrSub -eq "MG") { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementgroups/$($scopeId)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'" } - $method = "GET" - $requestPolicyDefinitionAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $true) { + $mgsDetails = ($optimizedTableForPathQueryMg | Select-Object Level, MgId -Unique) + $mgDepth = ($mgsDetails.Level | Measure-Object -maximum).Maximum + $totalMgCount = ($mgsDetails).count + $totalSubCount = ($optimizedTableForPathQuerySub).count - $scopePolicyDefinitions = $requestPolicyDefinitionAPI.where( { $_.properties.policyType -eq "custom" } ) + $markdown += @" +Total Management Groups: $totalMgCount (depth $mgDepth)\ +Total Subscriptions: $totalSubCount +"@ - 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 - } - } + $markdown += @" +`n +## Hierarchy Table - if ($doIt) { - if (($scopePolicyDefinition.Properties.description).length -eq 0) { - $policyDefinitionDescription = "no description given" - } - else { - $policyDefinitionDescription = $scopePolicyDefinition.Properties.description - } +| **MgLevel** | **MgName** | **MgId** | **MgParentName** | **MgParentId** | **SubName** | **SubId** | +|-------------|-------------|-------------|-------------|-------------|-------------|-------------| +$markdownTable +"@ - $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 = "" - } - $htTemp.DisplayName = $($scopePolicyDefinition.Properties.displayname) - $htTemp.Description = $($policyDefinitionDescription) - $htTemp.Type = $($scopePolicyDefinition.Properties.policyType) - $htTemp.Category = $($scopePolicyDefinition.Properties.metadata.category) - $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 + $markdown | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).md" -Encoding utf8 -Force + $endBuildMD = Get-Date + Write-Host "Building Markdown total duration: $((NEW-TIMESPAN -Start $startBuildMD -End $endBuildMD).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildMD -End $endBuildMD).TotalSeconds) seconds)" +} +function buildPolicyAllJSON { + Write-Host 'Creating PolicyAll JSON' + $startPolicyAllJSON = Get-Date + $htPolicyAndPolicySet = [ordered]@{} + $htPolicyAndPolicySet.Policy = [ordered]@{} + $htPolicyAndPolicySet.PolicySet = [ordered]@{} + foreach ($policy in ($tenantPoliciesDetailed | Sort-Object -Property Type, ScopeMGLevel, PolicyDefinitionId)) { + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()) = [ordered]@{} + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyType = $policy.Type + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).ScopeMGLevel = $policy.ScopeMGLevel + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).Scope = $policy.Scope + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).ScopeId = $policy.scopeId + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyDisplayName = $policy.PolicyDisplayName + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyDefinitionName = $policy.PolicyDefinitionName + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyDefinitionId = $policy.PolicyDefinitionId + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyEffect = $policy.PolicyEffect + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyCategory = $policy.PolicyCategory + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UniqueAssignmentsCount = $policy.UniqueAssignmentsCount + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UniqueAssignments = $policy.UniqueAssignments + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UsedInPolicySetsCount = $policy.UsedInPolicySetsCount + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UsedInPolicySets = $policy.UsedInPolicySet4JSON + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).CreatedOn = $policy.CreatedOn + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).CreatedBy = $policy.CreatedByJson + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UpdatedOn = $policy.UpdatedOn + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UpdatedBy = $policy.UpdatedByJson + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).JSON = $policy.Json + } + foreach ($policySet in ($tenantPolicySetsDetailed | Sort-Object -Property Type, ScopeMGLevel, PolicySetDefinitionId)) { + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()) = [ordered]@{} + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetType = $policy.Type + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).ScopeMGLevel = $policySet.ScopeMGLevel + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).Scope = $policySet.Scope + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).ScopeId = $policySet.scopeId + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetDisplayName = $policySet.PolicySetDisplayName + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetDefinitionName = $policySet.PolicySetDefinitionName + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetDefinitionId = $policySet.PolicySetDefinitionId + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetCategory = $policySet.PolicySetCategory + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).UniqueAssignmentsCount = $policySet.UniqueAssignmentsCount + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).UniqueAssignments = $policySet.UniqueAssignments + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PoliciesUsedCount = $policySet.PoliciesUsedCount + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PoliciesUsed = $policySet.PoliciesUsed4JSON + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).CreatedOn = $policySet.CreatedOn + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).CreatedBy = $policySet.CreatedByJson + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).UpdatedOn = $policySet.UpdatedOn + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).UpdatedBy = $policySet.UpdatedByJson + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).JSON = $policySet.Json + } + Write-Host " Exporting PolicyAll JSON '$($outputPath)$($DirectorySeparatorChar)$($fileName)_PolicyAll.json'" + $htPolicyAndPolicySet | ConvertTo-JSON -Depth 99 | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_PolicyAll.json" -Encoding utf8 -Force + + $endPolicyAllJSON = Get-Date + Write-Host "Creating PolicyAll JSON duration: $((NEW-TIMESPAN -Start $startPolicyAllJSON -End $endPolicyAllJSON).TotalSeconds) seconds" +} +function buildTree($mgId, $prnt) { + $getMg = $arrayEntitiesFromAPI.where( { $_.type -eq 'Microsoft.Management/managementGroups' -and $_.name -eq $mgId }) + $childrenManagementGroups = $arrayEntitiesFromAPI.where( { $_.type -eq 'Microsoft.Management/managementGroups' -and $_.properties.parent.id -eq "/providers/Microsoft.Management/managementGroups/$($getMg.Name)" }) + $mgNameValid = removeInvalidFileNameChars $getMg.Name + $mgDisplayNameValid = removeInvalidFileNameChars $getMg.properties.displayName + $prntx = "$($prnt)$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)")) { + $null = new-item -Name $prntx -ItemType directory -path $outputPath + } + + if (-not $json.'ManagementGroups') { + $json.'ManagementGroups' = [ordered]@{} + } + $json = $json.'ManagementGroups'.($getMg.Name) = [ordered]@{} + foreach ($mgCap in $htJSON.ManagementGroups.($getMg.Name).keys) { + $json.$mgCap = $htJSON.ManagementGroups.($getMg.Name).$mgCap + if ($mgCap -eq 'PolicyDefinitionsCustom') { + $mgCapShort = 'pd' + foreach ($pdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($pdc) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 } - if ($scopePolicyDefinition.Properties.metadata.preview -eq $true -or $scopePolicyDefinition.Properties.displayname -like "``[*Preview``]*") { - $htTemp.Preview = $scopePolicyDefinition.Properties.metadata.preview + } + if ($mgCap -eq 'PolicySetDefinitionsCustom') { + $mgCapShort = 'psd' + foreach ($psdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($psdc) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 } - else { - $htTemp.Preview = $false + } + if ($mgCap -eq 'PolicyAssignments') { + $mgCapShort = 'pa' + foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($pa) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($mgCap)$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 } - #effects - if ($scopePolicyDefinition.properties.parameters.effect.defaultvalue) { - $htTemp.effectDefaultValue = $scopePolicyDefinition.properties.parameters.effect.defaultvalue - if ($scopePolicyDefinition.properties.parameters.effect.allowedValues) { - $htTemp.effectAllowedValue = $scopePolicyDefinition.properties.parameters.effect.allowedValues -join "," + } + #marker + if ($mgCap -eq 'RoleAssignments') { + $mgCapShort = 'ra' + foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($ra) + if ($hlp.PIM -eq 'true') { + $pim = 'PIM_' } else { - $htTemp.effectAllowedValue = "n/a" + $pim = '' + } + $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($mgCap)$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + } + + if ($mgCap -eq 'Subscriptions') { + foreach ($sub in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $subNameValid = removeInvalidFileNameChars $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).SubscriptionName + $subFolderName = "$($prntx)$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + $null = new-item -Name $subFolderName -ItemType directory -path $outputPath + foreach ($subCap in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).Keys) { + if ($subCap -eq 'PolicyDefinitionsCustom') { + $subCapShort = 'pd' + foreach ($pdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($pdc) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + if ($subCap -eq 'PolicySetDefinitionsCustom') { + $subCapShort = 'psd' + foreach ($psdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($psdc) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + if ($subCap -eq 'PolicyAssignments') { + $subCapShort = 'pa' + foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($pa) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($subCap)$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + #marker + if ($subCap -eq 'RoleAssignments') { + $subCapShort = 'ra' + foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($ra) + if ($hlp.PIM -eq 'true') { + $pim = 'PIM_' + } + else { + $pim = '' + } + $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($pim)$($hlp.ObjectType)_$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($subCap)$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + } + + #RG Pol + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + if (-not $JsonExportExcludeResourceGroups) { + if ($subCap -eq 'ResourceGroups') { + foreach ($rg in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys | Sort-Object) { + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)")) { + $null = new-item -Name "$($subFolderName)$($DirectorySeparatorChar)$($rg)" -ItemType directory -path "$($outputPath)" + } + foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).PolicyAssignments.keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).PolicyAssignments.($pa) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)pa_$($displayName) ($($hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)PolicyAssignments$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))$($DirectorySeparatorChar)$($rg)" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($($hlp.name)).json" -Encoding utf8 + } + } + } + } + } + + #RG RoleAss + #marker + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + if (-not $JsonExportExcludeResourceGroups) { + if ($subCap -eq 'ResourceGroups') { + foreach ($rg in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys | Sort-Object) { + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)")) { + $null = new-item -Name "$($subFolderName)$($DirectorySeparatorChar)$($rg)" -ItemType directory -path "$($outputPath)" + } + foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).RoleAssignments.keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).RoleAssignments.($ra) + if ($hlp.PIM -eq 'true') { + $pim = 'PIM_' + } + else { + $pim = '' + } + $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)ra_$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)RoleAssignments$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))$($DirectorySeparatorChar)$($rg)" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + #res + if (-not $JsonExportExcludeResources) { + + foreach ($res in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).Resources.keys) { + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)$($res)")) { + $null = new-item -Name "$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)$($res)" -ItemType directory -path "$($outputPath)" + } + foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).Resources.($res).RoleAssignments.keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).Resources.($res).RoleAssignments.($ra) + if ($hlp.PIM -eq 'true') { + $pim = 'PIM_' + } + else { + $pim = '' + } + $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)$($res)$($DirectorySeparatorChar)ra_$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)RoleAssignments$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)$($res)" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + } + } + } + } + } + } } - $htTemp.effectFixedValue = "n/a" } - else { - if ($scopePolicyDefinition.properties.parameters.policyEffect.defaultValue) { - $htTemp.effectDefaultValue = $scopePolicyDefinition.properties.parameters.policyEffect.defaultvalue - if ($scopePolicyDefinition.properties.parameters.policyEffect.allowedValues) { - $htTemp.effectAllowedValue = $scopePolicyDefinition.properties.parameters.policyEffect.allowedValues -join "," + } + } + + if ($childrenManagementGroups.Count -eq 0) { + $json.'ManagementGroups' = @{} + } + else { + foreach ($childMg in $childrenManagementGroups) { + buildTree -mgId $childMg.Name -json $json -prnt $prntx + } + } +} +function cacheBuiltIn { + $startDefinitionsCaching = Get-Date + Write-Host 'Caching built-in Policy and RBAC Role definitions' + + $arrayBuiltInCaching = @('PolicyDefinitions', 'PolicySetDefinitions', 'RoleDefinitions') + + $arrayBuiltInCaching | ForEach-Object -parallel { + + $builtInCapability = $_ + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $htCacheDefinitionsPolicy = $using:htCacheDefinitionsPolicy + $htCacheDefinitionsPolicySet = $using:htCacheDefinitionsPolicySet + $htCacheDefinitionsRole = $using:htCacheDefinitionsRole + $htRoleDefinitionIdsUsedInPolicy = $using:htRoleDefinitionIdsUsedInPolicy + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + + if ($builtInCapability -eq 'PolicyDefinitions') { + $currentTask = 'Caching built-in Policy definitions' + #Write-Host " $currentTask" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'BuiltIn'" + $method = 'GET' + $requestPolicyDefinitionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask + + Write-Host " $($requestPolicyDefinitionAPI.Count) built-in Policy definitions returned" + $builtinPolicyDefinitions = $requestPolicyDefinitionAPI.where( { $_.properties.policyType -eq 'BuiltIn' } ) + + foreach ($builtinPolicyDefinition in $builtinPolicyDefinitions) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()) = @{} + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Id = ($builtinPolicyDefinition.Id).ToLower() + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeMGLevel = '' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Scope = 'n/a' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeMgSub = 'n/a' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeId = 'n/a' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).DisplayName = $builtinPolicyDefinition.Properties.displayname + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Description = $builtinPolicyDefinition.Properties.description + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Type = $builtinPolicyDefinition.Properties.policyType + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Category = $builtinPolicyDefinition.Properties.metadata.category + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).PolicyDefinitionId = ($builtinPolicyDefinition.Id).ToLower() + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).LinkToAzAdvertizer = "$($builtinPolicyDefinition.Properties.displayname)" + if ($builtinPolicyDefinition.Properties.metadata.deprecated -eq $true -or $builtinPolicyDefinition.Properties.displayname -like "``[Deprecated``]*") { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Deprecated = $builtinPolicyDefinition.Properties.metadata.deprecated + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Deprecated = $false + } + if ($builtinPolicyDefinition.Properties.metadata.preview -eq $true -or $builtinPolicyDefinition.Properties.displayname -like "``[*Preview``]*") { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Preview = $builtinPolicyDefinition.Properties.metadata.preview + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Preview = $false + } + #effects + if ($builtinPolicyDefinition.properties.parameters.effect.defaultvalue) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = $builtinPolicyDefinition.properties.parameters.effect.defaultvalue + if ($builtinPolicyDefinition.properties.parameters.effect.allowedValues) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = $builtinPolicyDefinition.properties.parameters.effect.allowedValues -join ',' } else { - $htTemp.effectAllowedValue = "n/a" + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = 'n/a' } - $htTemp.effectFixedValue = "n/a" + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = 'n/a' } else { - $htTemp.effectFixedValue = $scopePolicyDefinition.Properties.policyRule.then.effect - $htTemp.effectDefaultValue = "n/a" - $htTemp.effectAllowedValue = "n/a" + if ($builtinPolicyDefinition.properties.parameters.policyEffect.defaultValue) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = $builtinPolicyDefinition.properties.parameters.policyEffect.defaultvalue + if ($builtinPolicyDefinition.properties.parameters.policyEffect.allowedValues) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = $builtinPolicyDefinition.properties.parameters.policyEffect.allowedValues -join ',' + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = 'n/a' + } + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = 'n/a' + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = $builtinPolicyDefinition.Properties.policyRule.then.effect + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = 'n/a' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = 'n/a' + } } - } - $htTemp.Json = $scopePolicyDefinition - ($script:htCacheDefinitionsPolicy).($hlpPolicyDefinitionId) = $htTemp - + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Json = $builtinPolicyDefinition - 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 [string]::IsNullOrEmpty($builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds)) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).RoleDefinitionIds = $builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds + foreach ($roledefinitionId in $builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds) { if (-not $htRoleDefinitionIdsUsedInPolicy.($roledefinitionId)) { $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId) = @{} - $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = [array]$hlpPolicyDefinitionId + $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = [array]$builtinPolicyDefinition.Id } else { $usedInPolicies = $htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies - $usedInPolicies += $hlpPolicyDefinitionId + $usedInPolicies += $builtinPolicyDefinition.Id $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = $usedInPolicies } } - else { - Write-Host "$currentTask $($hlpPolicyDefinitionId) Finding: empty roleDefinitionId in roledefinitionIds" - } + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).RoleDefinitionIds = 'n/a' } } - 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 ($builtInCapability -eq 'PolicySetDefinitions') { + + $currentTask = 'Caching built-in PolicySet definitions' + #Write-Host " $currentTask" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'BuiltIn'" + $method = 'GET' + $requestPolicySetDefinitionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask + + $builtinPolicySetDefinitions = $requestPolicySetDefinitionAPI.where( { $_.properties.policyType -eq 'BuiltIn' } ) + Write-Host " $($requestPolicySetDefinitionAPI.Count) built-in PolicySet definitions returned" + foreach ($builtinPolicySetDefinition in $builtinPolicySetDefinitions) { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()) = @{} + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Id = ($builtinPolicySetDefinition.Id).ToLower() + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeMGLevel = '' + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Scope = 'n/a' + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeMgSub = 'n/a' + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeId = 'n/a' + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).DisplayName = $builtinPolicySetDefinition.Properties.displayname + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Description = $builtinPolicySetDefinition.Properties.description + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Type = $builtinPolicySetDefinition.Properties.policyType + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Category = $builtinPolicySetDefinition.Properties.metadata.category + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).PolicyDefinitionId = ($builtinPolicySetDefinition.Id).ToLower() + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).LinkToAzAdvertizer = "$($builtinPolicySetDefinition.Properties.displayname)" + $arrayPolicySetPolicyIdsToLower = @() + $arrayPolicySetPolicyIdsToLower = foreach ($policySetPolicy in $builtinPolicySetDefinition.properties.policydefinitions.policyDefinitionId) { + ($policySetPolicy).ToLower() } - } - 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 + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).PolicySetPolicyIds = $arrayPolicySetPolicyIdsToLower + if ($builtinPolicySetDefinition.Properties.metadata.deprecated -eq $true -or $builtinPolicySetDefinition.Properties.displayname -like "``[Deprecated``]*") { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Deprecated = $builtinPolicySetDefinition.Properties.metadata.deprecated + } + else { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Deprecated = $false + } + if ($builtinPolicySetDefinition.Properties.metadata.preview -eq $true -or $builtinPolicySetDefinition.Properties.displayname -like "``[*Preview``]*") { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Preview = $builtinPolicySetDefinition.Properties.metadata.preview + } + else { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Preview = $false } + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Json = $builtinPolicySetDefinition } - #endregion namingValidation } - } - $returnObject = @{} - $returnObject."PolicyDefinitionsScopedCount" = $PolicyDefinitionsScopedCount - return $returnObject -} -$funcDataCollectionPolicyDefinitions = $function:DataCollectionPolicyDefinitions.ToString() + if ($builtInCapability -eq 'RoleDefinitions') { + $currentTask = 'Caching built-in Role definitions' + #Write-Host " $currentTask" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($azAPICallConf['checkContext'].Subscription.Id)/providers/Microsoft.Authorization/roleDefinitions?api-version=2018-07-01&`$filter=type eq 'BuiltInRole'" + $method = 'GET' + $requestRoleDefinitionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -function DataCollectionPolicySetDefinitions { - [CmdletBinding()]Param( - [string]$TargetMgOrSub, - [string]$scopeId, - [string]$scopeDisplayName - ) + Write-Host " $($requestRoleDefinitionAPI.Count) built-in Role definitions returned" + foreach ($roleDefinition in $requestRoleDefinitionAPI) { + if ( + ( + $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/roleassignments/write' -or + $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/roleassignments/*' -or + $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/*/write' -or + $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/*' -or + $roleDefinition.properties.permissions.actions -contains '*/write' -or + $roleDefinition.properties.permissions.actions -contains '*' + ) -and ( + $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/roleassignments/write' -and + $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/roleassignments/*' -and + $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/*/write' -and + $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/*' -and + $roleDefinition.properties.permissions.notActions -notcontains '*/write' -and + $roleDefinition.properties.permissions.notActions -notcontains '*' + ) + ) { + $roleCapable4RoleAssignmentsWrite = $true + } + else { + $roleCapable4RoleAssignmentsWrite = $false + } - $currentTask = "PolicySet definitions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" - if ($TargetMgOrSub -eq "Sub") { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'" - } - if ($TargetMgOrSub -eq "MG") { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementgroups/$($scopeId)/providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'" + ($script:htCacheDefinitionsRole).($roleDefinition.name) = @{} + ($script:htCacheDefinitionsRole).($roleDefinition.name).Id = ($roleDefinition.name) + ($script:htCacheDefinitionsRole).($roleDefinition.name).Name = ($roleDefinition.properties.roleName) + ($script:htCacheDefinitionsRole).($roleDefinition.name).IsCustom = $false + ($script:htCacheDefinitionsRole).($roleDefinition.name).AssignableScopes = ($roleDefinition.properties.assignableScopes) + ($script:htCacheDefinitionsRole).($roleDefinition.name).Actions = ($roleDefinition.properties.permissions.actions) + ($script:htCacheDefinitionsRole).($roleDefinition.name).NotActions = ($roleDefinition.properties.permissions.notActions) + ($script:htCacheDefinitionsRole).($roleDefinition.name).DataActions = ($roleDefinition.properties.permissions.dataActions) + ($script:htCacheDefinitionsRole).($roleDefinition.name).NotDataActions = ($roleDefinition.properties.permissions.notDataActions) + ($script:htCacheDefinitionsRole).($roleDefinition.name).Json = ($roleDefinition.properties) + ($script:htCacheDefinitionsRole).($roleDefinition.name).LinkToAzAdvertizer = "$($roleDefinition.properties.roleName)" + ($script:htCacheDefinitionsRole).($roleDefinition.name).RoleCanDoRoleAssignments = $roleCapable4RoleAssignmentsWrite + } + } } - $method = "GET" - $requestPolicySetDefinitionAPI = AzAPICall -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 - } + $script:builtInPolicyDefinitionsCount = $script:htCacheDefinitionsPolicy.Keys.Count - foreach ($scopePolicySetDefinition in $scopePolicySetDefinitions) { - $hlpPolicySetDefinitionId = ($scopePolicySetDefinition.id).ToLower() + $endDefinitionsCaching = Get-Date + Write-Host "Caching built-in definitions duration: $((NEW-TIMESPAN -Start $startDefinitionsCaching -End $endDefinitionsCaching).TotalSeconds) seconds" +} +function createTagList { + $startTagListArray = Get-Date + Write-Host 'Creating TagList array' - $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 - } - } + $tagsSubRgResCount = ($htAllTagList.'AllScopes'.Keys).Count + $tagsSubsriptionCount = ($htAllTagList.'Subscription'.Keys).Count + $tagsResourceGroupCount = ($htAllTagList.'ResourceGroup'.Keys).Count + $tagsResourceCount = ($htAllTagList.'Resource'.Keys).Count + Write-Host " Total Number of ALL unique Tag Names: $tagsSubRgResCount" + Write-Host " Total Number of Subscription unique Tag Names: $tagsSubsriptionCount" + Write-Host " Total Number of ResourceGroup unique Tag Names: $tagsResourceGroupCount" + Write-Host " Total Number of Resource unique Tag Names: $tagsResourceCount" - if ($doIt) { - if (($scopePolicySetDefinition.Properties.description).length -eq 0) { - $policySetDefinitionDescription = "no description given" - } - else { - $policySetDefinitionDescription = $scopePolicySetDefinition.Properties.description - } + foreach ($tagScope in $htAllTagList.keys) { + foreach ($tagScopeTagName in $htAllTagList.($tagScope).keys) { + $null = $script:arrayTagList.Add([PSCustomObject]@{ + Scope = $tagScope + TagName = ($tagScopeTagName) + TagCount = $htAllTagList.($tagScope).($tagScopeTagName) + }) + } + } + $endTagListArray = Get-Date + Write-Host "Creating TagList array duration: $((NEW-TIMESPAN -Start $startTagListArray -End $endTagListArray).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startTagListArray -End $endTagListArray).TotalSeconds) seconds)" +} +function detailSubscriptions { + #API in rare cases returns duplicates, therefor sorting unique (id) + $childrenSubscriptions = $arrayEntitiesFromAPI.where( { $_.properties.parentNameChain -contains $ManagementGroupID -and $_.type -eq '/subscriptions' } ) | Sort-Object -Property id -Unique + $script:childrenSubscriptionsCount = ($childrenSubscriptions).Count + $script:subsToProcessInCustomDataCollection = [System.Collections.ArrayList]@() + foreach ($childrenSubscription in $childrenSubscriptions) { - $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 = "" - } - $htTemp.DisplayName = $($scopePolicySetDefinition.Properties.displayname) - $htTemp.Description = $($policySetDefinitionDescription) - $htTemp.Type = $($scopePolicySetDefinition.Properties.policyType) - $htTemp.Category = $($scopePolicySetDefinition.Properties.metadata.category) - $htTemp.PolicyDefinitionId = $hlpPolicySetDefinitionId - $arrayPolicySetPolicyIdsToLower = @() - $arrayPolicySetPolicyIdsToLower = foreach ($policySetPolicy in $scopePolicySetDefinition.properties.policydefinitions.policyDefinitionId) { - ($policySetPolicy).ToLower() - } - $htTemp.PolicySetPolicyIds = $arrayPolicySetPolicyIdsToLower - $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 + $sub = $htAllSubscriptionsFromAPI.($childrenSubscription.name) + if ($sub.subDetails.subscriptionPolicies.quotaId.startswith('AAD_', 'CurrentCultureIgnoreCase') -or $sub.subDetails.state -ne 'Enabled') { + if (($sub.subDetails.subscriptionPolicies.quotaId).startswith('AAD_', 'CurrentCultureIgnoreCase')) { + $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + outOfScopeReason = "QuotaId: AAD_ (State: $($sub.subDetails.state))" + ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent + ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName + Level = $htSubscriptionsMgPath.($childrenSubscription.name).level + }) } - else { - $htTemp.Preview = $false + if ($sub.subDetails.state -ne 'Enabled') { + $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + outOfScopeReason = "State: $($sub.subDetails.state)" + ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent + ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName + Level = $htSubscriptionsMgPath.($childrenSubscription.name).level + }) } - ($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) = @{} + } + else { + if ($SubscriptionQuotaIdWhitelist[0] -ne 'undefined') { + $whitelistMatched = 'unknown' + foreach ($subscriptionQuotaIdWhitelistQuotaId in $SubscriptionQuotaIdWhitelist) { + if (($sub.subDetails.subscriptionPolicies.quotaId).startswith($subscriptionQuotaIdWhitelistQuotaId, 'CurrentCultureIgnoreCase')) { + $whitelistMatched = 'inWhitelist' } - $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 + + if ($whitelistMatched -eq 'inWhitelist') { + #write-host "$($childrenSubscription.properties.displayName) in whitelist" + $null = $script:subsToProcessInCustomDataCollection.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + subscriptionQuotaId = $sub.subDetails.subscriptionPolicies.quotaId + }) + } + else { + #Write-Host " preCustomDataCollection: $($childrenSubscription.properties.displayName) ($($childrenSubscription.name)) Subscription Quota Id: $($sub.subDetails.subscriptionPolicies.quotaId) is out of scope for AzGovViz (not in Whitelist)" + $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + outOfScopeReason = "QuotaId: '$($sub.subDetails.subscriptionPolicies.quotaId)' not in Whitelist" + ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent + ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName + Level = $htSubscriptionsMgPath.($childrenSubscription.name).level + }) } } + else { + $null = $script:subsToProcessInCustomDataCollection.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + subscriptionQuotaId = $sub.subDetails.subscriptionPolicies.quotaId + }) + } } } - - $returnObject = @{} - $returnObject."PolicySetDefinitionsScopedCount" = $PolicySetDefinitionsScopedCount - return $returnObject + $script:subsToProcessInCustomDataCollectionCount = ($subsToProcessInCustomDataCollection).Count } -$funcDataCollectionPolicySetDefinitions = $function:DataCollectionPolicySetDefinitions.ToString() - -function DataCollectionPolicyAssignmentsMG { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $hierarchyLevel, - $mgParentId, - $mgParentName, - $mgAscSecureScoreResult, - $PolicyDefinitionsScopedCount, - $PolicySetDefinitionsScopedCount - ) +function exportBaseCSV { + Write-Host "Exporting CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName).csv'" + $startBuildCSV = Get-Date - $addRowToTableDone = $false - $currentTask = "Policy assignments '$($scopeDisplayName)' ('$($scopeId)')" - if ($htParameters.LargeTenant -eq $false -or $htParameters.PolicyAtScopeOnly -eq $false) { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementgroups/$($scopeId)/providers/Microsoft.Authorization/policyAssignments?`$filter=atscope()&api-version=2021-06-01" + $outprops = $newtable[0].PSObject.Properties.Name + $outprops.Set($outprops.IndexOf('PolicyAssignmentNotScopes'), @{L = 'PolicyAssignmentNotScopes'; E = { ($_.PolicyAssignmentNotScopes -join "$CsvDelimiterOpposite ") } }) + if ($CsvExportUseQuotesAsNeeded) { + $newTable | Sort-Object -Property level, mgId, SubscriptionId, PolicyAssignmentId, RoleAssignmentId, BlueprintId, BlueprintAssignmentId | Select-Object -Property $outprops -ExcludeProperty PolicyAssignmentParameters | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded } else { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementgroups/$($scopeId)/providers/Microsoft.Authorization/policyAssignments?`$filter=atExactScope()&api-version=2021-06-01" - } - $method = "GET" - $L0mgmtGroupPolicyAssignments = AzAPICall -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 + $newTable | Sort-Object -Property level, mgId, SubscriptionId, PolicyAssignmentId, RoleAssignmentId, BlueprintId, BlueprintAssignmentId | Select-Object -Property $outprops -ExcludeProperty PolicyAssignmentParameters | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).csv" -Delimiter "$csvDelimiter" -NoTypeInformation } - foreach ($L0mgmtGroupPolicyAssignment in $L0mgmtGroupPolicyAssignments) { + $endBuildCSV = Get-Date + Write-Host "Exporting CSV total duration: $((NEW-TIMESPAN -Start $startBuildCSV -End $endBuildCSV).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildCSV -End $endBuildCSV).TotalSeconds) seconds)" +} +function getConsumption { - $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 - } + function addToAllConsumptionData { + [CmdletBinding()]Param( + [Parameter(Mandatory)] + [object] + $consumptiondataFromAPI + ) - 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 + foreach ($consumptionline in $consumptiondataFromAPI.properties.rows) { + $hlper = $htSubscriptionsMgPath.($consumptionline[1]) + $null = $script:allConsumptionData.Add([PSCustomObject]@{ + "$($consumptiondataFromAPI.properties.columns.name[0])" = $consumptionline[0] + "$($consumptiondataFromAPI.properties.columns.name[1])" = $consumptionline[1] + SubscriptionName = $hlper.DisplayName + SubscriptionMgPath = $hlper.ParentNameChainDelimited + "$($consumptiondataFromAPI.properties.columns.name[2])" = $consumptionline[2] + "$($consumptiondataFromAPI.properties.columns.name[3])" = $consumptionline[3] + "$($consumptiondataFromAPI.properties.columns.name[4])" = $consumptionline[4] + "$($consumptiondataFromAPI.properties.columns.name[5])" = $consumptionline[5] + "$($consumptiondataFromAPI.properties.columns.name[6])" = $consumptionline[6] + }) } + } - #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 + $startConsumptionData = Get-Date - if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match "/providers/Microsoft.Authorization/policyDefinitions/" -OR $L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match "/providers/Microsoft.Authorization/policySetDefinitions/") { + #cost only for whitelisted quotaId + if ($SubscriptionQuotaIdWhitelist[0] -ne 'undefined') { + if ($subsToProcessInCustomDataCollectionCount -gt 0) { + #region mgScopeWhitelisted + #$subscriptionIdsOptimizedForBody = '"{0}"' -f ($subsToProcessInCustomDataCollection.subscriptionId -join '","') + $currenttask = "Getting Consumption data (scope MG '$($ManagementGroupId)') for $($subsToProcessInCustomDataCollectionCount) Subscriptions (QuotaId Whitelist: '$($SubscriptionQuotaIdWhitelist -join ', ')') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" + Write-Host "$currentTask" + #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" + $method = 'POST' - #policy - if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match "/providers/Microsoft.Authorization/policyDefinitions/") { - $policyVariant = "Policy" - $policyDefinitionId = ($L0mgmtGroupPolicyAssignment.properties.policydefinitionid).ToLower() + $counterBatch = [PSCustomObject] @{ Value = 0 } + $batchSize = 100 + $subscriptionsBatch = ($subsToProcessInCustomDataCollection | Sort-Object -Property subscriptionQuotaId) | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) } + $batchCnt = 0 - $policyDefinitionSplitted = $policyDefinitionId.split('/') - $hlpPolicyDefinitionScope = $policyDefinitionSplitted[4] + foreach ($batch in $subscriptionsBatch) { + $batchCnt++ + $subscriptionIdsOptimizedForBody = '"{0}"' -f (($batch.Group).subscriptionId -join '","') + $currenttask = "Getting Consumption data #batch$($batchCnt)/$(($subscriptionsBatch | Measure-Object).Count) (scope MG '$($ManagementGroupId)') for $(($batch.Group).Count) Subscriptions (QuotaId Whitelist: '$($SubscriptionQuotaIdWhitelist -join ', ')') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" + Write-Host "$currentTask" -ForegroundColor Cyan - 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) + $body = @" +{ +"type": "ActualCost", +"dataset": { + "granularity": "none", + "filter": { + "dimensions": { + "name": "SubscriptionId", + "operator": "In", + "values": [ + $($subscriptionIdsOptimizedForBody) + ] + } + }, + "aggregation": { + "totalCost": { + "name": "PreTaxCost", + "function": "Sum" + } + }, + "grouping": [ + { + "type": "Dimension", + "name": "SubscriptionId" + }, + { + "type": "Dimension", + "name": "ResourceId" + }, + { + "type": "Dimension", + "name": "ConsumedService" + }, + { + "type": "Dimension", + "name": "MeterCategory" + }, + { + "type": "Dimension", + "name": "ChargeType" + } + ] +}, +"timeframe": "Custom", +"timeperiod": { + "from": "$($azureConsumptionStartDate)", + "to": "$($azureConsumptionEndDate)" +} +} +"@ - if ([string]::IsnullOrEmpty($policyDefinition.PolicyDefinitionId)) { - Write-Host "check: $policyDefinitionId" - $policyDefinition - } + $mgConsumptionData = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + #endregion mgScopeWhitelisted - if ($policyDefinition.Type -eq "Custom") { - $policyDefintionScope = $policyDefinition.Scope - $policyDefintionScopeMgSub = $policyDefinition.ScopeMgSub - $policyDefintionScopeId = $policyDefinition.ScopeId - } - else { - $policyDefintionScope = "n/a" - $policyDefintionScopeMgSub = "n/a" - $policyDefintionScopeId = "n/a" - } + <#test + #$mgConsumptionData = "OfferNotSupported" + if ($batchCnt -eq 1){ + $mgConsumptionData = "OfferNotSupported" + } + #> - $policyAvailability = "" - $policyDisplayName = ($policyDefinition).DisplayName - $policyDescription = ($policyDefinition).Description - $policyDefinitionType = ($policyDefinition).Type - $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 - AzGovViz: check the last console output for details" + if ($mgConsumptionData -eq 'Unauthorized' -or $mgConsumptionData -eq 'OfferNotSupported') { + if (-not $script:htConsumptionExceptionLog.Mg.($ManagementGroupId)) { + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId) = @{} } - } - #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 + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt) = @{} + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt).Exception = $mgConsumptionData + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt).Subscriptions = ($batch.Group).subscriptionId + Write-Host " Switching to 'foreach Subscription' Subscription scope mode. Getting Consumption data #batch$($batchCnt) using Management Group scope failed." + #region subScopewhitelisted + $body = @" +{ +"type": "ActualCost", +"dataset": { + "granularity": "none", + "aggregation": { + "totalCost": { + "name": "PreTaxCost", + "function": "Sum" + } + }, + "grouping": [ + { + "type": "Dimension", + "name": "SubscriptionId" + }, + { + "type": "Dimension", + "name": "ResourceId" + }, + { + "type": "Dimension", + "name": "ConsumedService" + }, + { + "type": "Dimension", + "name": "MeterCategory" + }, + { + "type": "Dimension", + "name": "ChargeType" + } + ] +}, +"timeframe": "Custom", +"timeperiod": { + "from": "$($azureConsumptionStartDate)", + "to": "$($azureConsumptionEndDate)" +} +} +"@ + $funcAddToAllConsumptionData = $function:addToAllConsumptionData.ToString() + $batch.Group | ForEach-Object -Parallel { + $subIdToProcess = $_.subscriptionId + $subNameToProcess = $_.subscriptionName + $subscriptionQuotaIdToProcess = $_.subscriptionQuotaId + #region UsingVARs + $body = $using:body + $azureConsumptionStartDate = $using:azureConsumptionStartDate + $azureConsumptionEndDate = $using:azureConsumptionEndDate + $SubscriptionQuotaIdWhitelist = $using:SubscriptionQuotaIdWhitelist + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $allConsumptionData = $using:allConsumptionData + $htSubscriptionsMgPath = $using:htSubscriptionsMgPath + $htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI + $htConsumptionExceptionLog = $using:htConsumptionExceptionLog + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:addToAllConsumptionData = $using:funcAddToAllConsumptionData + #endregion UsingVARs - $policyDisplayName = "unknown" - $policyDescription = "unknown" - $policyDefinitionType = "likely Custom" - $policyCategory = "unknown" - $policyDefinitionEffectDefault = "unknown" - $policyDefinitionEffectFixed = "unknown" - } + $currentTask = " Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)) (whitelist))" + #test + write-host $currentTask + #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($subIdToProcess)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" + $method = 'POST' + $subConsumptionData = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + if ($subConsumptionData -eq 'Unauthorized' -or $subConsumptionData -eq 'OfferNotSupported' -or $subConsumptionData -eq 'InvalidQueryDefinition' -or $subConsumptionData -eq 'NonValidWebDirectAIRSOfferType' -or $subConsumptionData -eq 'NotFoundNotSupported' -or $subConsumptionData -eq 'IndirectCostDisabled') { + Write-Host " Failed ($subConsumptionData) - Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)) (whitelist))" + $hlper = $htAllSubscriptionsFromAPI.($subIdToProcess).subDetails + $hlper2 = $htSubscriptionsMgPath.($subIdToProcess) + $script:htConsumptionExceptionLog.Sub.($subIdToProcess) = @{} + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).Exception = $subConsumptionData + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionId = $subIdToProcess + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionName = $hlper.displayName + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).QuotaId = $hlper.subscriptionPolicies.quotaId + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgPath = $hlper2.ParentNameChainDelimited + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgParent = $hlper2.Parent + Continue + } + else { + Write-Host " $($subConsumptionData.Count) Consumption data entries ((scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess))))" + if ($subConsumptionData.Count -gt 0) { + addToAllConsumptionData -consumptiondataFromAPI $subConsumptionData + <# + foreach ($consumptionEntry in $subConsumptionData) { + if ($consumptionEntry.PreTaxCost -ne 0) { + $null = $script:allConsumptionData.Add($consumptionEntry) + } + } + #> - $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" + } + } + } -ThrottleLimit $ThrottleLimit + #endregion subScopewhitelisted } else { - $policyAssignmentDescription = $L0mgmtGroupPolicyAssignment.Properties.Description + Write-Host " $($mgConsumptionData.Count) Consumption data entries" + if ($mgConsumptionData.Count -gt 0) { + addToAllConsumptionData -consumptiondataFromAPI $mgConsumptionData + <# + foreach ($consumptionEntry in $mgConsumptionData) { + if ($consumptionEntry.PreTaxCost -ne 0) { + $null = $script:allConsumptionData.Add($consumptionEntry) + } + } + #> + } } + } + } + else { + $detailShowStopperResult = 'NoWhitelistSubscriptionsPresent' + Write-Host ' No Subscriptions matching whitelist present, skipping Consumption data processing' + #überprüfen + } + } + else { - if ($L0mgmtGroupPolicyAssignment.identity) { - $policyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId + if ($subsToProcessInCustomDataCollectionCount -gt 0) { + #region mgScope + $currenttask = "Getting Consumption data (scope MG '$($ManagementGroupId)') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" + Write-Host "$currentTask" + #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" + $method = 'POST' + $body = @" +{ + "type": "ActualCost", + "dataset": { + "granularity": "none", + "aggregation": { + "totalCost": { + "name": "PreTaxCost", + "function": "Sum" + } + }, + "grouping": [ + { + "type": "Dimension", + "name": "SubscriptionId" + }, + { + "type": "Dimension", + "name": "ResourceId" + }, + { + "type": "Dimension", + "name": "ConsumedService" + }, + { + "type": "Dimension", + "name": "MeterCategory" + }, + { + "type": "Dimension", + "name": "ChargeType" + } + ] + }, + "timeframe": "Custom", + "timeperiod": { + "from": "$($azureConsumptionStartDate)", + "to": "$($azureConsumptionEndDate)" + } +} +"@ + #$script:allConsumptionData = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + $allConsumptionDataAPIResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + #endregion mgScope + + #test + #$allConsumptionData = "OfferNotSupported" + + if ($allConsumptionDataAPIResult -eq 'AccountCostDisabled' -or $allConsumptionDataAPIResult -eq 'NoValidSubscriptions') { + $generalShowStopperResult = $true + if ($allConsumptionDataAPIResult -eq 'AccountCostDisabled') { + $detailShowStopperResult = $allConsumptionDataAPIResult } - else { - $policyAssignmentIdentity = "n/a" + if ($allConsumptionDataAPIResult -eq 'NoValidSubscriptions') { + $detailShowStopperResult = $allConsumptionDataAPIResult } + } + else { + if ($allConsumptionDataAPIResult -eq 'Unauthorized' -or $allConsumptionDataAPIResult -eq 'OfferNotSupported') { + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId) = @{} + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).Exception = $allConsumptionDataAPIResult + Write-Host " Switching to 'foreach Subscription' mode. Getting Consumption data using Management Group scope failed." + #region subScope + $body = @" +{ + "type": "ActualCost", + "dataset": { + "granularity": "none", + "aggregation": { + "totalCost": { + "name": "PreTaxCost", + "function": "Sum" + } + }, + "grouping": [ + { + "type": "Dimension", + "name": "SubscriptionId" + }, + { + "type": "Dimension", + "name": "ResourceId" + }, + { + "type": "Dimension", + "name": "ConsumedService" + }, + { + "type": "Dimension", + "name": "MeterCategory" + }, + { + "type": "Dimension", + "name": "ChargeType" + } + ] + }, + "timeframe": "Custom", + "timeperiod": { + "from": "$($azureConsumptionStartDate)", + "to": "$($azureConsumptionEndDate)" + } +} +"@ - $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 - } - } + $funcAddToAllConsumptionData = $function:addToAllConsumptionData.ToString() + $subsToProcessInCustomDataCollection | ForEach-Object -Parallel { + $subIdToProcess = $_.subscriptionId + $subNameToProcess = $_.subscriptionName + $subscriptionQuotaIdToProcess = $_.subscriptionQuotaId + #region UsingVARs + $body = $using:body + $azureConsumptionStartDate = $using:azureConsumptionStartDate + $azureConsumptionEndDate = $using:azureConsumptionEndDate + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $htSubscriptionsMgPath = $using:htSubscriptionsMgPath + $htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI + $allConsumptionData = $using:allConsumptionData + $htConsumptionExceptionLog = $using:htConsumptionExceptionLog + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:addToAllConsumptionData = $using:funcAddToAllConsumptionData + #endregion UsingVARs - if ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.Message) { - $nonComplianceMessage = $L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.Message + $currentTask = " Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" + #test + write-host $currentTask + #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($subIdToProcess)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" + $method = 'POST' + $subConsumptionData = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + if ($subConsumptionData -eq 'Unauthorized' -or $subConsumptionData -eq 'OfferNotSupported' -or $subConsumptionData -eq 'InvalidQueryDefinition' -or $subConsumptionData -eq 'NonValidWebDirectAIRSOfferType' -or $subConsumptionData -eq 'NotFoundNotSupported' -or $subConsumptionData -eq 'IndirectCostDisabled') { + Write-Host " Failed ($subConsumptionData) - Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" + $hlper = $htAllSubscriptionsFromAPI.($subIdToProcess).subDetails + $hlper2 = $htSubscriptionsMgPath.($subIdToProcess) + $script:htConsumptionExceptionLog.Sub.($subIdToProcess) = @{} + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).Exception = $subConsumptionData + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionId = $subIdToProcess + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionName = $hlper.displayName + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).QuotaId = $hlper.subscriptionPolicies.quotaId + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgPath = $hlper2.ParentNameChainDelimited + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgParent = $hlper2.Parent + Continue + } + else { + Write-Host " $($subConsumptionData.Count) Consumption data entries (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" + if ($subConsumptionData.Count -gt 0) { + addToAllConsumptionData -consumptiondataFromAPI $subConsumptionData + <# + foreach ($consumptionEntry in $subConsumptionData) { + if ($consumptionEntry.PreTaxCost -ne 0) { + $null = $script:allConsumptionData.Add($consumptionEntry) + } + } + #> + } + } + } -ThrottleLimit $ThrottleLimit + #endregion subScope } 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) ")" + Write-Host " $($allConsumptionDataAPIResult.properties.rows.Count) Consumption data entries" + if ($allConsumptionDataAPIResult.properties.rows.Count -gt 0) { + addToAllConsumptionData -consumptiondataFromAPI $allConsumptionDataAPIResult } - $formatedPolicyAssignmentParameters = $arrayPolicyAssignmentParameters -join "$($CsvDelimiterOpposite) " } + } + } + else { + $detailShowStopperResult = 'NoSubscriptionsPresent' + Write-Host ' No Subscriptions present, skipping Consumption data processing' + } + } - $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 ` - -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 - } + if ($detailShowStopperResult -eq 'AccountCostDisabled' -or $detailShowStopperResult -eq 'NoValidSubscriptions' -or $detailShowStopperResult -eq 'NoWhitelistSubscriptionsPresent' -or $detailShowStopperResult -eq 'NoSubscriptionsPresent') { + if ($detailShowStopperResult -eq 'AccountCostDisabled') { + Write-Host ' Seems Access to cost data has been disabled for this Account - skipping CostManagement' + } + if ($detailShowStopperResult -eq 'NoValidSubscriptions') { + Write-Host ' Seems there are no valid Subscriptions present - skipping CostManagement' + } + if ($detailShowStopperResult -eq 'NoWhitelistSubscriptionsPresent') { + Write-Host " Seems there are no Subscriptions present that match the whitelist ($($SubscriptionQuotaIdWhitelist -join ', ')) - skipping CostManagement" + } + if ($detailShowStopperResult -eq 'NoSubscriptionsPresent') { + Write-Host ' Seems there are no Subscriptions present - skipping CostManagement' + } + Write-Host " Action: Setting switch parameter 'DoAzureConsumption' to false" + $azAPICallConf['htParameters'].DoAzureConsumption = $false + } + else { + Write-Host ' Checking returned Consumption data' + $script:allConsumptionDataCount = $allConsumptionData.Count - #policySet - if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match "/providers/Microsoft.Authorization/policySetDefinitions/") { - $policyVariant = "PolicySet" - $policySetDefinitionId = ($L0mgmtGroupPolicyAssignment.properties.policydefinitionid).ToLower() - $policySetDefinitionSplitted = $policySetDefinitionId.split('/') - $hlpPolicySetDefinitionScope = $policySetDefinitionSplitted[4] + if ($allConsumptionDataCount -gt 0) { - $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 + $script:allConsumptionData = $allConsumptionData.where( { $_.PreTaxCost -ne 0 } ) + $script:allConsumptionDataCount = $allConsumptionData.Count + + if ($allConsumptionDataCount -gt 0) { + Write-Host " $($allConsumptionDataCount) relevant Consumption data entries" + + $script:consumptionData = $allConsumptionData + $script:consumptionDataGroupedByCurrency = $consumptionData | Group-Object -property Currency + + foreach ($currency in $consumptionDataGroupedByCurrency) { + + #subscriptions + $groupAllConsumptionDataPerCurrencyBySubscriptionId = $currency.group | Group-Object -Property SubscriptionId + foreach ($subscriptionId in $groupAllConsumptionDataPerCurrencyBySubscriptionId) { + + $subTotalCost = ($subscriptionId.Group.PreTaxCost | Measure-Object -Sum).Sum + $script:htAzureConsumptionSubscriptions.($subscriptionId.Name) = @{} + $script:htAzureConsumptionSubscriptions.($subscriptionId.Name).ConsumptionData = $subscriptionId.group + $script:htAzureConsumptionSubscriptions.($subscriptionId.Name).TotalCost = $subTotalCost + $script:htAzureConsumptionSubscriptions.($subscriptionId.Name).Currency = $currency.Name + $resourceTypes = $subscriptionId.Group.ConsumedService | Sort-Object -Unique + + foreach ($parentMg in $htSubscriptionsMgPath.($subscriptionId.Name).ParentNameChain) { + + if (-not $htManagementGroupsCost.($parentMg)) { + $script:htManagementGroupsCost.($parentMg) = @{} + $script:htManagementGroupsCost.($parentMg).currencies = $currency.Name + $script:htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" = $subTotalCost #[decimal]$subTotalCost + $script:htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" = ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count + $script:htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent = ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count + $script:htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" = 1 + $script:htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent = 1 + $script:htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" = $resourceTypes + $script:htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent = $resourceTypes + $script:htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" = $subscriptionId.group + $script:htManagementGroupsCost.($parentMg).consumptionDataSubscriptions = $subscriptionId.group + } + else { + $newMgTotalCost = $htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" + $subTotalCost #[decimal]$subTotalCost + $script:htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" = $newMgTotalCost #[decimal]$newMgTotalCost + + $currencies = [array]$htManagementGroupsCost.($parentMg).currencies + if ($currencies -notcontains $currency.Name) { + $currencies += $currency.Name + $script:htManagementGroupsCost.($parentMg).currencies = $currencies + } + + #currency based + $resourcesThatGeneratedCost = $htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" + ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count + $script:htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" = $resourcesThatGeneratedCost + + $subscriptionsThatGeneratedCost = $htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" + 1 + $script:htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" = $subscriptionsThatGeneratedCost + + $consumptionDataSubscriptions = $htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" += $subscriptionId.group + $script:htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" = $consumptionDataSubscriptions + + $resourceTypesThatGeneratedCost = $htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" + foreach ($resourceType in $resourceTypes) { + if ($resourceTypesThatGeneratedCost -notcontains $resourceType) { + $resourceTypesThatGeneratedCost += $resourceType + } + } + $script:htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" = $resourceTypesThatGeneratedCost + + #currencyIndependent + $resourcesThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent + ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count + $script:htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent = $resourcesThatGeneratedCostCurrencyIndependent + + $subscriptionsThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent + 1 + $script:htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent = $subscriptionsThatGeneratedCostCurrencyIndependent + + $consumptionDataSubscriptionsCurrencyIndependent = $htManagementGroupsCost.($parentMg).consumptionDataSubscriptions += $subscriptionId.group + $script:htManagementGroupsCost.($parentMg).consumptionDataSubscriptions = $consumptionDataSubscriptionsCurrencyIndependent + + $resourceTypesThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent + foreach ($resourceType in $resourceTypes) { + if ($resourceTypesThatGeneratedCostCurrencyIndependent -notcontains $resourceType) { + $resourceTypesThatGeneratedCostCurrencyIndependent += $resourceType + } + } + $script:htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent = $resourceTypesThatGeneratedCostCurrencyIndependent + } + } + } + + $totalCost = 0 + $script:tenantSummaryConsumptionDataGrouped = $currency.group | Group-Object -property ConsumedService, ChargeType, MeterCategory + $subsCount = ($tenantSummaryConsumptionDataGrouped.group.subscriptionId | Sort-Object -Unique | Measure-Object).Count + $consumedServiceCount = ($tenantSummaryConsumptionDataGrouped.group.consumedService | Sort-Object -Unique | Measure-Object).Count + $resourceCount = ($tenantSummaryConsumptionDataGrouped.group.ResourceId | Sort-Object -Unique | Measure-Object).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 { - $policySetDefintionScope = "n/a" - $policySetDefintionScopeMgSub = "n/a" - $policySetDefintionScopeId = "n/a" + $cost = [math]::Round($costConsumptionLine, 2).ToString('0.00') } - $policySetDisplayName = $policySetDefinition.DisplayName - $policySetDescription = $policySetDefinition.Description - $policySetDefinitionType = $policySetDefinition.Type - $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" - $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 - } + $null = $script:arrayConsumptionData.Add([PSCustomObject]@{ + ConsumedService = ($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 + }) - if ($L0mgmtGroupPolicyAssignment.identity) { - $policyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId - } - else { - $policyAssignmentIdentity = "n/a" - } + $totalCost = $totalCost + $costConsumptionLine - $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 ([math]::Round($totalCost, 2) -eq 0) { + $totalCost = $totalCost } - } - - 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) ")" + else { + $totalCost = [math]::Round($totalCost, 2).ToString('0.00') } - $formatedPolicyAssignmentParameters = $arrayPolicyAssignmentParameters -join "$($CsvDelimiterOpposite) " + $script:arrayTotalCostSummary += "$($totalCost) $($currency.Name) generated by $($resourceCount) Resources ($($consumedServiceCount) ResourceTypes) in $($subsCount) Subscriptions" } + } + else { + Write-Host ' No relevant consumption data entries (0)' + } + } - $addRowToTableDone = $true - AddRowToTable ` - -level $hierarchyLevel ` - -mgName $scopeDisplayName ` - -mgId $scopeId ` - -mgParentId $mgParentId ` - -mgParentName $mgParentName ` - -mgASCSecureScore $mgAscSecureScoreResult ` - -Policy $policySetDisplayName ` - -PolicyDescription $policySetDescription ` - -PolicyVariant $policyVariant ` - -PolicyType $policySetDefinitionType ` - -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 + #region BuildConsumptionCSV + if (-not $NoAzureConsumptionReportExportToCSV) { + Write-Host " Exporting Consumption CSV $($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" + $startBuildConsumptionCSV = Get-Date + if ($CsvExportUseQuotesAsNeeded) { + $allConsumptionData | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded + } + else { + $allConsumptionData | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" -Delimiter "$csvDelimiter" -NoTypeInformation } + $endBuildConsumptionCSV = Get-Date + Write-Host " Exporting Consumption CSV total duration: $((NEW-TIMESPAN -Start $startBuildConsumptionCSV -End $endBuildConsumptionCSV).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildConsumptionCSV -End $endBuildConsumptionCSV).TotalSeconds) seconds)" } + #endregion BuildConsumptionCSV } + $endConsumptionData = Get-Date + Write-Host "Getting Consumption data duration: $((NEW-TIMESPAN -Start $startConsumptionData -End $endConsumptionData).TotalSeconds) seconds" +} +function getDefaultManagementGroup { + $currentTask = 'Get Default Management Group' + Write-Host $currentTask + #https://docs.microsoft.com/en-us/azure/governance/management-groups/how-to/protect-resource-hierarchy#setting---default-management-group + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($azAPICallConf['checkContext'].Tenant.Id)/settings?api-version=2020-02-01" + $method = 'GET' + $settingsMG = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask - $returnObject = @{} - if ($addRowToTableDone) { - $returnObject."addRowToTableDone" = @{} + if (($settingsMG).count -gt 0) { + write-host " default ManagementGroup Id: $($settingsMG.properties.defaultManagementGroup)" + $script:defaultManagementGroupId = $settingsMG.properties.defaultManagementGroup + write-host " requireAuthorizationForGroupCreation: $($settingsMG.properties.requireAuthorizationForGroupCreation)" + $script:requireAuthorizationForGroupCreation = $settingsMG.properties.requireAuthorizationForGroupCreation + } + else { + write-host " default ManagementGroup: $(($azAPICallConf['checkContext']).Tenant.Id) (Tenant Root)" + $script:defaultManagementGroupId = ($azAPICallConf['checkContext']).Tenant.Id + $script:requireAuthorizationForGroupCreation = $false } - return $returnObject } -$funcDataCollectionPolicyAssignmentsMG = $function:DataCollectionPolicyAssignmentsMG.ToString() +function getEntities { + Write-Host 'Entities' + $startEntities = Get-Date + $currentTask = ' Getting Entities' + Write-Host $currentTask + #https://management.azure.com/providers/Microsoft.Management/getEntities?api-version=2020-02-01 + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/getEntities?api-version=2020-02-01" + $method = 'POST' + $script:arrayEntitiesFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -function DataCollectionPolicyAssignmentsSub { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $hierarchyLevel, - $childMgDisplayName, - $childMgId, - $childMgParentId, - $childMgParentName, - $mgAscSecureScoreResult, - $subscriptionQuotaId, - $subscriptionState, - $subscriptionASCSecureScore, - $subscriptionTags, - $subscriptionTagsCount, - $PolicyDefinitionsScopedCount, - $PolicySetDefinitionsScopedCount - ) + Write-Host " $($arrayEntitiesFromAPI.Count) Entities returned" - $currentTask = "Policy assignments '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyAssignments?api-version=2021-06-01" - $method = "GET" + $endEntities = Get-Date + Write-Host " Getting Entities duration: $((NEW-TIMESPAN -Start $startEntities -End $endEntities).TotalSeconds) seconds" - $addRowToTableDone = $false - if ($htParameters.DoNotIncludeResourceGroupsOnPolicy -eq $false) { - $L1mgmtGroupSubPolicyAssignments = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + $startEntitiesdata = Get-Date + Write-Host ' Processing Entities data' + $script:htSubscriptionsMgPath = @{} + $script:htManagementGroupsMgPath = @{} + $script:htEntities = @{} + $script:htEntitiesPlain = @{} - $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 + foreach ($entity in $arrayEntitiesFromAPI) { + $script:htEntitiesPlain.($entity.Name) = @{} + $script:htEntitiesPlain.($entity.Name) = $entity } - else { - $L1mgmtGroupSubPolicyAssignments = AzAPICall -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 + foreach ($entity in $arrayEntitiesFromAPI) { + if ($entity.Type -eq '/subscriptions') { + $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__' + } + else { + $parent = $entity.properties.parent.Id -replace '.*/' + } + $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:htEntities.($entity.name) = @{} + $script:htEntities.($entity.name).ParentNameChain = $entity.properties.parentNameChain + $script:htEntities.($entity.name).Parent = $parent + if ($parent -eq '__TenantRoot__') { + $parentDisplayName = '__TenantRoot__' } - $L1mgmtGroupSubPolicyAssignmentsQuery = $L1mgmtGroupSubPolicyAssignments.where( { $_.Id -notmatch "/subscriptions/$($scopeId)/resourceGroups" } ) + 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 } - $L1mgmtGroupSubPolicyAssignmentsPolicyAndPolicySetAtScopeCount = ($L1mgmtGroupSubPolicyAssignmentsPolicyAtScopeCount + $L1mgmtGroupSubPolicyAssignmentsPolicySetAtScopeCount) + Write-Host " $(($htManagementGroupsMgPath.Keys).Count) Management Groups returned" + Write-Host " $(($htSubscriptionsMgPath.Keys).Count) Subscriptions returned" - 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 - } + $endEntitiesdata = Get-Date + Write-Host " Processing Entities data duration: $((NEW-TIMESPAN -Start $startEntitiesdata -End $endEntitiesdata).TotalSeconds) seconds" - #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: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 ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $true) { + $script:fileName = "AzGovViz_HierarchyMapOnly_$($ManagementGroupId)" + } + elseif ($azAPICallConf['htParameters'].ManagementGroupsOnly -eq $true) { + $script:fileName = "AzGovViz_ManagementGroupsOnly_$($ManagementGroupId)" + } + else { + $script:fileName = "AzGovViz_$($ManagementGroupId)" + } + } + else { + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $true) { + $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 } - $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) = @{} + + #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: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/") { + $script:htAADGroupsDetails.($aadGroupId).MembersAllCount = $aadGroupMembersAllCount + $script:htAADGroupsDetails.($aadGroupId).MembersUsersCount = $aadGroupMembersUsersCount + $script:htAADGroupsDetails.($aadGroupId).MembersGroupsCount = $aadGroupMembersGroupsCount + $script:htAADGroupsDetails.($aadGroupId).MembersServicePrincipalsCount = $aadGroupMembersServicePrincipalsCount - #policy - if ($L1mgmtGroupSubPolicyAssignment.properties.policyDefinitionId -match "/providers/Microsoft.Authorization/policyDefinitions/") { - $policyVariant = "Policy" - $policyDefinitionId = ($L1mgmtGroupSubPolicyAssignment.properties.policydefinitionid).ToLower() + if ($aadGroupMembersAllCount -gt 0) { + $script:htAADGroupsDetails.($aadGroupId).MembersAll = $aadGroupMembersAll - if (($htCacheDefinitionsPolicy).($policyDefinitionId)) { - $policyAvailability = "" + 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 { + $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' - #handling some strange scenario where the synchronized hashTable responds fragments?! - $tryCounter = 0 - do { - $tryCounter++ - $policyAssignmentsPolicyDefinition = ($htCacheDefinitionsPolicy).($policyDefinitionId) + $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 +'@ - if (($policyAssignmentsPolicyDefinition).Type -eq "Custom" -or ($policyAssignmentsPolicyDefinition).Type -eq "Builtin") { - $policyReturnedFromHt = $true + $body = @" + { + "query": "$($query)", + "managementGroups":[ + "$($ManagementGroupId)" + ] + } +"@ - $policyDisplayName = ($policyAssignmentsPolicyDefinition).DisplayName - $policyDescription = ($policyAssignmentsPolicyDefinition).Description - $policyDefinitionType = ($policyAssignmentsPolicyDefinition).Type - $policyCategory = ($policyAssignmentsPolicyDefinition).Category - $policyDefinitionEffectDefault = ($policyAssignmentsPolicyDefinition).effectDefaultValue - $policyDefinitionEffectFixed = ($policyAssignmentsPolicyDefinition).effectFixedValue + $start = Get-Date + $getMgAscSecureScore = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -body $body -listenOn 'Content' + $end = Get-Date + Write-Host " Getting Microsoft Defender for Cloud Secure Score for Management Groups duration: $((NEW-TIMESPAN -Start $start -End $end).TotalSeconds) seconds" + if ($getMgAscSecureScore) { + if ($getMgAscSecureScore -eq 'capitulation') { + Write-Host ' Microsoft Defender for Cloud SecureScore for Management Groups will not be available' -ForegroundColor Yellow + } + else { + foreach ($entry in $getMgAscSecureScore.data) { + $script:htMgASCSecureScore.($entry.mgId) = @{} + if ($entry.secureScore -eq 404) { + $script:htMgASCSecureScore.($entry.mgId).SecureScore = 'n/a' + } + else { + $script:htMgASCSecureScore.($entry.mgId).SecureScore = $entry.secureScore + } + } + } + } +} +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 to process" + $script:resourceTypesSummarizedArray = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + + $script:resourceTypesDiagnosticsArray = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $resourceTypesUnique.where( { $_.Name -like 'microsoft.*' }) | ForEach-Object -Parallel { + $resourceTypesUniqueGroup = $_ + $resourcetype = $resourceTypesUniqueGroup.Name + #region UsingVARs + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $ExcludedResourceTypesDiagnosticsCapable = $using:ExcludedResourceTypesDiagnosticsCapable + $resourceTypesDiagnosticsArray = $using:resourceTypesDiagnosticsArray + $htResourceTypesUniqueResource = $using:htResourceTypesUniqueResource + $resourceTypesSummarizedArray = $using:resourceTypesSummarizedArray + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #endregion UsingVARs - 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: $($htParameters.GithubRepository)" -ForegroundColor Yellow - throw - } + $skipThisResourceType = $false + if (($ExcludedResourceTypesDiagnosticsCapable).Count -gt 0) { + foreach ($excludedResourceType in $ExcludedResourceTypesDiagnosticsCapable) { + if ($excludedResourceType -eq $resourcetype) { + $skipThisResourceType = $true + } + } + } - if ($policyDefinitionType -eq "Custom") { - $policyDefintionScope = ($policyAssignmentsPolicyDefinition).Scope - $policyDefintionScopeMgSub = ($policyAssignmentsPolicyDefinition).ScopeMgSub - $policyDefintionScopeId = ($policyAssignmentsPolicyDefinition).ScopeId - } + if ($skipThisResourceType -eq $false) { + $resourceCount = $resourceTypesUniqueGroup.Count - 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 - } + #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] } - 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: $($htParameters.GithubRepository)" -ForegroundColor Yellow - throw + else { + $resourceId = $resourceTypesUniqueGroup.Group.Id } - } - #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)'" + $resourceAvailability = $resourceAvailability - 1 + $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 -method $method -currentTask $currentTask + if ($responseJSON -notlike 'meanwhile_deleted*') { + if ($responseJSON -eq 'ResourceTypeOrResourceProviderNotSupported') { + Write-Host " ResourceTypeOrResourceProviderNotSupported | The resource type '$($resourcetype)' does not support diagnostic settings." + } 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" - } + Write-Host " ResourceTypeSupported | The resource type '$($resourcetype)' supports diagnostic settings." } - $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 + else { + Write-Host "resId '$resourceId' meanwhile deleted" } - $policyAvailability = "na" - $policyDisplayName = "unknown" - $policyDescription = "unknown" - $policyDefinitionType = "likely Custom" - $policyCategory = "unknown" - $policyDefinitionEffectDefault = "unknown" - $policyDefinitionEffectFixed = "unknown" } + until ($resourceAvailability -lt 0 -or $responseJSON -notlike 'meanwhile_deleted*') - $PolicyAssignmentScope = $L1mgmtGroupSubPolicyAssignment.Properties.Scope - if ($PolicyAssignmentScope -like "/providers/Microsoft.Management/managementGroups/*") { - $PolicyAssignmentScopeMgSubRg = "Mg" + if ($resourceAvailability -lt 0 -and $responseJSON -like 'meanwhile_deleted*') { + Write-Host "tried for all available resourceIds ($($resourceCount)) for resourceType $resourceType, but seems all resources meanwhile have been deleted" + $null = $script:resourceTypesDiagnosticsArray.Add([PSCustomObject]@{ + ResourceType = $resourcetype + Metrics = "n/a - $responseJSON" + Logs = "n/a - $responseJSON" + LogCategories = 'n/a' + ResourceCount = $resourceCount + }) } else { - $splitPolicyAssignmentScope = ($PolicyAssignmentScope).Split('/') - switch (($splitPolicyAssignmentScope).Count - 1) { - #sub - 2 { - $PolicyAssignmentScopeMgSubRg = "Sub" - } - 4 { - $PolicyAssignmentScopeMgSubRg = "Rg" - } - Default { - $PolicyAssignmentScopeMgSubRg = "unknown" + 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 + } } } - } - - $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" + $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 + #[System.GC]::Collect() + } + 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 - $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 - } - } + Write-Host " $($requestAllSubscriptionsAPI.Count) Subscriptions returned" + foreach ($subscription in $requestAllSubscriptionsAPI) { + $script:htAllSubscriptionsFromAPI.($subscription.subscriptionId) = @{} + $script:htAllSubscriptionsFromAPI.($subscription.subscriptionId).subDetails = $subscription + } - if ($L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.Message) { - $nonComplianceMessage = $L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.Message - } - else { - $nonComplianceMessage = "" - } + $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 - $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) " - } + 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' + } - $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 ` - -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 = "" + 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 { + Write-Host 'Resolving AAD Groups (for which a RBAC Role assignment exists)' + Write-Host " Users known as Guest count: $($htUserTypesGuest.Keys.Count) (before Resolving AAD Groups)" + $startAADGroupsResolveMembers = Get-Date - #handling some strange behavior where the synchronized hashTable responds fragments?! - $tryCounter = 0 - do { - $tryCounter++ - $policyAssignmentsPolicySetDefinition = ($htCacheDefinitionsPolicySet).($policySetDefinitionId) + $optimizedTableForAADGroupsQuery = ($roleAssignmentsUniqueById.where( { $_.RoleAssignmentIdentityObjectType -eq 'Group' } ) | Select-Object -Property RoleAssignmentIdentityObjectId, RoleAssignmentIdentityDisplayname) | Sort-Object -Property RoleAssignmentIdentityObjectId -Unique + $aadGroupsCount = ($optimizedTableForAADGroupsQuery).Count - if (($policyAssignmentsPolicySetDefinition).Type -eq "Custom" -or ($policyAssignmentsPolicySetDefinition).Type -eq "Builtin") { - $policySetReturnedFromHt = $true + if ($aadGroupsCount -gt 0) { - $policySetDisplayName = ($policyAssignmentsPolicySetDefinition).DisplayName - $policySetDescription = ($policyAssignmentsPolicySetDefinition).Description - $policySetDefinitionType = ($policyAssignmentsPolicySetDefinition).Type - $policySetCategory = ($policyAssignmentsPolicySetDefinition).Category + 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 } + } - 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: $($htParameters.GithubRepository)" -ForegroundColor Yellow - throw - } + Write-Host " processing $($aadGroupsCount) AAD Groups with Role assignments (indicating progress in steps of $indicator)" - 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: $($htParameters.GithubRepository)" -ForegroundColor Yellow - throw - } - } - #policySetDefinition not exists! - else { - $policyAvailability = "na" - $policySetDisplayName = "unknown" - $policySetDescription = "unknown" - $policySetDefinitionType = "likely Custom" - $policySetCategory = "unknown" + $optimizedTableForAADGroupsQuery | ForEach-Object -Parallel { + $aadGroupIdWithRoleAssignment = $_ + #region UsingVARs + #fromOtherFunctions + $AADGroupMembersLimit = $using:AADGroupMembersLimit + $azAPICallConf = $using:azAPICallConf + #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 + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:getGroupmembers = $using:funcGetGroupmembers + #endregion UsingVARs - 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 + $rndom = Get-Random -Minimum 10 -Maximum 750 + start-sleep -Millisecond $rndom - } - Write-Host " $scopeDisplayName ($scopeId); policyAssignment '$($L1mgmtGroupSubPolicyAssignment.Id)' policyDefinition (PolicySet) could not be found: '$($policySetDefinitionId)'" - } + $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' - $PolicyAssignmentScope = $L1mgmtGroupSubPolicyAssignment.Properties.Scope - if ($PolicyAssignmentScope -like "/providers/Microsoft.Management/managementGroups/*") { - $PolicyAssignmentScopeMgSubRg = "Mg" + 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 { - $splitPolicyAssignmentScope = ($PolicyAssignmentScope).Split('/') - switch (($splitPolicyAssignmentScope).Count - 1) { - #sub - 2 { - $PolicyAssignmentScopeMgSubRg = "Sub" - } - 4 { - $PolicyAssignmentScopeMgSubRg = "Rg" - } - Default { - $PolicyAssignmentScopeMgSubRg = "unknown" - } - } + getGroupmembers -aadGroupId $aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId -aadGroupDisplayName $aadGroupIdWithRoleAssignment.RoleAssignmentIdentityDisplayname } + } - $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 + $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) + #[System.GC]::Collect() + } + else { + Write-Host " processing $($aadGroupsCount) AAD Groups with Role assignments" + } - if ($L1mgmtGroupSubPolicyAssignment.identity) { - $PolicyAssignmentIdentity = $L1mgmtGroupSubPolicyAssignment.identity.principalId - } - else { - $PolicyAssignmentIdentity = "n/a" - } + $arrayGroupRequestResourceNotFoundCount = ($arrayGroupRequestResourceNotFound).Count + if ($arrayGroupRequestResourceNotFoundCount -gt 0) { + Write-Host "$arrayGroupRequestResourceNotFoundCount Groups could not be checked for Memberships" + } - $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 - } - } + Write-Host " Collected $($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 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 { - if (($L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message) { - $nonComplianceMessage = ($L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message + #region UsingVARs + $currentDateUTC = $using:currentDateUTC + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $arrayApplicationRequestResourceNotFound = $using:arrayApplicationRequestResourceNotFound + $htAppDetails = $using:htAppDetails + $htServicePrincipals = $using:htServicePrincipals + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #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 { - $nonComplianceMessage = "" - } + $script:htAppDetails.($sp.id) = @{} + $script:htAppDetails.($sp.id).servicePrincipalType = $sp.servicePrincipalType + $script:htAppDetails.($sp.id).spGraphDetails = $sp + $script:htAppDetails.($sp.id).appGraphDetails = $getApplication - $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) ")" + $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 } - $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 ` - -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 + $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 + } + } } - } - } - $returnObject = @{} - if ($addRowToTableDone) { - $returnObject."addRowToTableDone" = @{} + } -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)" } - return $returnObject } -$funcDataCollectionPolicyAssignmentsSub = $function:DataCollectionPolicyAssignmentsSub.ToString() - -function DataCollectionRoleDefinitions { +function processDataCollection { [CmdletBinding()]Param( - [string]$TargetMgOrSub, - [string]$scopeId, - [string]$scopeDisplayName + [string]$mgId ) - $currentTask = "Custom Role definitions $($TargetMgOrSub) '$($childMgSubDisplayName)' ('$scopeId')" - if ($TargetMgOrSub -eq "Sub") { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-07-01&`$filter=type eq 'CustomRole'" - } - if ($TargetMgOrSub -eq "MG") { - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-07-01&`$filter=type eq 'CustomRole'" - } - $method = "GET" - $scopeCustomRoleDefinitions = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + Write-Host ' CustomDataCollection ManagementGroups' + $startMgLoop = Get-Date - foreach ($scopeCustomRoleDefinition in $scopeCustomRoleDefinitions) { - if (-not $($htCacheDefinitionsRole).($scopeCustomRoleDefinition.name)) { + $allManagementGroupsFromEntitiesChildOfRequestedMg = $arrayEntitiesFromAPI.where( { $_.type -eq 'Microsoft.Management/managementGroups' -and ($_.Name -eq $mgId -or $_.properties.parentNameChain -contains $mgId) }) + $allManagementGroupsFromEntitiesChildOfRequestedMgCount = ($allManagementGroupsFromEntitiesChildOfRequestedMg).Count - 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 - } + $mgBatch = ($allManagementGroupsFromEntitiesChildOfRequestedMg | Group-Object -Property { ($_.properties.parentNameChain).Count }) | Sort-Object -Property Name + foreach ($batchLevel in $mgBatch) { + Write-Host " Processing Management Groups L$($batchLevel.Name) ($($batchLevel.Count) Management Groups)" - $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 + showMemoryUsage - #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 - } + $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 + #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 + $arrayAPICallTrackingCustomDataCollection = $using:arrayAPICallTrackingCustomDataCollection + $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 + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop } - } - } -} -$funcDataCollectionRoleDefinitions = $function:DataCollectionRoleDefinitions.ToString() - -function DataCollectionRoleAssignmentsMG { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $hierarchyLevel, - $mgParentId, - $mgParentName, - $mgAscSecureScoreResult - ) + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:addRowToTable = $using:funcAddRowToTable + $function:namingValidation = $using:funcNamingValidation + $function:resolveObjectIds = $using:funcResolveObjectIds + + #$function:dataCollectionFunctions = $using:funcdataCollectionFunctions + + $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 - $addRowToTableDone = $false - #PIM MGRoleAssignmentSchedules - $currentTask = "Role assignment schedules API '$($scopeDisplayName)' ('$($scopeId)')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentSchedules?api-version=2020-10-01-preview" - $method = "GET" - $roleAssignmentSchedulesFromAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" -getRoleAssignmentSchedules $true + #endregion usingVARS + $builtInPolicyDefinitionsCount = $using:builtInPolicyDefinitionsCount - if ($roleAssignmentSchedulesFromAPI -eq "ResourceNotOnboarded" -or $roleAssignmentSchedulesFromAPI -eq "TenantNotOnboarded" -or $roleAssignmentSchedulesFromAPI -eq "InvalidResourceType") { - #Write-Host "Scope '$($scopeDisplayName)' ('$scopeId') not onboarded in PIM" - } - else { - $roleAssignmentSchedules = ($roleAssignmentSchedulesFromAPI.where( { -not [string]::IsNullOrEmpty($_.properties.roleAssignmentScheduleRequestId) })) - $roleAssignmentSchedulesCount = $roleAssignmentSchedules.Count - if ($roleAssignmentSchedulesCount -gt 0) { - $htRoleAssignmentsPIM = @{} - foreach ($roleAssignmentSchedule in $roleAssignmentSchedules) { - $keyName = "$(($roleAssignmentSchedule.properties.scope).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.principal.id).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.roleDefinition.id).ToLower())" - $htRoleAssignmentsPIM.($keyName) = $roleAssignmentSchedule.properties - } - } - } + $addRowToTableDone = $false - #RoleAssignment API MG - $currentTask = "Role assignments API '$($scopeDisplayName)' ('$($scopeId)')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01" - $method = "GET" - $roleAssignmentsFromAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + $MgDetailThis = $htManagementGroupsMgPath.($mgdetail.Name) + $MgParentId = $MgDetailThis.Parent + $hierarchyLevel = $MgDetailThis.ParentNameChainCount - 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 ($MgParentId -eq '__TenantRoot__') { + $MgParentId = 'TenantRoot' + $MgParentName = $MgParentId + } + else { + $MgParentName = $htManagementGroupsMgPath.($MgParentId).DisplayName } - } - - if ($principalsToResolve.Count -gt 0) { - ResolveObjectIds -objectIds $principalsToResolve - } - } - $L0mgmtGroupRoleAssignments = $roleAssignmentsFromAPI + $rndom = Get-Random -Minimum 10 -Maximum 750 + start-sleep -Millisecond $rndom + $startMgLoopThis = Get-Date - $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'].HierarchyMapOnly -eq $false) { - if ($htParameters.LargeTenant -eq $true -or $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() + #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 + } + } - $keyName = "$(($L0mgmtGroupRoleAssignment.properties.scope).ToLower())-$(($L0mgmtGroupRoleAssignment.properties.principalId).ToLower())-$(($L0mgmtGroupRoleAssignment.properties.roleDefinitionId).ToLower())" - if ($htRoleAssignmentsPIM.($keyName)) { - $hlperPim = $htRoleAssignmentsPIM.($keyName) - $pim = "true" - $pimAssignmentType = $hlperPim.assignmentType - $pimSlotStart = $($hlperPim.startDateTime) - if ($hlperPim.endDateTime) { - $pimSlotEnd = $($hlperPim.endDateTime) - } - else { - $pimSlotEnd = "eternity" - } - } - else { - $pim = "false" - $pimAssignmentType = "" - $pimSlotStart = "" - $pimSlotEnd = "" - } + $targetMgOrSub = 'MG' + $baseParameters = @{ + scopeId = $mgdetail.Name + scopeDisplayName = $mgdetail.properties.displayName + } - if (-not $htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace ".*/")) { - $script:htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace ".*/") = @{} - $script:htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace ".*/").assignment = $L0mgmtGroupRoleAssignment - } + #ManagementGroupASCSecureScore + $mgAscSecureScoreResult = DataCollectionMGSecureScore -Id $mgdetail.Name - $roleDefinitionId = $L0mgmtGroupRoleAssignment.properties.roleDefinitionId - $roleDefinitionIdGuid = $roleDefinitionId -replace ".*/" + $addRowToTableParameters = @{ + hierarchyLevel = $hierarchyLevel + mgParentId = $mgParentId + mgParentName = $mgParentName + mgAscSecureScoreResult = $mgAscSecureScoreResult + } - if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) { - $roleAssignmentsRoleDefinition = "" - $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'" - } - else { - $roleAssignmentsRoleDefinition = ($htCacheDefinitionsRole).($roleDefinitionIdGuid) - $roleDefinitionName = $roleAssignmentsRoleDefinition.Name - } + #mg diag + DataCollectionDiagnosticsMG @baseParameters - $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 ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + #MGPolicyCompliance + DataCollectionPolicyComplianceStates @baseParameters -TargetMgOrSub $targetMgOrSub + } - 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 - }) + #MGBlueprintDefinitions + $functionReturn = DataCollectionBluePrintDefinitionsMG @baseParameters @addRowToTableParameters + if ($functionReturn.'addRowToTableDone') { + $addRowToTableDone = $true + } - $htTemp = @{} - $htTemp.Assignment = $arrayRoleAssignment + #MGPolicyExemptions + DataCollectionPolicyExemptions @baseParameters -TargetMgOrSub $targetMgOrSub - 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 - } + #MGPolicyDefinitions + $functionReturn = DataCollectionPolicyDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub + $policyDefinitionsScopedCount = $functionReturn.'PolicyDefinitionsScopedCount' - if (($htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).displayName).length -eq 0) { - $roleAssignmentIdentityDisplayname = "n/a" - } - else { - if ($htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).type -eq "User") { - if ($htParameters.DoNotShowRoleAssignmentsUserData -eq $false) { - $roleAssignmentIdentityDisplayname = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).displayName + #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 } - else { - $roleAssignmentIdentityDisplayname = "scrubbed" + + $scopedPolicyCounts = @{ + policyDefinitionsScopedCount = $policyDefinitionsScopedCount + policySetDefinitionsScopedCount = $policySetDefinitionsScopedCount } - } - 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 ($htParameters.DoNotShowRoleAssignmentsUserData -eq $false) { - $roleAssignmentIdentitySignInName = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName + + #MgPolicyAssignments + $functionReturn = DataCollectionPolicyAssignmentsMG @baseParameters @addRowToTableParameters @scopedPolicyCounts + if ($functionReturn.'addRowToTableDone') { + $addRowToTableDone = $true } - else { - $roleAssignmentIdentitySignInName = "scrubbed" + + #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 { - $roleAssignmentIdentitySignInName = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName + addRowToTable ` + -level $hierarchyLevel ` + -mgName $mgdetail.properties.displayName ` + -mgId $mgdetail.Name ` + -mgParentId $mgParentId ` + -mgParentName $mgParentName ` + -mgASCSecureScore $mgAscSecureScoreResult } - } - $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 = "" + $endMgLoopThis = Get-Date + $null = $script:customDataCollectionDuration.Add([PSCustomObject]@{ + Type = 'Mg' + Id = $mgdetail.Name + DurationSec = (NEW-TIMESPAN -Start $startMgLoopThis -End $endMgLoopThis).TotalSeconds + }) - 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 + $null = $script:arrayDataCollectionProgressMg.Add($mgdetail.Name) + $progressCount = ($arrayDataCollectionProgressMg).Count + Write-Host " $($progressCount)/$($allManagementGroupsFromEntitiesChildOfRequestedMgCount) Management Groups processed" - $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 + } -ThrottleLimit $ThrottleLimit + #[System.GC]::Collect() } - $returnObject = @{} - if ($addRowToTableDone) { - $returnObject."addRowToTableDone" = @{} - } - return $returnObject -} -$funcDataCollectionRoleAssignmentsMG = $function:DataCollectionRoleAssignmentsMG.ToString() + $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)" -function DataCollectionRoleAssignmentsSub { - [CmdletBinding()]Param( - [string]$scopeId, - [string]$scopeDisplayName, - $hierarchyLevel, - $childMgDisplayName, - $childMgId, - $childMgParentId, - $childMgParentName, - $mgAscSecureScoreResult, - $subscriptionQuotaId, - $subscriptionState, - $subscriptionASCSecureScore, - $subscriptionTags, - $subscriptionTagsCount - ) + #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 + } + } - $addRowToTableDone = $false - #Usage - $currentTask = "Role assignments usage metrics '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentsUsageMetrics?api-version=2019-08-01-preview" - $method = "GET" - $roleAssignmentsUsage = AzAPICall -uri $uri -method $method -currentTask $currentTask -listenOn "Content" -caller "CustomDataCollection" - $script:htSubscriptionsRoleAssignmentLimit.($scopeId) = $roleAssignmentsUsage.roleAssignmentsLimit + #region SUBSCRIPTION + Write-Host ' CustomDataCollection Subscriptions' + $subsExcludedStateCount = ($outOfScopeSubscriptions.where( { $_.outOfScopeReason -like 'State*' } )).Count + $subsExcludedWhitelistCount = ($outOfScopeSubscriptions.where( { $_.outOfScopeReason -like 'QuotaId*' } )).Count + if ($subsExcludedStateCount -gt 0) { + Write-Host " CustomDataCollection $($subsExcludedStateCount) Subscriptions excluded (State != enabled)" + } + if ($subsExcludedWhitelistCount -gt 0) { + Write-Host " CustomDataCollection $($subsExcludedWhitelistCount) Subscriptions excluded (not in quotaId whitelist: '$($SubscriptionQuotaIdWhitelist -join ', ')' OR is AAD_ quotaId)" + } + Write-Host " CustomDataCollection Subscriptions will process $subsToProcessInCustomDataCollectionCount of $childrenSubscriptionsCount" - #PIM SubscriptionRoleAssignmentSchedules - $currentTask = "Role assignment schedules API '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentSchedules?api-version=2020-10-01-preview" - $method = "GET" - $roleAssignmentSchedulesFromAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" -getRoleAssignmentSchedules $true + $startSubLoop = Get-Date + if ($subsToProcessInCustomDataCollectionCount -gt 0) { - if ($roleAssignmentSchedulesFromAPI -eq "ResourceNotOnboarded" -or $roleAssignmentSchedulesFromAPI -eq "TenantNotOnboarded" -or $roleAssignmentSchedulesFromAPI -eq "InvalidResourceType") { - #Write-Host "Scope '$($scopeDisplayName)' ('$scopeId') not onboarded in PIM" - } - else { - $roleAssignmentSchedules = ($roleAssignmentSchedulesFromAPI.where( { -not [string]::IsNullOrEmpty($_.properties.roleAssignmentScheduleRequestId) })) - $roleAssignmentSchedulesCount = $roleAssignmentSchedules.Count - if ($roleAssignmentSchedulesCount -gt 0) { - $htRoleAssignmentsPIM = @{} - foreach ($roleAssignmentSchedule in $roleAssignmentSchedules) { - $keyName = "$(($roleAssignmentSchedule.properties.scope).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.principal.id).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.roleDefinition.id).ToLower())" - $htRoleAssignmentsPIM.($keyName) = $roleAssignmentSchedule.properties - } + $counterBatch = [PSCustomObject] @{ Value = 0 } + $batchSize = 100 + if ($subsToProcessInCustomDataCollectionCount -gt 500) { + $batchSize = 200 } - } - - #RoleAssignment API Sub - $currentTask = "Role assignments API '$($scopeDisplayName)' ('$scopeId')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$scopeId/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01" - $method = "GET" - $roleAssignmentsFromAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + Write-Host " Subscriptions Batch size: $batchSize" - $baseRoleAssignments = [System.Collections.ArrayList]@() - if ($roleAssignmentsFromAPI.Count -gt 0) { - foreach ($roleAssignmentFromAPI in $roleAssignmentsFromAPI) { + $subscriptionsBatch = $subsToProcessInCustomDataCollection | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) } + $batchCnt = 0 + foreach ($batch in $subscriptionsBatch) { + #[System.GC]::Collect() + $startBatch = Get-Date + $batchCnt++ + Write-Host " processing Batch #$batchCnt/$(($subscriptionsBatch | Measure-Object).Count) ($(($batch.Group | Measure-Object).Count) Subscriptions)" + showMemoryUsage - if ($roleAssignmentFromAPI.id -match "/subscriptions/$($scopeId)/") { - if (-not $htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentFromAPI.id -replace ".*/")) { - $null = $baseRoleAssignments.Add($roleAssignmentFromAPI) + $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 + #Array&HTs + $newTable = $using:newTable + $resourcesAll = $using:resourcesAll + $resourcesIdsAll = $using:resourcesIdsAll + $resourceGroupsAll = $using:resourceGroupsAll + $customDataCollectionDuration = $using:customDataCollectionDuration + $htSubscriptionsMgPath = $using:htSubscriptionsMgPath + $htManagementGroupsMgPath = $using:htManagementGroupsMgPath + $htResourceProvidersAll = $using:htResourceProvidersAll + $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 + $arrayAPICallTrackingCustomDataCollection = $using:arrayAPICallTrackingCustomDataCollection + $arrayDiagnosticSettingsMgSub = $using:arrayDiagnosticSettingsMgSub + $htMgASCSecureScore = $using:htMgASCSecureScore + $htRoleAssignmentsFromAPIInheritancePrevention = $using:htRoleAssignmentsFromAPIInheritancePrevention + $htNamingValidation = $using:htNamingValidation + $htPrincipals = $using:htPrincipals + $htServicePrincipals = $using:htServicePrincipals + $htUserTypesGuest = $using:htUserTypesGuest + $arrayDefenderPlans = $using:arrayDefenderPlans + $arrayDefenderPlansSubscriptionNotRegistered = $using:arrayDefenderPlansSubscriptionNotRegistered + $arrayUserAssignedIdentities4Resources = $using:arrayUserAssignedIdentities4Resources + $htSubscriptionsRoleAssignmentLimit = $using:htSubscriptionsRoleAssignmentLimit + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop } else { - $null = $baseRoleAssignments.Add($htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentFromAPI.id -replace ".*/").assignment) - } - } - else { - $null = $baseRoleAssignments.Add($roleAssignmentFromAPI) - } - } - } + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:addRowToTable = $using:funcAddRowToTable + $function:namingValidation = $using:funcNamingValidation + $function:resolveObjectIds = $using:funcResolveObjectIds + $function:dataCollectionMGSecureScore = $using:funcDataCollectionMGSecureScore + $function:dataCollectionDefenderPlans = $using:funcDataCollectionDefenderPlans + $function:dataCollectionDiagnosticsSub = $using:funcDataCollectionDiagnosticsSub + $function:dataCollectionResources = $using:funcDataCollectionResources + $function:dataCollectionResourceGroups = $using:funcDataCollectionResourceGroups + $function:dataCollectionResourceProviders = $using:funcDataCollectionResourceProviders + $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 + #endregion UsingVARs - if ($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 - } - } + $addRowToTableDone = $false - if ($principalsToResolve.Count -gt 0) { - ResolveObjectIds -objectIds $principalsToResolve - } - } + $childMgSubId = $childMgSubDetail.subscriptionId + $childMgSubDisplayName = $childMgSubDetail.subscriptionName + $hierarchyInfo = $htSubscriptionsMgPath.($childMgSubDetail.subscriptionId) + $hierarchyLevel = $hierarchyInfo.level + $childMgId = $hierarchyInfo.Parent + $childMgDisplayName = $hierarchyInfo.ParentName + $childMgMgPath = $hierarchyInfo.pathDelimited + $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) { - $L1mgmtGroupSubRoleAssignments = $baseRoleAssignments + $script:htNamingValidation.Subscription.($childMgSubId) = @{} + $script:htNamingValidation.Subscription.($childMgSubId).displayNameInvalidChars = ($namingValidationResult -join '') + $script:htNamingValidation.Subscription.($childMgSubId).displayName = $childMgSubDisplayName + } + } - if ($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC -eq $true) { - foreach ($L1mgmtGroupSubRoleAssignmentOnRg in $L1mgmtGroupSubRoleAssignments.where( { $_.id -match "/subscriptions/$($scopeId)/resourcegroups/" } )) { - if (-not ($htCacheAssignmentsRBACOnResourceGroupsAndResources).($L1mgmtGroupSubRoleAssignmentOnRg.id)) { + #$rndom = Get-Random -Minimum 10 -Maximum 750 + #start-sleep -Millisecond $rndom + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + $currentSubscription = $htAllSubscriptionsFromAPI.($childMgSubId).subDetails + $subscriptionQuotaId = $currentSubscription.subscriptionPolicies.quotaId + $subscriptionState = $currentSubscription.state - $roleDefinitionId = $L1mgmtGroupSubRoleAssignmentOnRg.properties.roleDefinitionId - $roleDefinitionIdGuid = $roleDefinitionId -replace ".*/" + $targetMgOrSub = 'Sub' + $baseParameters = @{ + scopeId = $childMgSubId + scopeDisplayName = $childMgSubDisplayName + } - if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) { - $roleAssignmentsRoleDefinition = "" - $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'" - } - else { - $roleAssignmentsRoleDefinition = ($htCacheDefinitionsRole).($roleDefinitionIdGuid) - $roleDefinitionName = $roleAssignmentsRoleDefinition.Name - } + if (-not $azAPICallConf['htParameters'].ManagementGroupsOnly) { + #mgSecureScore + $mgAscSecureScoreResult = DataCollectionMGSecureScore -Id $childMgId - #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 - }) + #defenderPlans + $dataCollectionDefenderPlansParameters = @{ + ChildMgMgPath = $childMgMgPath + } + DataCollectionDefenderPlans @baseParameters @dataCollectionDefenderPlansParameters - ($script:htCacheAssignmentsRBACOnResourceGroupsAndResources).($L1mgmtGroupSubRoleAssignmentOnRg.id) = $arrayRoleAssignment - } - } - } + #diagnostics + $dataCollectionDiagnosticsSubParameters = @{ + ChildMgMgPath = $childMgMgPath + ChildMgId = $childMgId + } + DataCollectionDiagnosticsSub @baseParameters @dataCollectionDiagnosticsSubParameters - if ($htParameters.LargeTenant -eq $true -or $htParameters.RBACAtScopeOnly -eq $true) { - if ($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC -eq $false) { - $assignmentsScope = $L1mgmtGroupSubRoleAssignments - } - else { - $assignmentsScope = $L1mgmtGroupSubRoleAssignments.where( { $_.properties.Scope -eq "/subscriptions/$($scopeId)" } ) - } - } - else { - if ($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC -eq $false) { - $assignmentsScope = $L1mgmtGroupSubRoleAssignments - } - else { - $assignmentsScope = $L1mgmtGroupSubRoleAssignments.where( { $_.id -notmatch "/subscriptions/$($scopeId)/resourcegroups/" } ) - } - } - foreach ($L1mgmtGroupSubRoleAssignment in $assignmentsScope) { + if ($azAPICallConf['htParameters'].NoResources -eq $false) { + #resources + $dataCollectionResourcesParameters = @{ + ChildMgMgPath = $childMgMgPath + } + DataCollectionResources @baseParameters @dataCollectionResourcesParameters + } - $roleAssignmentId = ($L1mgmtGroupSubRoleAssignment.id).ToLower() - $roleDefinitionId = $L1mgmtGroupSubRoleAssignment.properties.roleDefinitionId - $roleDefinitionIdGuid = $roleDefinitionId -replace ".*/" + #resourceGroups + DataCollectionResourceGroups @baseParameters - if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) { - $roleAssignmentsRoleDefinition = "" - $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'" - } - else { - $roleAssignmentsRoleDefinition = ($htCacheDefinitionsRole).($roleDefinitionIdGuid) - $roleDefinitionName = $roleAssignmentsRoleDefinition.Name - } + #resourceProviders + DataCollectionResourceProviders @baseParameters - $roleAssignmentIdentityObjectId = $L1mgmtGroupSubRoleAssignment.properties.principalId - $roleAssignmentIdentityObjectType = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).type - $roleAssignmentScope = $L1mgmtGroupSubRoleAssignment.properties.scope - $roleAssignmentScopeName = $roleAssignmentScope -replace '.*/' + #resourceLocks + DataCollectionResourceLocks @baseParameters - 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] - } + #tags + $subscriptionTagsReturn = DataCollectionTags @baseParameters + $subscriptionTags = $subscriptionTagsReturn.subscriptionTags + $subscriptionTagsCount = $subscriptionTagsReturn.subscriptionTagsCount - $keyName = "$(($L1mgmtGroupSubRoleAssignment.properties.scope).ToLower())-$(($L1mgmtGroupSubRoleAssignment.properties.principalId).ToLower())-$(($L1mgmtGroupSubRoleAssignment.properties.roleDefinitionId).ToLower())" - if ($htRoleAssignmentsPIM.($keyName)) { - $hlperPim = $htRoleAssignmentsPIM.($keyName) - $pim = "true" - $pimAssignmentType = $hlperPim.assignmentType - $pimSlotStart = $($hlperPim.startDateTime) - if ($hlperPim.endDateTime) { - $pimSlotEnd = $($hlperPim.endDateTime) - } - else { - $pimSlotEnd = "eternity" - } - } - else { - $pim = "false" - $pimAssignmentType = "" - $pimSlotStart = "" - $pimSlotEnd = "" - } + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + #SubscriptionPolicyCompliance + DataCollectionPolicyComplianceStates @baseParameters -TargetMgOrSub $targetMgOrSub + } - if ($roleAssignmentId -like "/subscriptions/$($scopeId)/*") { + #SubscriptionASCSecureScore + $subscriptionASCSecureScore = DataCollectionASCSecureScoreSub @baseParameters - #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 - }) + $addRowToTableParameters = @{ + hierarchyLevel = $hierarchyLevel + childMgDisplayName = $childMgDisplayName + childMgId = $childMgId + childMgParentId = $childMgParentId + childMgParentName = $childMgParentName + mgAscSecureScoreResult = $mgAscSecureScoreResult + subscriptionQuotaId = $subscriptionQuotaId + subscriptionState = $subscriptionState + subscriptionASCSecureScore = $subscriptionASCSecureScore + subscriptionTags = $subscriptionTags + subscriptionTagsCount = $subscriptionTagsCount + } - $htTemp = @{} - $htTemp.Assignment = $arrayRoleAssignment + #SubscriptionBlueprintDefinitions + $functionReturn = DataCollectionBluePrintDefinitionsSub @baseParameters @addRowToTableParameters + if ($functionReturn.'addRowToTableDone') { + $addRowToTableDone = $true + } - $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 - } + #SubscriptionBlueprintAssignments + $functionReturn = DataCollectionBluePrintAssignmentsSub @baseParameters @addRowToTableParameters + if ($functionReturn.'addRowToTableDone') { + $addRowToTableDone = $true + } + #SubscriptionPolicyExemptions + DataCollectionPolicyExemptions @baseParameters -TargetMgOrSub $targetMgOrSub - if (($htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).displayName).length -eq 0) { - $roleAssignmentIdentityDisplayname = "n/a" - } - else { - if ($htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).type -eq "User") { - if ($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 ($htParameters.DoNotShowRoleAssignmentsUserData -eq $false) { - $roleAssignmentIdentitySignInName = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).signInName + #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 + } + } + + if ($addRowToTableDone -ne $true) { + addRowToTable ` + -level $hierarchyLevel ` + -mgName $childMgDisplayName ` + -mgId $childMgId ` + -mgParentId $childMgParentId ` + -mgParentName $childMgParentName ` + -mgASCSecureScore $mgAscSecureScoreResult ` + -Subscription $childMgSubDisplayName ` + -SubscriptionId $childMgSubId ` + -SubscriptionASCSecureScore $subscriptionASCSecureScore + } } else { - $roleAssignmentIdentitySignInName = "scrubbed" + addRowToTable ` + -level $hierarchyLevel ` + -mgName $childMgDisplayName ` + -mgId $childMgId ` + -mgParentId $childMgParentId ` + -mgParentName $childMgParentName ` + -mgASCSecureScore $mgAscSecureScoreResult ` + -Subscription $childMgSubDisplayName ` + -SubscriptionId $childMgSubId ` + -SubscriptionASCSecureScore $subscriptionASCSecureScore } - } - else { - $roleAssignmentIdentitySignInName = $htPrincipals.($L1mgmtGroupSubRoleAssignment.properties.principalId).signInName - } - } + $endSubLoopThis = Get-Date + $null = $script:customDataCollectionDuration.Add([PSCustomObject]@{ + Type = 'SUB' + Id = $childMgSubId + DurationSec = (NEW-TIMESPAN -Start $startSubLoopThis -End $endSubLoopThis).TotalSeconds + }) - $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 - } + $null = $script:arrayDataCollectionProgressSub.Add($childMgSubId) + $progressCount = ($arrayDataCollectionProgressSub).Count + Write-Host " $($progressCount)/$($subsToProcessInCustomDataCollectionCount) Subscriptions processed" - $createdBy = "" - $createdOn = "" - $createdOnUnformatted = $null - $updatedBy = "" - $updatedOn = "" + } -ThrottleLimit $ThrottleLimit - 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 + $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)" } - $createdOnUnformatted = $L1mgmtGroupSubRoleAssignment.properties.createdOn + #[System.GC]::Collect() - $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 - } + $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)" - $returnObject = @{} - if ($addRowToTableDone) { - $returnObject."addRowToTableDone" = @{} + #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)" } - return $returnObject -} -$funcDataCollectionRoleAssignmentsSub = $function:DataCollectionRoleAssignmentsSub.ToString() - -#endregion functions4DataCollection + #endregion SUBSCRIPTION -#region dataCollection -function DataCollection($mgId) { - Write-Host " CustomDataCollection ManagementGroups" - $startMgLoop = Get-Date + $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))" - $allManagementGroupsFromEntitiesChildOfRequestedMg = $arrayEntitiesFromAPI.where( { $_.type -eq "Microsoft.Management/managementGroups" -and ($_.Name -eq $mgId -or $_.properties.parentNameChain -contains $mgId) }) - $allManagementGroupsFromEntitiesChildOfRequestedMgCount = ($allManagementGroupsFromEntitiesChildOfRequestedMg).Count + #APITracking + $APICallTrackingCount = ($arrayAPICallTrackingCustomDataCollection).Count + $APICallTrackingRetriesCount = ($arrayAPICallTrackingCustomDataCollection.where( { $_.TryCounter -gt 1 } )).Count + $APICallTrackingRestartDueToDuplicateNextlinkCounterCount = ($arrayAPICallTrackingCustomDataCollection.where( { $_.RestartDueToDuplicateNextlinkCounter -gt 0 } )).Count + Write-Host "Collecting custom data APICalls (Management) total count: $APICallTrackingCount ($APICallTrackingRetriesCount retries; $APICallTrackingRestartDueToDuplicateNextlinkCounterCount nextLinkReset)" - $mgBatch = ($allManagementGroupsFromEntitiesChildOfRequestedMg | Group-Object -Property { ($_.properties.parentNameChain).Count }) | Sort-Object -Property Name - foreach ($batchLevel in $mgBatch) { - Write-Host " Processing Management Groups L$($batchLevel.Name) ($($batchLevel.Count) Management Groups)" - $batchLevel.Group | ForEach-Object -Parallel { - $mgdetail = $_ - #region UsingVARs - #Parameters MG&Sub related - $CsvDelimiter = $using:CsvDelimiter - $CsvDelimiterOpposite = $using:CsvDelimiterOpposite - $ManagementGroupId = $using:ManagementGroupId - #fromOtherFunctions - $arrayAzureManagementEndPointUrls = $using:arrayAzureManagementEndPointUrls - $checkContext = $using:checkContext - $htAzureEnvironmentRelatedUrls = $using:htAzureEnvironmentRelatedUrls - $htBearerAccessToken = $using:htBearerAccessToken - #Array&HTs - $htParameters = $using:htParameters - $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 - #$allManagementGroupsFromEntitiesChildOfRequestedMg = $using:allManagementGroupsFromEntitiesChildOfRequestedMg - $allManagementGroupsFromEntitiesChildOfRequestedMgCount = $using:allManagementGroupsFromEntitiesChildOfRequestedMgCount - $arrayDataCollectionProgressMg = $using:arrayDataCollectionProgressMg - $arrayAPICallTracking = $using:arrayAPICallTracking - $arrayAPICallTrackingCustomDataCollection = $using:arrayAPICallTrackingCustomDataCollection - $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 - #Functions - $function:AzAPICall = $using:funcAzAPICall - $function:CreateBearerToken = $using:funcCreateBearerToken - $function:AddRowToTable = $using:funcAddRowToTable - $function:GetJWTDetails = $using:funcGetJWTDetails - $function:NamingValidation = $using:funcNamingValidation - $function:ResolveObjectIds = $using:funcResolveObjectIds - $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 - #endregion usingVARS - $builtInPolicyDefinitionsCount = $using:builtInPolicyDefinitionsCount + if ($azAPICallConf['htParameters'].NoResources -eq $false) { - $addRowToTableDone = $false + $script:resourcesAllGroupedBySubcriptionId = $resourcesAll | Group-Object -property subscriptionId - $MgDetailThis = $htManagementGroupsMgPath.($mgdetail.Name) - $MgParentId = $MgDetailThis.Parent - $hierarchyLevel = $MgDetailThis.ParentNameChainCount + $totaldurationSubResourcesAddArray = ($arraySubResourcesAddArrayDuration.DurationSec | Measure-Object -sum).Sum + Write-Host "Collecting custom data total duration writing the subResourcesArray: $totaldurationSubResourcesAddArray seconds" - if ($MgParentId -eq "__TenantRoot__") { - $MgParentId = "TenantRoot" - $MgParentName = $MgParentId - } - else { - $MgParentName = $htManagementGroupsMgPath.($MgParentId).DisplayName + if (-not $azAPICallConf['htParameters'].HierarchyMapOnly -and -not $azAPICallConf['htParameters'].ManagementGroupsOnly) { + if (-not $NoCsvExport) { + #DataCollection Export of All Resources + 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 } + } + } - $rndom = Get-Random -Minimum 10 -Maximum 750 - start-sleep -Millisecond $rndom - $startMgLoopThis = Get-Date - - if ($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 - } + 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' + } + } - #mg diag - DataCollectionDiagnosticsMG @baseParameters + 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' - if ($htParameters.NoPolicyComplianceStates -eq $false) { - #MGPolicyCompliance - DataCollectionPolicyComplianceStates @baseParameters -TargetMgOrSub $targetMgOrSub - } + $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) { - #MGBlueprintDefinitions - $functionReturn = DataCollectionBluePrintDefinitionsMG @baseParameters @addRowToTableParameters - if ($functionReturn."addRowToTableDone") { - $addRowToTableDone = $true - } + 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 + } - #MGPolicyExemptions - DataCollectionPolicyExemptions @baseParameters -TargetMgOrSub $targetMgOrSub + if ($L0mgmtGroupPolicyAssignment.identity) { + $PolicyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId + } + else { + $PolicyAssignmentIdentity = 'n/a' + } - #MGPolicyDefinitions - $functionReturn = DataCollectionPolicyDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub - $policyDefinitionsScopedCount = $functionReturn."PolicyDefinitionsScopedCount" + if ($Def.Type -eq 'Custom') { + $policyDefintionScope = $Def.Scope + $policyDefintionScopeMgSub = $Def.ScopeMgSub + $policyDefintionScopeId = $Def.ScopeId + } + else { + $policyDefintionScope = 'n/a' + $policyDefintionScopeMgSub = 'n/a' + $policyDefintionScopeId = 'n/a' + } - #MGPolicySetDefinitions - $functionReturn = DataCollectionPolicySetDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub - $policySetDefinitionsScopedCount = $functionReturn."PolicySetDefinitionsScopedCount" + $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 (-not $htMgAtScopePoliciesScoped.($mgdetail.Name)) { - $script:htMgAtScopePoliciesScoped.($mgdetail.Name) = @{} - $script:htMgAtScopePoliciesScoped.($mgdetail.Name).ScopedCount = $policyDefinitionsScopedCount + $policySetDefinitionsScopedCount - } + if (($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message) { + $nonComplianceMessage = ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message + } + else { + $nonComplianceMessage = '' + } - $scopedPolicyCounts = @{ - policyDefinitionsScopedCount = $policyDefinitionsScopedCount - policySetDefinitionsScopedCount = $policySetDefinitionsScopedCount - } + $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) " + } - #MgPolicyAssignments - $functionReturn = DataCollectionPolicyAssignmentsMG @baseParameters @addRowToTableParameters @scopedPolicyCounts - if ($functionReturn."addRowToTableDone") { - $addRowToTableDone = $true - } + #mgSecureScore + $mgAscSecureScoreResult = '' - #MGRoleDefinitions - DataCollectionRoleDefinitions @baseParameters -TargetMgOrSub $targetMgOrSub + 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 + } - #MGRoleAssignments - $functionReturn = DataCollectionRoleAssignmentsMG @baseParameters @addRowToTableParameters - if ($functionReturn."addRowToTableDone") { - $addRowToTableDone = $true - } + 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 ($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 - } + 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' + } - $endMgLoopThis = Get-Date - $null = $script:customDataCollectionDuration.Add([PSCustomObject]@{ - Type = "Mg" - Id = $mgdetail.Name - DurationSec = (NEW-TIMESPAN -Start $startMgLoopThis -End $endMgLoopThis).TotalSeconds - }) + $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 + } + } - $null = $script:arrayDataCollectionProgressMg.Add($mgdetail.Name) - $progressCount = ($arrayDataCollectionProgressMg).Count - Write-Host " $($progressCount)/$($allManagementGroupsFromEntitiesChildOfRequestedMgCount) Management Groups processed" + if (($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message) { + $nonComplianceMessage = ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message + } + else { + $nonComplianceMessage = '' + } - } -ThrottleLimit $ThrottleLimit - #[System.GC]::Collect() - } + $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) " + } - $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)" - - #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 + 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) { - #region SUBSCRIPTION - Write-Host " CustomDataCollection Subscriptions" - $subsExcludedStateCount = ($outOfScopeSubscriptions.where( { $_.outOfScopeReason -like "State*" } )).Count - $subsExcludedWhitelistCount = ($outOfScopeSubscriptions.where( { $_.outOfScopeReason -like "QuotaId*" } )).Count - if ($subsExcludedStateCount -gt 0) { - Write-Host " CustomDataCollection $($subsExcludedStateCount) Subscriptions excluded (State != enabled)" - } - if ($subsExcludedWhitelistCount -gt 0) { - Write-Host " CustomDataCollection $($subsExcludedWhitelistCount) Subscriptions excluded (not in quotaId whitelist: '$($SubscriptionQuotaIdWhitelist -join ", ")' OR is AAD_ quotaId)" - } - Write-Host " CustomDataCollection Subscriptions will process $subsToProcessInCustomDataCollectionCount of $childrenSubscriptionsCount" + #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 - $startSubLoop = Get-Date - if ($subsToProcessInCustomDataCollectionCount -gt 0) { + if ($roleAssignmentsFromAPI.Count -gt 0) { + $principalsToResolve = @() + $principalsToResolve = foreach ($ra in $roleAssignmentsFromAPI.properties | Sort-Object -Property principalId -Unique) { + if (-not $htPrincipals.($ra.principalId)) { + $ra.principalId + } + } - $counterBatch = [PSCustomObject] @{ Value = 0 } - $batchSize = 100 - if ($subsToProcessInCustomDataCollectionCount -gt 500) { - $batchSize = 200 + if ($principalsToResolve.Count -gt 0) { + ResolveObjectIds -objectIds $principalsToResolve + } } - Write-Host " Subscriptions Batch size: $batchSize" - $subscriptionsBatch = $subsToProcessInCustomDataCollection | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) } - $batchCnt = 0 - foreach ($batch in $subscriptionsBatch) { - #[System.GC]::Collect() - $startBatch = Get-Date - $batchCnt++ - Write-Host " processing Batch #$batchCnt/$(($subscriptionsBatch | Measure-Object).Count) ($(($batch.Group | Measure-Object).Count) Subscriptions)" + #$upperScopesRoleAssignments = GetRoleAssignments -Scope "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)" -scopeDetails "getRoleAssignments upperScopes (Mg)" + $upperScopesRoleAssignments = $roleAssignmentsFromAPI - $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 - $arrayAzureManagementEndPointUrls = $using:arrayAzureManagementEndPointUrls - $checkContext = $using:checkContext - $htAzureEnvironmentRelatedUrls = $using:htAzureEnvironmentRelatedUrls - $htBearerAccessToken = $using:htBearerAccessToken - #Array&HTs - $htParameters = $using:htParameters - $newTable = $using:newTable - $resourcesAll = $using:resourcesAll - $resourcesIdsAll = $using:resourcesIdsAll - $resourceGroupsAll = $using:resourceGroupsAll - $customDataCollectionDuration = $using:customDataCollectionDuration - $htSubscriptionsMgPath = $using:htSubscriptionsMgPath - $htManagementGroupsMgPath = $using:htManagementGroupsMgPath - $htResourceProvidersAll = $using:htResourceProvidersAll - $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 - $arrayAPICallTracking = $using:arrayAPICallTracking - $arrayAPICallTrackingCustomDataCollection = $using:arrayAPICallTrackingCustomDataCollection - $arrayDiagnosticSettingsMgSub = $using:arrayDiagnosticSettingsMgSub - $htMgASCSecureScore = $using:htMgASCSecureScore - $htRoleAssignmentsFromAPIInheritancePrevention = $using:htRoleAssignmentsFromAPIInheritancePrevention - $htNamingValidation = $using:htNamingValidation - $htPrincipals = $using:htPrincipals - $htServicePrincipals = $using:htServicePrincipals - $htUserTypesGuest = $using:htUserTypesGuest - $arrayDefenderPlans = $using:arrayDefenderPlans - $arrayDefenderPlansSubscriptionNotRegistered = $using:arrayDefenderPlansSubscriptionNotRegistered - $arrayUserAssignedIdentities4Resources = $using:arrayUserAssignedIdentities4Resources - $htSubscriptionsRoleAssignmentLimit = $using:htSubscriptionsRoleAssignmentLimit - #Functions - $function:AzAPICall = $using:funcAzAPICall - $function:CreateBearerToken = $using:funcCreateBearerToken - $function:AddRowToTable = $using:funcAddRowToTable - $function:GetJWTDetails = $using:funcGetJWTDetails - $function:NamingValidation = $using:funcNamingValidation - $function:ResolveObjectIds = $using:funcResolveObjectIds - $function:DataCollectionMGSecureScore = $using:funcDataCollectionMGSecureScore - $function:DataCollectionDefenderPlans = $using:funcDataCollectionDefenderPlans - $function:DataCollectionDiagnosticsSub = $using:funcDataCollectionDiagnosticsSub - $function:DataCollectionResources = $using:funcDataCollectionResources - $function:DataCollectionResourceGroups = $using:funcDataCollectionResourceGroups - $function:DataCollectionResourceProviders = $using:funcDataCollectionResourceProviders - $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 - #endregion UsingVARs + $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 + } - $addRowToTableDone = $false + foreach ($upperScopesRoleAssignment in $upperScopesRoleAssignments) { - $childMgSubId = $childMgSubDetail.subscriptionId - $childMgSubDisplayName = $childMgSubDetail.subscriptionName - $hierarchyInfo = $htSubscriptionsMgPath.($childMgSubDetail.subscriptionId) - $hierarchyLevel = $hierarchyInfo.level - $childMgId = $hierarchyInfo.Parent - $childMgDisplayName = $hierarchyInfo.ParentName - $childMgMgPath = $hierarchyInfo.pathDelimited - $childMgParentInfo = $htManagementGroupsMgPath.($childMgId) - $childMgParentId = $childMgParentInfo.Parent - $childMgParentName = $htManagementGroupsMgPath.($childMgParentInfo.Parent).DisplayName + $roleAssignmentId = ($upperScopesRoleAssignment.id).ToLower() - #namingValidation - if (-not [string]::IsNullOrEmpty($childMgSubDisplayName)) { - $namingValidationResult = NamingValidation -toCheck $childMgSubDisplayName - if ($namingValidationResult.Count -gt 0) { + if ($upperScopesRoleAssignment.properties.scope -ne "/providers/Microsoft.Management/managementGroups/$ManagementGroupId") { + $roleDefinitionId = $upperScopesRoleAssignment.properties.roleDefinitionId + $roleDefinitionIdGuid = $roleDefinitionId -replace '.*/' - $script:htNamingValidation.Subscription.($childMgSubId) = @{} - $script:htNamingValidation.Subscription.($childMgSubId).displayNameInvalidChars = ($namingValidationResult -join "") - $script:htNamingValidation.Subscription.($childMgSubId).displayName = $childMgSubDisplayName - } + if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) { + $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'" + } + else { + $roleDefinitionName = ($htCacheDefinitionsRole).($roleDefinitionIdGuid).Name } - #$rndom = Get-Random -Minimum 10 -Maximum 750 - #start-sleep -Millisecond $rndom - if ($htParameters.HierarchyMapOnly -eq $false) { - $currentSubscription = $htAllSubscriptionsFromAPI.($childMgSubId).subDetails - $subscriptionQuotaId = $currentSubscription.subscriptionPolicies.quotaId - $subscriptionState = $currentSubscription.state - - $targetMgOrSub = "Sub" - $baseParameters = @{ - scopeId = $childMgSubId - scopeDisplayName = $childMgSubDisplayName + 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' + } } - - if (-not $htParameters.ManagementGroupsOnly) { - #mgSecureScore - $mgAscSecureScoreResult = DataCollectionMGSecureScore -Id $childMgId - - #defenderPlans - $dataCollectionDefenderPlansParameters = @{ - ChildMgMgPath = $childMgMgPath + 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 } - DataCollectionDefenderPlans @baseParameters @dataCollectionDefenderPlansParameters - - #diagnostics - $dataCollectionDiagnosticsSubParameters = @{ - ChildMgMgPath = $childMgMgPath - ChildMgId = $childMgId + else { + $roleAssignmentIdentitySignInName = 'scrubbed' } - DataCollectionDiagnosticsSub @baseParameters @dataCollectionDiagnosticsSubParameters - + } + else { + $roleAssignmentIdentitySignInName = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).signInName + } + } + $roleAssignmentIdentityObjectId = $upperScopesRoleAssignment.properties.principalId + $roleAssignmentIdentityObjectType = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).type - if ($htParameters.NoResources -eq $false) { - #resources - $dataCollectionResourcesParameters = @{ - ChildMgMgPath = $childMgMgPath - } - DataCollectionResources @baseParameters @dataCollectionResourcesParameters - } + $roleAssignmentScope = $upperScopesRoleAssignment.properties.scope + $roleAssignmentScopeName = $roleAssignmentScope -replace '.*/' + $roleAssignmentScopeType = 'MG' - #resourceGroups - DataCollectionResourceGroups @baseParameters + $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 + } - #resourceProviders - DataCollectionResourceProviders @baseParameters + $createdBy = '' + $createdOn = '' + $createdOnUnformatted = $null + $updatedBy = '' + $updatedOn = '' - #resourceLocks - DataCollectionResourceLocks @baseParameters + 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 - #tags - $subscriptionTagsReturn = DataCollectionTags @baseParameters - $subscriptionTags = $subscriptionTagsReturn.subscriptionTags - $subscriptionTagsCount = $subscriptionTagsReturn.subscriptionTagsCount - - if ($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 - } - } - - if ($addRowToTableDone -ne $true) { - AddRowToTable ` - -level $hierarchyLevel ` - -mgName $childMgDisplayName ` - -mgId $childMgId ` - -mgParentId $childMgParentId ` - -mgParentName $childMgParentName ` - -mgASCSecureScore $mgAscSecureScoreResult ` - -Subscription $childMgSubDisplayName ` - -SubscriptionId $childMgSubId ` - -SubscriptionASCSecureScore $subscriptionASCSecureScore - } + if (($azAPICallConf['checkContext']).Tenant.Id -ne $ManagementGroupId) { + $levelToUse = (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain).Count - 1) + $toUseAsmgName = $getMgParentName + $toUseAsmgId = $getMgParentId + $toUseAsmgParentId = "'upperScopes'" + $toUseAsmgParentName = 'upperScopes' } else { - AddRowToTable ` - -level $hierarchyLevel ` - -mgName $childMgDisplayName ` - -mgId $childMgId ` - -mgParentId $childMgParentId ` - -mgParentName $childMgParentName ` - -mgASCSecureScore $mgAscSecureScoreResult ` - -Subscription $childMgSubDisplayName ` - -SubscriptionId $childMgSubId ` - -SubscriptionASCSecureScore $subscriptionASCSecureScore + $levelToUse = (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain).Count) + $toUseAsmgName = $selectedManagementGroupId.DisplayName + $toUseAsmgId = $selectedManagementGroupId.Name + $toUseAsmgParentId = 'Tenant' + $toUseAsmgParentName = 'Tenant' } - $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 + #mgSecureScore + $mgAscSecureScoreResult = '' - $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)" + 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' + } } - #[System.GC]::Collect() - - $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)" - - #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 } +function processDefinitionInsights() { + $startDefinitionInsights = Get-Date + Write-Host ' Building DefinitionInsights' -#endregion dataCollection + $md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider + $utf8 = New-Object -TypeName System.Text.UTF8Encoding -#HTML + #region definitionInsightsAzurePolicy + $htmlDefinitionInsights = [System.Text.StringBuilder]::new() + [void]$htmlDefinitionInsights.AppendLine( @' + +
+'@) -#region hierarchyMgHTML -function HierarchyMgHTML($mgChild) { - $mgDetails = $htMgDetails.($mgChild).details - $mgName = $mgDetails.mgName - $mgId = $mgDetails.MgId + #policy/policySet preQuery + #region preQuery + $htPolicyWithAssignments = @{} + $htPolicyWithAssignments.policy = @{} + $htPolicyWithAssignments.policySet = @{} - if ($mgId -eq ($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`"" + 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 { - $class = "class=`"mgnonradius`"" + #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) + } } - $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 += @" -
  • - -
    -
    -
    -"@ - if ($mgPolicyAssignmentCount -gt 0 -or $mgPolicyPolicySetScopedCount -gt 0) { - if ($mgPolicyAssignmentCount -gt 0 -and $mgPolicyPolicySetScopedCount -gt 0) { - $script:html += @" -
    - $($mgPolicyAssignmentCount) -
    -
    - $($mgPolicyPolicySetScopedCount) -
    -"@ - } - else { - if ($mgPolicyAssignmentCount -gt 0) { - $script:html += @" -
    - $($mgPolicyAssignmentCount) -
    -"@ + 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) } - if ($mgPolicyPolicySetScopedCount -gt 0) { - $script:html += @" -
    - $($mgPolicyPolicySetScopedCount) -
    -"@ + else { + $array = @() + $array += ($htPolicyWithAssignments).policy.($customPolicy.PolicyDefinitionId).Assignments + $array += $htPoliciesWithAssignmentOnRgRes.($customPolicy.PolicyDefinitionId).Assignments + ($htPolicyWithAssignments).policy.($customPolicy.PolicyDefinitionId).Assignments = $array } } } - else { - $script:html += @" -
    -"@ - } - $script:html += @" -
    - -
    -"@ - if ($mgIdRoleAssignmentCount -gt 0) { - $script:html += @" -
    - $($mgIdRoleAssignmentCount) -
    -"@ - } - else { - $script:html += @" -
    -"@ - } - $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 += @" - -"@ - } -} -#endregion hierarchyMgHTML -#region hierarchySubForMgHTML -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
  • -"@ + 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 hierarchySubForMgHTML + #endregion preQuery -#region hierarchySubForMgUlHTML -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 += @" - -"@ - } - } -} -#endregion hierarchySubForMgUlHTML + #region definitionInsightsPolicyDefinitions + $startDefinitionInsightsPolicyDefinitions = Get-Date + Write-Host ' processing DefinitionInsights Policy definitions' + $tfCount = $tenantAllPoliciesCount + $htmlTableId = 'definitionInsights_Policy' + [void]$htmlDefinitionInsights.AppendLine( @" + +
    -#region tableMgHTML -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 += @" - -
    - - -"@ - if ($mgId -eq $defaultManagementGroupId) { - $script:html += @" - -"@ - } - $script:html += @" - - - -"@ - } - 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 - } - } -} -#endregion tableMgHTML +
    + + +
    -#region tableSubForMgHTML -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 += @" - - - - +
    + + +
    -

    Highlight Management Group in HierarchyMap

    Default Management Group docs

    Management Group Name: $($mgName -replace "<", "<" -replace ">", ">")

    Management Group Id: $mgId

    Management Group Path: $mgPath

    - -
    -"@ - } - foreach ($subEntry in $subscriptions | Sort-Object -Property subscription, subscriptionId) { - #$subPath = $htSubscriptionsMgPath.($subEntry.subscriptionId).pathDelimited - if ($subscriptionLinkedCount -gt 1) { - if (-not $NoScopeInsights) { - $script:html += @" - -
    -"@ - } - } - #exactly 1 - else { - if (-not $NoScopeInsights) { - $script:html += @" - $($subEntry.subscription -replace "<", "<" -replace ">", ">") ($($subEntry.subscriptionId)) -"@ - } - } - if (-not $NoScopeInsights) { - $script:html += @" - - -"@ - } +
    + + +
    - if (-not $htParameters.ManagementGroupsOnly) { - ProcessScopeInsightsMgOrSub -mgOrSub "sub" -subscriptionId $subEntry.subscriptionId -subscriptionsMgId $mgChild - } + - if (-not $NoScopeInsights) { - $script:html += @" -

    Highlight Subscription in HierarchyMap

    -"@ - } - 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 tableSubForMgHTML -#rsi -#region ScopeInsights -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 ($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 + $cnter = 0 + $htmlDefinitionInsightshlp = $null + $htmlDefinitionInsightshlp = foreach ($policy in (($htCacheDefinitionsPolicy).Values | Sort-Object @{Expression = { $_.DisplayName } }, @{Expression = { $_.PolicyDefinitionId } })) { - if ($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 $($htParameters.NoMDfCSecureScore))" + $cnter++ + if ($cnter % 1000 -eq 0) { + Write-Host " $cnter Policy definitions processed" } - $cssClass = "mgDetailsTable" + $hasAssignments = 'false' + $assignmentsCount = 0 + $assignmentsDetailed = 'n/a' - #$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 (($htPolicyWithAssignments).policy.($policy.PolicyDefinitionId)) { + $hasAssignments = 'true' + $assignments = ($htPolicyWithAssignments).policy.($policy.PolicyDefinitionId).Assignments + $assignmentsCount = $assignments.Count - if ($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) + 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 " } - $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)*" } ) + $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 " + } - $arrayUserAssignedIdentities4ResourcesSubscription = $arrayUserAssignedIdentities4Resources.where( { $_.resourceSubscriptionId -eq $subscriptionId -or $_.miSubscriptionId -eq $subscriptionId } ) - $arrayUserAssignedIdentities4ResourcesSubscriptionCount = $arrayUserAssignedIdentities4ResourcesSubscription.Count + $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))" + } - $cssClass = "subDetailsTable" + $usedInPolicySet = 'false' + $usedInPolicySetCount = 0 + $usedInPolicySets = 'n/a' - #$endScopeInsightsPreQuerySub = Get-Date - #Write-Host " ScopeInsights SUB PreQuery processing duration: $((NEW-TIMESPAN -Start $startScopeInsightsPreQuerySub -End $endScopeInsightsPreQuerySub).TotalSeconds) seconds" - } - #endregion ScopeInsightsBaseCollection + if ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId)) { + $usedInPolicySet = 'true' + $usedInPolicySetCount = ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId).policySet).Count + $usedInPolicySets = ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId).policySet | Sort-Object) -join "$CsvDelimiterOpposite " + } - if ($mgOrSub -eq "sub") { - [void]$htmlScopeInsights.AppendLine(@" - - - - - - - + - + + + + + + + + + + +"@ + } + [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) + $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $htmlDefinitionInsights = [System.Text.StringBuilder]::new() + [void]$htmlDefinitionInsights.AppendLine( @" + +
    JSONPolicyTypeCategoryDeprecatedPreviewScope Mg/SubScope Name/IdeffectDefaultValuehasAssignmentsAssignments CountAssignmentsUsedInPolicySetPolicySetsCountPolicySetsRoles

    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

    -"@) + $json = $($policy.Json | convertto-json -depth 99) + $guid = ([System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($policy.PolicyDefinitionId)))) -replace '-' + @" +
    - #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 + - [void]$htmlScopeInsights.AppendLine(@" -   Download CSV semicolon | comma - - - - - + + + + + + + + + + + + + + + - - - -"@) - - 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(@" - - - - -"@) - } - [void]$htmlScopeInsights.AppendLine(@" - - +"@ + } + [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) + $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $htmlDefinitionInsights = [System.Text.StringBuilder]::new() + [void]$htmlDefinitionInsights.AppendLine( @" +
    PlanTier$($policy.Type)$($policy.Category -replace '<', '<' -replace '>', '>')$($policy.Deprecated)$($policy.Preview)$($policy.ScopeMgSub)$($scopeDetails -replace '<', '<' -replace '>', '>')$($policy.effectDefaultValue)$hasAssignments$assignmentsCount$assignmentsDetailed$usedInPolicySet$usedInPolicySetCount$usedInPolicySets$($roleDefinitionIds -replace '<', '<' -replace '>', '>')
    $($thisDefenderPlan)$($plan.defenderPlanTier)
    + "@) - } - else { - $subscriptionNotregisteredMDfC = $arrayDefenderPlansSubscriptionNotRegistered.where( { $_.subscriptionId -eq $subscriptionId } ) - if ($subscriptionNotregisteredMDfC.Count -gt 0) { - [void]$htmlScopeInsights.AppendLine(@" -

    Microsoft Defender for Cloud plans - Subscription not registered (ResourceProvider: Microsoft.Security) docs

    -"@) - } - else { - [void]$htmlScopeInsights.AppendLine(@" -

    No Microsoft Defender for Cloud plans docs

    -"@) - } - } - [void]$htmlScopeInsights.AppendLine(@" -
    -"@) - #endregion ScopeInsightsDefenderPlans + $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 ScopeInsightsDiganosticsSubscription - if (($htDiagnosticSettingsMgSub).sub.($subscriptionId)) { - $diagnosticsSubCount = (($htDiagnosticSettingsMgSub).sub.($subscriptionId).Values.Count) - $tfCount = $diagnosticsSubCount - $htmlTableId = "ScopeInsights_DiagnosticsSub_$($subscriptionId -replace '-','_')" - $randomFunctionName = "func_$htmlTableId" - [void]$htmlScopeInsights.AppendLine(@" - -
    -   Download CSV semicolon | comma - - - - - - -"@) - foreach ($logCategory in $diagnosticSettingsSubCategories) { - [void]$htmlScopeInsights.AppendLine(@" - -"@) - } - [void]$htmlScopeInsights.AppendLine(@" + #region definitionInsightsPolicySetDefinitions + $startDefinitionInsightsPolicySetDefinitions = Get-Date + Write-Host ' processing DefinitionInsights PolicySet definitions' + $tfCount = $tenantAllPolicySetsCount + $htmlTableId = 'definitionInsights_PolicySet' + [void]$htmlDefinitionInsights.AppendLine( @" + +
    + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    + +
    Diagnostic settingTargetTarget Id$logCategory
    + + + + + + + + + + + + "@) - $htmlScopeInsightsDiagnosticsSub = $null - $htmlScopeInsightsDiagnosticsSub = foreach ($entry in ($htDiagnosticSettingsMgSub).sub.($subscriptionId).keys | Sort-Object) { - foreach ($diagset in ($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.keys | Sort-Object) { - @" - - - - -"@ - foreach ($logCategory in $diagnosticSettingsSubCategories) { - if (($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticCategoriesHt.($logCategory)) { - @" - -"@ - } - else { - @" - -"@ - } + $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 " } + } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsSub) - [void]$htmlScopeInsights.AppendLine(@" - -
    JSONPolicySet TypeCategoryDeprecatedPreviewScope Mg/SubScope Name/IdhasAssignmentsAssignments CountAssignments
    $(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticSettingName)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticTargetType)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticTargetId)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticCategoriesHt.($logCategory))n/a
    - +
    + +
    + + + }; + setJSON$($guid)(); + jsonViewer$($guid).showJSON(jsonObj$($guid)) + + +
    $($policySet.Type)$($policySet.Category -replace '<', '<' -replace '>', '>')$($policySet.Deprecated)$($policySet.Preview)$($policySet.ScopeMgSub)$($scopeDetails -replace '<', '<' -replace '>', '>')$hasAssignments$assignmentsCount$assignmentsDetailed
    + +
    +"@) + $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' + $tfCount = $tenantAllRolesCount + $htmlTableId = 'definitionInsights_Roles' + [void]$htmlDefinitionInsights.AppendLine( @" + +
    + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    + + - - + + + + + + + "@) - $htmlScopeInsightsTags = $null - $htmlScopeInsightsTags = foreach ($tag in (($htSubscriptionTags).($subscriptionId)).keys | Sort-Object) { - @" - - - - -"@ + $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 } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTags) - [void]$htmlScopeInsights.AppendLine(@" - -
    Tag NameTag ValueJSONRole TypeDatacanDoRoleAssignmentshasAssignmentsAssignments CountAssignments
    $tag$($htSubscriptionTags.$subscriptionId[$tag])
    - -
    -"@) + } else { - [void]$htmlScopeInsights.AppendLine(@" -

    $tagsSubscriptionCount Subscription Tags

    -"@) + $roleType = 'Builtin' + $AssignableScopesCount = '' + $AssignableScopesMG = '' + } + if (-not [string]::IsNullOrEmpty($role.DataActions) -or -not [string]::IsNullOrEmpty($role.NotDataActions)) { + $roleManageData = 'true' + } + else { + $roleManageData = 'false' } - [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) - }) + $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 " } } - $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(@" - -
    -   Resource naming and tagging decision guide docs
    -   Download CSV semicolon | comma - - - - - - - - - -"@) - $htmlScopeInsightsTagsUsage = $null - $htmlScopeInsightsTagsUsage = foreach ($tagEntry in $arrayTagListSubscription | Sort-Object Scope, TagName -CaseSensitive) { - @" + #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 '-' + @" - - - - -"@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTagsUsage) - [void]$htmlScopeInsights.AppendLine(@" - -
    ScopeTagNameCount
    $($tagEntry.Scope)$($tagEntry.TagName)$($tagEntry.TagCount)
    -
    -"@) - } - else { - [void]$htmlScopeInsights.AppendLine(@" -

    Tag Name Usage ($tagsUsageCount Tags) docs

    -"@) - } - [void]$htmlScopeInsights.AppendLine(@" - - -"@) - #endregion ScopeInsightsTagNameUsage - - #Consumption - #$startScopeInsightsConsumptionSub = Get-Date - #region ScopeInsightsConsumptionSub - if ($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.consumedService | Sort-Object -Unique | Measure-Object).Count - $resourceCount = ($consumptionData.ResourceId | Sort-Object -Unique | Measure-Object).Count - $subConsumptionDataGrouped = $consumptionData | Group-Object -property ConsumedService, 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]@{ - ConsumedService = ($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(@" - -
    -   Download CSV semicolon | comma - - - - - - - - - - - - -"@) - $htmlScopeInsightsConsumptionSub = $null - $htmlScopeInsightsConsumptionSub = foreach ($consumptionLine in $arrayConsumptionData) { - @" - - - - - - - + + + + + + + "@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsConsumptionSub) - [void]$htmlScopeInsights.AppendLine(@" - + } + + #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 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation + $arrayRoleDefinitionsForCSVExport = $null + } + #endregion exportCSV + + [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) + $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $htmlDefinitionInsights = [System.Text.StringBuilder]::new() + [void]$htmlDefinitionInsights.AppendLine( @" +
    ChargeTypeResourceTypeCategoryResourceCountCost ($($AzureConsumptionPeriod)d)Currency
    $($consumptionLine.ConsumedServiceChargeType)$($consumptionLine.ConsumedService)$($consumptionLine.ConsumedServiceCategory)$($consumptionLine.ConsumedServiceInstanceCount)$($consumptionLine.ConsumedServiceCost)$($currency)$($roleType)$($roleManageData)$($role.RoleCanDoRoleAssignments)$hasAssignments$assignmentsCount$assignmentsDetailed
    +
    "@) + $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 + + $script:html += $htmlDefinitionInsights + $htmlDefinitionInsights = $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)" +} +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 { - [void]$htmlScopeInsights.AppendLine(@" -

    No Consumption data available

    -"@) + $mgParentNameId = "$mgParentName
    $mgParentId" } - } - 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 ($htParameters.NoResourceProvidersDetailed -eq $false) { - if (($htResourceProvidersAll).($subscriptionId)) { - $tfCount = ($htResourceProvidersAll).($subscriptionId).Providers.Count - $htmlTableId = "ScopeInsights_ResourceProvider_$($subscriptionId -replace '-','_')" - $randomFunctionName = "func_$htmlTableId" - [void]$htmlScopeInsights.AppendLine(@" - -
    -   Download CSV semicolon | comma - - - - - - - - -"@) - $htmlScopeInsightsResourceProvidersDetailed = $null - $htmlScopeInsightsResourceProvidersDetailed = foreach ($provider in ($htResourceProvidersAll).($subscriptionId).Providers) { - @" - - - - + 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 "@ } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResourceProvidersDetailed) - [void]$htmlScopeInsights.AppendLine(@" - -
    ProviderState
    $($provider.namespace)$($provider.registrationState)
    -
    - -"@) + else { + $mgNameId = "$mgName
    $mgInLevel" + } + $script:markdownhierarchySubs += @" +$mgInLevel(`"$mgNameId`") --> SubsOf$mgInLevel(`"$(($subsUnderMg | measure-object).count)`")`n +"@ } else { - [void]$htmlScopeInsights.AppendLine(@" -

    $(($htResourceProvidersAll.Keys).count) Resource Providers

    -"@) + $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 +"@ } - [void]$htmlScopeInsights.AppendLine(@" - - -"@) - } - #endregion ScopeInsightsResourceProvidersDetailed - #ResourceLocks - #region ScopeInsightsResourceLocks - if ($htResourceLocks.($subscriptionId)) { - $tfCount = 6 - $htmlTableId = "ScopeInsights_ResourceLocks_$($subscriptionId -replace '-','_')" - $randomFunctionName = "func_$htmlTableId" + 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 $arrayEntitiesFromAPI) { + if ($entity.properties.parentNameChain -contains $ManagementGroupID -or $entity.Name -eq $ManagementGroupId) { + if ($entity.type -eq '/subscriptions') { + addRowToTable ` + -level (($htEntities.($entity.name).ParentNameChain).Count - 1) ` + -mgName $htEntities.(($entity.properties.parent.Id) -replace '.*/').displayName ` + -mgId (($entity.properties.parent.Id) -replace '.*/') ` + -mgParentId $htEntities.(($entity.properties.parent.Id) -replace '.*/').Parent ` + -mgParentName $htEntities.(($entity.properties.parent.Id) -replace '.*/').ParentDisplayName ` + -Subscription $htEntities.($entity.name).DisplayName ` + -SubscriptionId $htEntities.($entity.name).Id + } + if ($entity.type -eq 'Microsoft.Management/managementGroups') { + addRowToTable ` + -level ($htEntities.($entity.name).ParentNameChain).Count ` + -mgName $entity.properties.displayname ` + -mgId $entity.Name ` + -mgParentId $htEntities.($entity.name).Parent ` + -mgParentName $htEntities.($entity.name).ParentDisplayName + } + } + } +} +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 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) + } - $subscriptionLocksCannotDeleteCount = $htResourceLocks.($subscriptionId).SubscriptionLocksCannotDeleteCount - $subscriptionLocksReadOnlyCount = $htResourceLocks.($subscriptionId).SubscriptionLocksReadOnlyCount - $resourceGroupsLocksCannotDeleteCount = $htResourceLocks.($subscriptionId).ResourceGroupsLocksCannotDeleteCount - $resourceGroupsLocksReadOnlyCount = $htResourceLocks.($subscriptionId).ResourceGroupsLocksReadOnlyCount - $resourcesLocksCannotDeleteCount = $htResourceLocks.($subscriptionId).ResourcesLocksCannotDeleteCount - $resourcesLocksReadOnlyCount = $htResourceLocks.($subscriptionId).ResourcesLocksReadOnlyCount + } + $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 + + $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') { + [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

    + +"@) + + #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(@" - +
    -   Considerations before applying locks 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 - - - + + - - - - - - + +"@) + + 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(@" + + + + +"@) + } + [void]$htmlScopeInsights.AppendLine(@" +
    Lock scopeLock typepresencePlanTier
    SubscriptionCannotDelete$($subscriptionLocksCannotDeleteCount)
    SubscriptionReadOnly$($subscriptionLocksReadOnlyCount)
    ResourceGroupCannotDelete$($resourceGroupsLocksCannotDeleteCount)
    ResourceGroupReadOnly$($resourceGroupsLocksReadOnlyCount)
    ResourceCannotDelete$($resourcesLocksCannotDeleteCount)
    ResourceReadOnly$($resourcesLocksReadOnlyCount)
    $($thisDefenderPlan)$($plan.defenderPlanTier)
    +}; +var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); +tf.init();}} +
    "@) } else { - [void]$htmlScopeInsights.AppendLine(@" -

    0 Resource Locks docs

    -"@) + $subscriptionNotregisteredMDfC = $arrayDefenderPlansSubscriptionNotRegistered.where( { $_.subscriptionId -eq $subscriptionId } ) + if ($subscriptionNotregisteredMDfC.Count -gt 0) { + [void]$htmlScopeInsights.AppendLine(@' +

    Microsoft Defender for Cloud plans - Subscription not registered (ResourceProvider: Microsoft.Security) docs

    +'@) + } + else { + [void]$htmlScopeInsights.AppendLine(@' +

    No Microsoft Defender for Cloud plans 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

    - -"@) + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsDefenderPlans - #region ScopeInsightsDiagnosticsMg - if (($htDiagnosticSettingsMgSub).mg.($mgChild)) { - $diagnosticsMgCount = (($htDiagnosticSettingsMgSub).mg.($mgChild).Values.Count) - $tfCount = $diagnosticsMgCount - $htmlTableId = "ScopeInsights_DiagnosticsMg_$($mgChild -replace '-','_')" + #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(@" - -
    + +
       Download CSV semicolon | comma @@ -6716,52 +5869,52 @@ extensions: [{ name: 'sort' }] "@) - foreach ($logCategory in $diagnosticSettingsMgCategories) { + foreach ($logCategory in $diagnosticSettingsSubCategories) { [void]$htmlScopeInsights.AppendLine(@" "@) } - [void]$htmlScopeInsights.AppendLine(@" + [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) { +'@) + $htmlScopeInsightsDiagnosticsSub = $null + $htmlScopeInsightsDiagnosticsSub = foreach ($entry in ($htDiagnosticSettingsMgSub).sub.($subscriptionId).keys | Sort-Object) { + foreach ($diagset in ($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.keys | Sort-Object) { @" - - - + + + "@ - foreach ($logCategory in $diagnosticSettingsMgCategories) { - if (($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticCategoriesHt.($logCategory)) { + foreach ($logCategory in $diagnosticSettingsSubCategories) { + if (($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticCategoriesHt.($logCategory)) { @" - + "@ } else { - @" + @' -"@ +'@ } } - @" + @' -"@ +'@ } } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsMg) + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsSub) [void]$htmlScopeInsights.AppendLine(@" - +
    Target Target Id$logCategory
    $(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticSettingName)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticTargetType)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticTargetId)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticSettingName)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticTargetType)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticTargetId)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticCategoriesHt.($logCategory))$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticCategoriesHt.($logCategory))n/a
    "@) } else { - [void]$htmlScopeInsights.AppendLine(@" -

    No Management Group Diagnostic settings docs

    -"@) + [void]$htmlScopeInsights.AppendLine(@' +

    No Subscription Diagnostic settings docs

    +'@) } - #endregion ScopeInsightsDiagnosticsMg - - [void]$htmlScopeInsights.AppendLine(@" + [void]$htmlScopeInsights.AppendLine(@' - -"@) - - #$startScopeInsightsConsumptionMg = Get-Date - #region ScopeInsightsConsumptionMg - if ($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 ConsumedService, ChargeType, MeterCategory - $subsCount = ($tenantSummaryConsumptionDataGrouped.group.subscriptionId | Sort-Object -Unique).Count - $consumedServiceCount = ($tenantSummaryConsumptionDataGrouped.group.consumedService | 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]@{ - ConsumedService = ($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" - } + +'@) + #endregion ScopeInsightsDiganosticsSubscription - $tfCount = ($arrayConsumptionData).Count - $htmlTableId = "ScopeInsights_Consumption_$($mgChild -replace '-','_')" - $randomFunctionName = "func_$htmlTableId" - [void]$htmlScopeInsights.AppendLine(@" - -
    -   Download CSV -semicolon | -comma + #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(@" + +
    +   Download CSV semicolon | comma - - - - - - - + + "@) - $htmlScopeInsightsConsumptionMg = $null - $htmlScopeInsightsConsumptionMg = foreach ($consumptionLine in $arrayConsumptionData) { - @" + $htmlScopeInsightsTags = $null + $htmlScopeInsightsTags = foreach ($tag in (($htSubscriptionTags).($subscriptionId)).keys | Sort-Object) { + @" - - - - - - - + + "@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsConsumptionMg) - [void]$htmlScopeInsights.AppendLine(@" - -
    ChargeTypeResourceTypeCategoryResourceCountCost ($($AzureConsumptionPeriod)d)CurrencySubscriptionsTag NameTag Value
    $($consumptionLine.ConsumedServiceChargeType)$($consumptionLine.ConsumedService)$($consumptionLine.ConsumedServiceCategory)$($consumptionLine.ConsumedServiceInstanceCount)$($consumptionLine.ConsumedServiceCost)$($consumptionLine.ConsumedServiceCurrency)$($consumptionLine.ConsumedServiceSubscriptions)$tag$($htSubscriptionTags.$subscriptionId[$tag])
    -
    - -"@) - } - 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($htmlScopeInsightsTags) [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 ($htParameters.NoResources -eq $false) { - #resources - #region ScopeInsightsResources - if ($mgOrSub -eq "mg") { - if ($resourcesAllChildSubscriptionLocationCount -gt 0) { - $tfCount = ($resourcesAllChildSubscriptionsArray).count - $htmlTableId = "ScopeInsights_Resources_$($mgChild -replace '-','_')" - $randomFunctionName = "func_$htmlTableId" - [void]$htmlScopeInsights.AppendLine(@" - -
    -   Download CSV semicolon | comma - - - - - - - - - -"@) - $htmlScopeInsightsResources = $null - $htmlScopeInsightsResources = foreach ($resourceAllChildSubscriptionResourceTypePerLocation in $resourcesAllChildSubscriptionsArray | Sort-Object @{Expression = { $_.ResourceType } }, @{Expression = { $_.location } }) { - @" - - - - - -"@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResources) - [void]$htmlScopeInsights.AppendLine(@"
    ResourceTypeLocationCount
    $($resourceAllChildSubscriptionResourceTypePerLocation.ResourceType)$($resourceAllChildSubscriptionResourceTypePerLocation.location)$($resourceAllChildSubscriptionResourceTypePerLocation.ResourceCount)
    "@) - } - else { - [void]$htmlScopeInsights.AppendLine(@" -

    $resourcesAllChildSubscriptionResourceTypeCount ResourceTypes (all Subscriptions below this scope)

    -"@) - } + } + else { [void]$htmlScopeInsights.AppendLine(@" - - +

    $tagsSubscriptionCount Subscription Tags

    "@) } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsTags - if ($mgOrSub -eq "sub") { - if ($resourcesSubscriptionResourceTypeCount -gt 0) { - $tfCount = ($resourcesSubscription).Count - $htmlTableId = "ScopeInsights_Resources_$($subscriptionId -replace '-','_')" - $randomFunctionName = "func_$htmlTableId" - [void]$htmlScopeInsights.AppendLine(@" - + #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(@" +
    +   Resource naming and tagging decision guide docs
       Download CSV semicolon | comma - - + + "@) - $htmlScopeInsightsResources = $null - $htmlScopeInsightsResources = foreach ($resourceSubscriptionResourceTypePerLocation in $resourcesSubscription | Sort-Object @{Expression = { $_.type } }, @{Expression = { $_.location } }, @{Expression = { $_.count_ } }) { - @" + $htmlScopeInsightsTagsUsage = $null + $htmlScopeInsightsTagsUsage = foreach ($tagEntry in $arrayTagListSubscription | Sort-Object Scope, TagName -CaseSensitive) { + @" - - - + + + "@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResources) - [void]$htmlScopeInsights.AppendLine(@" + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTagsUsage) + [void]$htmlScopeInsights.AppendLine(@"
    ResourceTypeLocationScopeTagName Count
    $($resourceSubscriptionResourceTypePerLocation.type)$($resourceSubscriptionResourceTypePerLocation.location)$($resourceSubscriptionResourceTypePerLocation.count_)$($tagEntry.Scope)$($tagEntry.TagName)$($tagEntry.TagCount)
    -
    -"@) - } - else { - [void]$htmlScopeInsights.AppendLine(@" -

    $resourcesSubscriptionResourceTypeCount ResourceTypes

    + paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) } [void]$htmlScopeInsights.AppendLine(@" - - + btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, + col_0: 'multiple', + col_types: [ + 'caseinsensitivestring', + 'caseinsensitivestring', + 'number' + ], + extensions: [{ name: 'sort' }] + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + +
    "@) } - #endregion ScopeInsightsResources - } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    Tag Name Usage ($tagsUsageCount Tags) docs

    +"@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsTagNameUsage - #ScopeInsightsDiagnosticsCapable - if ($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 + #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.consumedService | Sort-Object -Unique | Measure-Object).Count + $resourceCount = ($consumptionData.ResourceId | Sort-Object -Unique | Measure-Object).Count + $subConsumptionDataGrouped = $consumptionData | Group-Object -property ConsumedService, 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]@{ + ConsumedService = ($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 { - $resourceDiagnosticscapable = $false + $totalCost = [math]::Round($totalCost, 2).ToString('0.00') } - $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 + $arrayTotalCostSummarySub += "$($totalCost) $($currency) generated by $($resourceCount) Resources ($($consumedServiceCount) ResourceTypes)" - if ($resourcesAllChildSubscriptionResourceTypeCount -gt 0) { - $tfCount = $resourcesAllChildSubscriptionResourceTypeCount - $htmlTableId = "ScopeInsights_resourcesDiagnosticsCapable_$($mgchild -replace '-','_')" + $tfCount = ($arrayConsumptionData | Measure-Object).Count + $htmlTableId = "ScopeInsights_Consumption_$($subscriptionId -replace '-','_')" $randomFunctionName = "func_$htmlTableId" [void]$htmlScopeInsights.AppendLine(@" - -
    + +
       Download CSV semicolon | comma - - - - - - + + + + + + "@) - $htmlScopeInsightsDiagnosticsCapable = $null - $htmlScopeInsightsDiagnosticsCapable = foreach ($resourceSubscriptionResourceType in $resourceTypesSummarizedArray | Sort-Object @{Expression = { $_.ResourceType } }) { + $htmlScopeInsightsConsumptionSub = $null + $htmlScopeInsightsConsumptionSub = foreach ($consumptionLine in $arrayConsumptionData) { @" - - - - - - + + + + + + "@ } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsCapable) + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsConsumptionSub) [void]$htmlScopeInsights.AppendLine(@" - -
    ResourceTypeResource CountDiagnostics capableMetricsLogsLogCategoriesChargeTypeResourceTypeCategoryResourceCountCost ($($AzureConsumptionPeriod)d)Currency
    $($resourceSubscriptionResourceType.ResourceType)$($resourceSubscriptionResourceType.ResourceCount)$($resourceSubscriptionResourceType.DiagnosticsCapable)$($resourceSubscriptionResourceType.Metrics)$($resourceSubscriptionResourceType.Logs)$($resourceSubscriptionResourceType.LogCategories)$($consumptionLine.ConsumedServiceChargeType)$($consumptionLine.ConsumedService)$($consumptionLine.ConsumedServiceCategory)$($consumptionLine.ConsumedServiceInstanceCount)$($consumptionLine.ConsumedServiceCost)$($currency)
    - -
    +}; +var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); +tf.init();}} + "@) } else { - [void]$htmlScopeInsights.AppendLine(@" -

    $resourcesAllChildSubscriptionResourceTypeCount ResourceTypes (1st party) Diagnostics capable (all Subscriptions below this scope)

    -"@) + [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

    +'@) } - 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 ") - }) - } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsConsumptionSub + #$endScopeInsightsConsumptionSub = Get-Date + #Write-Host " **ScopeInsightsConsumptionSub data duration: $((NEW-TIMESPAN -Start $startScopeInsightsConsumptionSub -End $endScopeInsightsConsumptionSub).TotalSeconds) seconds" - $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 + #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 - if ($resourcesSubscriptionResourceTypeCount -gt 0) { - $tfCount = $resourcesSubscriptionResourceTypeCount - $htmlTableId = "ScopeInsights_resourcesDiagnosticsCapable_$($subscriptionId -replace '-','_')" + #ResourceProvider + #region ScopeInsightsResourceProvidersDetailed + 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(@" - +
       Download CSV semicolon | comma - - - - - - + + "@) - $htmlScopeInsightsDiagnosticsCapable = $null - $htmlScopeInsightsDiagnosticsCapable = foreach ($resourceSubscriptionResourceType in $resourceTypesSummarizedArray | Sort-Object @{Expression = { $_.ResourceType } }) { + $htmlScopeInsightsResourceProvidersDetailed = $null + $htmlScopeInsightsResourceProvidersDetailed = foreach ($provider in ($htResourceProvidersAll).($subscriptionId).Providers) { @" - - - - - - + + "@ } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsCapable) + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResourceProvidersDetailed) [void]$htmlScopeInsights.AppendLine(@"
    ResourceTypeResource CountDiagnostics capableMetricsLogsLogCategoriesProviderState
    $($resourceSubscriptionResourceType.ResourceType)$($resourceSubscriptionResourceType.ResourceCount)$($resourceSubscriptionResourceType.DiagnosticsCapable)$($resourceSubscriptionResourceType.Metrics)$($resourceSubscriptionResourceType.Logs)$($resourceSubscriptionResourceType.LogCategories)$($provider.namespace)$($provider.registrationState)
    - -
    + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { [void]$htmlScopeInsights.AppendLine(@" -

    $resourcesSubscriptionResourceTypeCount ResourceTypes (1st party) Diagnostics capable

    +

    $(($htResourceProvidersAll.Keys).count) Resource Providers

    "@) } - [void]$htmlScopeInsights.AppendLine(@" + [void]$htmlScopeInsights.AppendLine(@' -"@) +'@) } - #endregion ScopeInsightsDiagnosticsCapable - } + #endregion ScopeInsightsResourceProvidersDetailed - #ScopeInsightsUserAssignedIdentities4Resources - if ($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(@" + #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

    -   Managed identity 'user-assigned' vs 'system-assigned' docs
    -   Download CSV semicolon | comma +   Considerations before applying locks docs - - - - - - - - - - - - - - - - + + -"@) - $htmlScopeInsightsTags = $null - $htmlScopeInsightsTags = foreach ($miResEntry in $arrayUserAssignedIdentities4ResourcesSubscription | Sort-Object -Property miResourceId, resourceId) { - @" - - - - - - - - - - - - - - - - - - - -"@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTags) - [void]$htmlScopeInsights.AppendLine(@" - -
    MI NameMI MgPathMI Subscription NameMI Subscription IdMI ResourceGroupMI ResourceIdMI AAD SP objectIdMI AAD SP applicationIdMI count Res assignments -Res NameRes TypeRes MgPathRes Subscription NameRes Subscription IdRes ResourceGroupRes IdRes count assigned MIs +Lock scopeLock typepresence
    $($miResEntry.miResourceName)$($miResEntry.miMgPath)$($miResEntry.miSubscriptionName)$($miResEntry.miSubscriptionId)$($miResEntry.miResourceGroupName)$($miResEntry.miResourceId)$($miResEntry.miPrincipalId)$($miResEntry.miClientId)$($htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount)$($miResEntry.resourceName)$($miResEntry.resourceType)$($miResEntry.resourceMgPath)$($miResEntry.resourceSubscriptionName)$($miResEntry.resourceSubscriptionId)$($miResEntry.resourceResourceGroupName)$($miResEntry.resourceId)$($htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount)
    - -
    -"@) - } - else { - [void]$htmlScopeInsights.AppendLine(@" -

    No UserAssigned Managed Identities assigned to Resources / vice versa - at all

    +
    "@) - } - [void]$htmlScopeInsights.AppendLine(@" + } + else { + [void]$htmlScopeInsights.AppendLine(@' +

    0 Resource Locks docs

    +'@) + } + [void]$htmlScopeInsights.AppendLine(@' -"@) - #endregion ScopeInsightsUserAssignedIdentities4Resources - } +'@) + #endregion ScopeInsightsResourceLocks + } - #PolicyAssignments - #region ScopeInsightsPolicyAssignments - if ($mgOrSub -eq "mg") { - $SIDivContentClass = "contentSIMG" - $htmlTableIdentifier = $mgChild + #MgChildInfo + #region ScopeInsightsManagementGroups + if ($mgOrSub -eq 'mg') { - $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(@" - -
    -   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 +

    $(($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 '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma - - - - - - - - - - - + + + "@) - - if ($htParameters.NoPolicyComplianceStates -eq $false) { - - [void]$htmlScopeInsights.AppendLine(@" - - - - - + foreach ($logCategory in $diagnosticSettingsMgCategories) { + [void]$htmlScopeInsights.AppendLine(@" + "@) - } - - [void]$htmlScopeInsights.AppendLine(@" - - - - - - - - - + } + [void]$htmlScopeInsights.AppendLine(@' -"@) - $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 - } - @" +'@) + $htmlScopeInsightsDiagnosticsMg = $null + $htmlScopeInsightsDiagnosticsMg = foreach ($entry in ($htDiagnosticSettingsMgSub).mg.($mgChild).keys | Sort-Object) { + foreach ($diagset in ($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.keys | Sort-Object) { + @" - - - - - - - - - - - + + + "@ - - if ($htParameters.NoPolicyComplianceStates -eq $false) { - @" - - - - - + foreach ($logCategory in $diagnosticSettingsMgCategories) { + if (($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticCategoriesHt.($logCategory)) { + @" + "@ - } - - @" - - - - - - - - - + } + else { + @' + +'@ + } + } + @' -"@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPolicyAssignments) - [void]$htmlScopeInsights.AppendLine(@" - -
    InheritanceScopeExcludedExemption appliesPolicy DisplayNamePolicyIdTypeCategoryEffectParametersEnforcementNonCompliance MessageDiagnostic settingTargetTarget IdPolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources Conflicting$logCategoryRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignmentIdAssignedByCreatedOnCreatedByUpdatedOnUpdatedBy
    $($policyAssignment.Inheritance)$($policyAssignment.ExcludedScope)$($policyAssignment.ExemptionScope)$($policyName)$($policyAssignment.PolicyId)$($policyAssignment.PolicyType)$($policyAssignment.PolicyCategory -replace "<", "<" -replace ">", ">")$($policyAssignment.Effect)$($policyAssignment.PolicyAssignmentParameters)$($policyAssignment.PolicyAssignmentEnforcementMode)$($policyAssignment.PolicyAssignmentNonComplianceMessages)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticSettingName)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticTargetType)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticTargetId)$($policyAssignment.NonCompliantPolicies)$($policyAssignment.CompliantPolicies)$($policyAssignment.NonCompliantResources)$($policyAssignment.CompliantResources)$($policyAssignment.ConflictingResources)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticCategoriesHt.($logCategory))$($policyAssignment.RelatedRoleAssignments)$($policyAssignment.PolicyAssignmentMI)$($policyAssignment.PolicyAssignmentDisplayName -replace "<", "<" -replace ">", ">")$($policyAssignment.PolicyAssignmentId -replace "<", "<" -replace ">", ">")$($policyAssignment.AssignedBy)$($policyAssignment.CreatedOn)$($policyAssignment.CreatedBy)$($policyAssignment.UpdatedOn)$($policyAssignment.UpdatedBy)n/a
    -
    - -"@) - } - 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++ + } + [void]$htmlScopeInsights.AppendLine(@' + col_types: [ + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', +'@) + $cnt = 0 + foreach ($logCategory in $diagnosticSettingsMgCategories) { + $cnt++ + if ($diagnosticSettingsMgCategories.Count -eq $cnt) { + [void]$htmlScopeInsights.AppendLine(@' + 'caseinsensitivestring' +'@) } - if ($policySetAssignment.Inheritance -notlike "this*") { - $policySetsInherited++ + else { + [void]$htmlScopeInsights.AppendLine(@' + 'caseinsensitivestring', +'@) } } + [void]$htmlScopeInsights.AppendLine(@" + ], +extensions: [{ name: 'sort' }] + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + +
    +"@) } - } - 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++ - } + else { + [void]$htmlScopeInsights.AppendLine(@' +

    No Management Group Diagnostic settings docs

    +'@) } - } + #endregion ScopeInsightsDiagnosticsMg - if (($policySetsAssigned).count -gt 0) { - $tfCount = ($policiesAssigned).count - $htmlTableId = "ScopeInsights_PolicySetAssignments_$($htmlTableIdentifier -replace "\(","_" -replace "\)","_" -replace "-","_" -replace "\.","_")" - $randomFunctionName = "func_$htmlTableId" - $noteOrNot = "" - [void]$htmlScopeInsights.AppendLine(@" - -
    -   Download CSV semicolon | comma + [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 ConsumedService, ChargeType, MeterCategory + $subsCount = ($tenantSummaryConsumptionDataGrouped.group.subscriptionId | Sort-Object -Unique).Count + $consumedServiceCount = ($tenantSummaryConsumptionDataGrouped.group.consumedService | 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]@{ + ConsumedService = ($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 '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV +semicolon | +comma - - - - - + + - - - -"@) - - if ($htParameters.NoPolicyComplianceStates -eq $false) { - - [void]$htmlScopeInsights.AppendLine(@" - - - - - -"@) - } - - [void]$htmlScopeInsights.AppendLine(@" - - - - - - - - - + + + + "@) - $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 - } - @" + $htmlScopeInsightsConsumptionMg = $null + $htmlScopeInsightsConsumptionMg = foreach ($consumptionLine in $arrayConsumptionData) { + @" - - - - - - - - - -"@ - if ($htParameters.NoPolicyComplianceStates -eq $false) { - @" - - - - - -"@ - } - @" - - - - - - - - - + + + + + + + "@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPolicySetAssignments) - [void]$htmlScopeInsights.AppendLine(@" - -
    InheritanceScopeExcludedPolicySet DisplayNamePolicySetIdTypeChargeTypeResourceType CategoryParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignmentIdAssignedByCreatedOnCreatedByUpdatedOnUpdatedByResourceCountCost ($($AzureConsumptionPeriod)d)CurrencySubscriptions
    $($policyAssignment.Inheritance)$($policyAssignment.ExcludedScope)$($policyName)$($policyAssignment.PolicyId)$($policyAssignment.PolicyType)$($policyAssignment.PolicyCategory -replace "<", "<" -replace ">", ">")$($policyAssignment.PolicyAssignmentParameters)$($policyAssignment.PolicyAssignmentEnforcementMode)$($policyAssignment.PolicyAssignmentNonComplianceMessages)$($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)$($consumptionLine.ConsumedServiceChargeType)$($consumptionLine.ConsumedService)$($consumptionLine.ConsumedServiceCategory)$($consumptionLine.ConsumedServiceInstanceCount)$($consumptionLine.ConsumedServiceCost)$($consumptionLine.ConsumedServiceCurrency)$($consumptionLine.ConsumedServiceSubscriptions)
    -
    - -"@) - } - 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

    +col_types: [ +'caseinsensitivestring', +'caseinsensitivestring', +'caseinsensitivestring', +'number', +'number', +'caseinsensitivestring', +'number' +], +extensions: [{ name: 'sort' }] +}; +var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); +tf.init();}} + "@) - } - 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 } ) - } + } + else { + [void]$htmlScopeInsights.AppendLine(@' +

    No Consumption data available for Subscriptions under this ManagementGroup

    +'@) + } + } + else { + [void]$htmlScopeInsights.AppendLine(@' +

    No Consumption data available

    +'@) + } - if ($scopePolicyAssignmentsLimit.PolicyAndPolicySetAssignmentAtScopeCount -gt (($limit) * $LimitCriticalPercentage / 100)) { - $faImage = "" + [void]$htmlScopeInsights.AppendLine(@' + + +'@) } else { - $faimage = "" + [void]$htmlScopeInsights.AppendLine(@' +

    No Consumption data available as switch parameter -DoAzureConsumption was not applied

    +'@) } - [void]$htmlScopeInsights.AppendLine(@" -

    $faImage Policy Assignment Limit: $($scopePolicyAssignmentsLimit.PolicyAndPolicySetAssignmentAtScopeCount)/$($limit)

    -"@) - } - [void]$htmlScopeInsights.AppendLine(@" - - -"@) - #endregion ScopeInsightsPolicyAssignmentsLimit + #endregion ScopeInsightsConsumptionMg + #$endScopeInsightsConsumptionMg = Get-Date + #Write-Host " ++ScopeInsightsConsumptionMg duration: ($((NEW-TIMESPAN -Start $startScopeInsightsConsumptionMg -End $endScopeInsightsConsumptionMg).TotalSeconds) seconds)" - #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 = "" - } - } + } + #endregion ScopeInsightsManagementGroups - [void]$htmlScopeInsights.AppendLine(@" - -
    + #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 '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
       Download CSV semicolon | comma - - - - - - - + + + "@) - $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) - } - @" + $htmlScopeInsightsResources = $null + $htmlScopeInsightsResources = foreach ($resourceAllChildSubscriptionResourceTypePerLocation in $resourcesAllChildSubscriptionsArray | Sort-Object @{Expression = { $_.ResourceType } }, @{Expression = { $_.location } }) { + @" - - - - - - - + + + "@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsScopedPolicies) - [void]$htmlScopeInsights.AppendLine(@" - -
    Policy DisplayNamePolicyIdCategoryPolicy effectRole definitionsUnique assignmentsUsed in PolicySetsResourceTypeLocationCount
    $($customPolicy.PolicyDisplayName -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyDefinitionId -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyCategory -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace "<", "<" -replace ">", ">")$($customPolicyUsedInPolicySets)$($resourceAllChildSubscriptionResourceTypePerLocation.ResourceType)$($resourceAllChildSubscriptionResourceTypePerLocation.location)$($resourceAllChildSubscriptionResourceTypePerLocation.ResourceCount)
    -
    + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResources) + [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(@" +

    $resourcesAllChildSubscriptionResourceTypeCount ResourceTypes (all Subscriptions below this scope)

    +"@) } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) } - [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(@" + +
       Download CSV semicolon | comma - - - - - + + + "@) - $htmlScopeInsightsScopedPolicySets = $null - $htmlScopeInsightsScopedPolicySets = foreach ($custompolicySet in $scopePolicySets | Sort-Object @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { - @" + $htmlScopeInsightsResources = $null + $htmlScopeInsightsResources = foreach ($resourceSubscriptionResourceTypePerLocation in $resourcesSubscription | Sort-Object @{Expression = { $_.type } }, @{Expression = { $_.location } }, @{Expression = { $_.count_ } }) { + @" - - - - - + + + "@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsScopedPolicySets) - [void]$htmlScopeInsights.AppendLine(@" - -
    PolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies UsedResourceTypeLocationCount
    $($custompolicySet.PolicySetDisplayName -replace "<", "<" -replace ">", ">")$($custompolicySet.PolicySetDefinitionId -replace "<", "<" -replace ">", ">")$($custompolicySet.PolicySetCategory -replace "<", "<" -replace ">", ">")$($custompolicySet.UniqueAssignments -replace "<", "<" -replace ">", ">")$($custompolicySet.PoliciesUsed -replace "<", "<" -replace ">", ">")$($resourceSubscriptionResourceTypePerLocation.type)$($resourceSubscriptionResourceTypePerLocation.location)$($resourceSubscriptionResourceTypePerLocation.count_)
    -
    + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResources) + [void]$htmlScopeInsights.AppendLine(@" + + +
    "@) - } - else { - [void]$htmlScopeInsights.AppendLine(@" -

    $scopePolicySetsCount Custom PolicySet definitions scoped

    + } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $resourcesSubscriptionResourceTypeCount ResourceTypes

    "@) - } - [void]$htmlScopeInsights.AppendLine(@" + } + [void]$htmlScopeInsights.AppendLine(@' -"@) - #endregion ScopeInsightsScopedPolicySets - - #BlueprintAssignments - #region ScopeInsightsBlueprintAssignments - if ($mgOrSub -eq "sub") { - if ($blueprintsAssignedCount -gt 0) { +'@) + } + #endregion ScopeInsightsResources + } - if ($mgOrSub -eq "mg") { - $htmlTableIdentifier = $mgChild - } - if ($mgOrSub -eq "sub") { - $htmlTableIdentifier = $subscriptionId + #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 ") + }) } - $htmlTableId = "ScopeInsights_BlueprintAssignment_$($htmlTableIdentifier -replace "\(","_" -replace "\)","_" -replace "-","_" -replace "\.","_")" - $randomFunctionName = "func_$htmlTableId" - [void]$htmlScopeInsights.AppendLine(@" - -
    + $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 '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
       Download CSV semicolon | comma - - - - - - + + + + + + "@) - $htmlScopeInsightsBlueprintAssignments = $null - $htmlScopeInsightsBlueprintAssignments = foreach ($blueprintAssigned in $blueprintsAssigned) { - @" + $htmlScopeInsightsDiagnosticsCapable = $null + $htmlScopeInsightsDiagnosticsCapable = foreach ($resourceSubscriptionResourceType in $resourceTypesSummarizedArray | Sort-Object @{Expression = { $_.ResourceType } }) { + @" - - - - - - + + + + + + "@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsBlueprintAssignments) - [void]$htmlScopeInsights.AppendLine(@" - -
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdBlueprint VersionBlueprint AssignmentIdResourceTypeResource CountDiagnostics capableMetricsLogsLogCategories
    $($blueprintAssigned.BlueprintName -replace "<", "<" -replace ">", ">")$($blueprintAssigned.BlueprintDisplayName -replace "<", "<" -replace ">", ">")$($blueprintAssigned.BlueprintDescription -replace "<", "<" -replace ">", ">")$($blueprintAssigned.BlueprintId -replace "<", "<" -replace ">", ">")$($blueprintAssigned.BlueprintAssignmentVersion -replace "<", "<" -replace ">", ">")$($blueprintAssigned.BlueprintAssignmentId -replace "<", "<" -replace ">", ">")$($resourceSubscriptionResourceType.ResourceType)$($resourceSubscriptionResourceType.ResourceCount)$($resourceSubscriptionResourceType.DiagnosticsCapable)$($resourceSubscriptionResourceType.Metrics)$($resourceSubscriptionResourceType.Logs)$($resourceSubscriptionResourceType.LogCategories)
    -
    - +
    "@) - } - else { - [void]$htmlScopeInsights.AppendLine(@" -

    $blueprintsAssignedCount Blueprints assigned

    + } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $resourcesAllChildSubscriptionResourceTypeCount ResourceTypes (1st party) Diagnostics capable (all Subscriptions below this scope)

    "@) - } - [void]$htmlScopeInsights.AppendLine(@" + } + [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(@" - -
    + + 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(@" + +
       Download CSV semicolon | comma - - - - + + + + + + "@) - $htmlScopeInsightsBlueprintsScoped = $null - $htmlScopeInsightsBlueprintsScoped = foreach ($blueprintScoped in $blueprintsScoped) { - @" + $htmlScopeInsightsDiagnosticsCapable = $null + $htmlScopeInsightsDiagnosticsCapable = foreach ($resourceSubscriptionResourceType in $resourceTypesSummarizedArray | Sort-Object @{Expression = { $_.ResourceType } }) { + @" - - - - + + + + + + "@ - } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsBlueprintsScoped) - [void]$htmlScopeInsights.AppendLine(@" - -
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdResourceTypeResource CountDiagnostics capableMetricsLogsLogCategories
    $($blueprintScoped.BlueprintName -replace "<", "<" -replace ">", ">")$($blueprintScoped.BlueprintDisplayName -replace "<", "<" -replace ">", ">")$($blueprintScoped.BlueprintDescription -replace "<", "<" -replace ">", ">")$($blueprintScoped.BlueprintId -replace "<", "<" -replace ">", ">")$($resourceSubscriptionResourceType.ResourceType)$($resourceSubscriptionResourceType.ResourceCount)$($resourceSubscriptionResourceType.DiagnosticsCapable)$($resourceSubscriptionResourceType.Metrics)$($resourceSubscriptionResourceType.Logs)$($resourceSubscriptionResourceType.LogCategories)
    -
    + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsCapable) + [void]$htmlScopeInsights.AppendLine(@" + + +
    "@) - } - else { - [void]$htmlScopeInsights.AppendLine(@" -

    $blueprintsScopedCount Blueprints scoped

    + } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $resourcesSubscriptionResourceTypeCount ResourceTypes (1st party) Diagnostics capable

    "@) - } - [void]$htmlScopeInsights.AppendLine(@" + } + [void]$htmlScopeInsights.AppendLine(@' -"@) - #endregion ScopeInsightsBlueprintsScoped - - #RoleAssignments - #region ScopeInsightsRoleAssignments - if ($mgOrSub -eq "mg") { - $SIDivContentClass = "contentSIMG" - $htmlTableIdentifier = $mgChild - $LimitRoleAssignmentsScope = $LimitRBACRoleAssignmentsManagementGroup +'@) + } + #endregion ScopeInsightsDiagnosticsCapable + } - $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 -eq "User") { - $rolesAssignedUser++ - } - if ($roleAssignment.ObjectType -eq "Group") { - $rolesAssignedGroup++ - } - if ($roleAssignment.ObjectType -eq "ServicePrincipal") { - $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++ + #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(@" + +
    +   Managed identity 'user-assigned' vs 'system-assigned' docs
    +   Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + + + +"@) + $htmlScopeInsightsTags = $null + $htmlScopeInsightsTags = foreach ($miResEntry in $arrayUserAssignedIdentities4ResourcesSubscription | Sort-Object -Property miResourceId, resourceId) { + @" + + + + + + + + + + + + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTags) + [void]$htmlScopeInsights.AppendLine(@" + +
    MI NameMI MgPathMI Subscription NameMI Subscription IdMI ResourceGroupMI ResourceIdMI AAD SP objectIdMI AAD SP applicationIdMI count Res assignments +Res NameRes TypeRes MgPathRes Subscription NameRes Subscription IdRes ResourceGroupRes IdRes count assigned MIs +
    $($miResEntry.miResourceName)$($miResEntry.miMgPath)$($miResEntry.miSubscriptionName)$($miResEntry.miSubscriptionId)$($miResEntry.miResourceGroupName)$($miResEntry.miResourceId)$($miResEntry.miPrincipalId)$($miResEntry.miClientId)$($htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount)$($miResEntry.resourceName)$($miResEntry.resourceType)$($miResEntry.resourceMgPath)$($miResEntry.resourceSubscriptionName)$($miResEntry.resourceSubscriptionId)$($miResEntry.resourceResourceGroupName)$($miResEntry.resourceId)$($htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount)
    + +
    +"@) + } + else { + [void]$htmlScopeInsights.AppendLine(@' +

    No UserAssigned Managed Identities assigned to Resources / vice versa - at all

    +'@) } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsUserAssignedIdentities4Resources } } - 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) { + #PolicyAssignments + #region ScopeInsightsPolicyAssignments + if ($mgOrSub -eq 'mg') { + $SIDivContentClass = 'contentSIMG' + $htmlTableIdentifier = $mgChild - $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++ + $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 ($roleAssignment.ObjectType -eq "Unknown") { - $rolesAssignedUnknown++ + } + } + 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 ($roleAssignment.RbacRelatedPolicyAssignment -ne "none") { - $roleAssignmentsRelatedToPolicyCount++ + if ($policyAssignment.PolicyType -eq 'Custom') { + $policiesCountCustom++ } - if ($roleAssignment.RoleSecurityCustomRoleOwner -eq 1) { - $roleSecurityFindingCustomRoleOwner++ + if ($policyAssignment.Inheritance -like 'this*') { + $policiesAssignedAtScope++ } - if ($roleAssignment.RoleSecurityOwnerAssignmentSP -eq 1) { - $roleSecurityFindingOwnerAssignmentSP++ + if ($policyAssignment.Inheritance -notlike 'this*') { + $policiesInherited++ } } } - $rolesAssignedAtScopeCount = $rolesAssignedCount - $rolesAssignedInheritedCount - - if (($rolesAssigned).count -gt 0) { - $tfCount = ($rolesAssigned).count - $htmlTableId = "ScopeInsights_RoleAssignments_$($htmlTableIdentifier -replace "\(","_" -replace "\)","_" -replace "-","_" -replace "\.","_")" + if (($policiesAssigned).count -gt 0) { + $tfCount = ($policiesAssigned).count + $htmlTableId = "ScopeInsights_PolicyAssignments_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')" $randomFunctionName = "func_$htmlTableId" - $noteOrNot = "" + $noteOrNot = '' [void]$htmlScopeInsights.AppendLine(@" - +
       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 - - - - - - - - - - - - - - - + + + + + + + + + + + +"@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + + [void]$htmlScopeInsights.AppendLine(@' + + + + + +'@) + } + + [void]$htmlScopeInsights.AppendLine(@" + + + + + + + "@) - $htmlScopeInsightsRoleAssignments = $null - $htmlScopeInsightsRoleAssignments = foreach ($roleAssignment in ($rolesAssigned | Sort-Object -Property Level, MgName, MgId, SubscriptionName, SubscriptionId, Scope, Role, RoleId, ObjectId, RoleAssignmentId)) { - @" - - - - - - - - - - - - - - - - - - + $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 + } + @" + + + + + + + + + + + + +"@ + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + @" + + + + + +"@ + } + + @" + + + + + + + + + "@ } - [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsRoleAssignments) + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPolicyAssignments) [void]$htmlScopeInsights.AppendLine(@"
    ScopeRoleRoleIdRole TypeDataCan do Role assignmentIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsRole AssignmentIdRelated Policy Assignment $noteOrNotInheritanceScopeExcludedExemption appliesPolicy DisplayNamePolicyIdTypeCategoryEffectParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignmentIdAssignedBy CreatedOn CreatedByUpdatedOnUpdatedBy
    $($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)
    $($policyAssignment.Inheritance)$($policyAssignment.ExcludedScope)$($policyAssignment.ExemptionScope)$($policyName)$($policyAssignment.PolicyId)$($policyAssignment.PolicyType)$($policyAssignment.PolicyCategory -replace '<', '<' -replace '>', '>')$($policyAssignment.Effect)$($policyAssignment.PolicyAssignmentParameters)$($policyAssignment.PolicyAssignmentEnforcementMode)$($policyAssignment.PolicyAssignmentNonComplianceMessages)$($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)
    @@ -8796,14 +7644,14 @@ extensions: [{ name: 'sort' }] paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) } - [void]$htmlScopeInsights.AppendLine(@" + [void]$htmlScopeInsights.AppendLine(@' btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, linked_filters: true, - col_3: 'select', - col_4: 'select', + col_1: 'select', + col_2: 'select', col_5: 'select', - col_9: 'multiple', - col_10: 'select', + col_7: 'select', + col_9: 'select', locale: 'en-US', col_types: [ 'caseinsensitivestring', @@ -8817,14 +7665,31 @@ btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring', +'@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + + [void]$htmlScopeInsights.AppendLine(@' + + 'number', + 'number', + 'number', + 'number', + 'number', +'@) + } + [void]$htmlScopeInsights.AppendLine(@" + 'caseinsensitivestring', + 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring', + 'date', 'caseinsensitivestring', 'date', 'caseinsensitivestring' ], - watermark: ['', 'try owner||reader', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + watermark: ['try: thisScope'], extensions: [{ name: 'colsVisibility', text: 'Columns: ', enable_tick_all: true },{ name: 'sort' }] }; var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); @@ -8834,948 +7699,616 @@ btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { } else { [void]$htmlScopeInsights.AppendLine(@" -

    $(($rbacAll).count) Role assignments

    - +

    $(($policiesAssigned).count) Policy assignments

    "@) } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsPolicyAssignments - [void]$htmlScopeInsights.AppendLine(@" - -"@) - #endregion ScopeInsightsRoleAssignments - - - if (-not $NoScopeInsights) { - $script:html += $htmlScopeInsights - } + #PolicySetAssignments + #region ScopeInsightsPolicySetAssignments + if ($mgOrSub -eq 'mg') { + $SIDivContentClass = 'contentSIMG' + $htmlTableIdentifier = $mgChild - 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 + $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 - 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 - #[System.GC]::Collect() + $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++ + } } } -} -#endregion ScopeInsights - -#rsu -#region TenantSummary -function ProcessTenantSummary() { - Write-Host " Building TenantSummary" - - if ($getMgParentName -eq "Tenant Root") { - $scopeNamingSummary = "Tenant wide" - } - else { - $scopeNamingSummary = "ManagementGroup '$ManagementGroupIdCaseSensitived' and descendants wide" - } - - #region tenantSummaryPre - $startRoleAssignmentsAllPre = Get-Date - $roleAssignmentsallCount = ($rbacBaseQuery).count - Write-Host " processing (pre) TenantSummary RoleAssignments (all $roleAssignmentsallCount)" + if (($policySetsAssigned).count -gt 0) { + $tfCount = ($policiesAssigned).count + $htmlTableId = "ScopeInsights_PolicySetAssignments_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')" + $randomFunctionName = "func_$htmlTableId" + $noteOrNot = '' + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + + +"@) - #region RelatedPolicyAssignments - $startRelatedPolicyAssignmentsAll = Get-Date - $htRoleAssignmentRelatedPolicyAssignments = @{} - $htOrphanedSPMI = @{} - foreach ($roleAssignmentIdUnique in $roleAssignmentsUniqueById) { + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { - $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId) = @{} + [void]$htmlScopeInsights.AppendLine(@' + + + + + +'@) + } - if ($htManagedIdentityForPolicyAssignment.($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId)) { - $hlpPolicyAssignmentId = ($htManagedIdentityForPolicyAssignment.($roleAssignmentIdUnique.RoleAssignmentIdentityObjectId).policyAssignmentId).ToLower() - if (-not $htCacheAssignmentsPolicy.($hlpPolicyAssignmentId)) { - if ($ManagementGroupId -eq $checkContext.Tenant.Id) { - if ($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) = @{} - } - } - } + [void]$htmlScopeInsights.AppendLine(@" + + + + + + + + + + + + +"@) + $htmlScopeInsightsPolicySetAssignments = $null + $htmlScopeInsightsPolicySetAssignments = foreach ($policyAssignment in $policySetsAssigned | Sort-Object -Property Level, PolicyAssignmentId) { + if ($policyAssignment.PolicyType -eq 'Custom') { + $policyName = ($policyAssignment.PolicyName -replace '<', '<' -replace '>', '>') } 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)" + $policyName = $policyAssignment.PolicyName } - } - 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" + @" + + + + + + + + + + +"@ + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + @" + + + + + +"@ } - else { - $roleType = "Custom" + @" + + + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPolicySetAssignments) + [void]$htmlScopeInsights.AppendLine(@" + +
    InheritanceScopeExcludedPolicySet DisplayNamePolicySetIdTypeCategoryParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignmentIdAssignedByCreatedOnCreatedByUpdatedOnUpdatedBy
    $($policyAssignment.Inheritance)$($policyAssignment.ExcludedScope)$($policyName)$($policyAssignment.PolicyId)$($policyAssignment.PolicyType)$($policyAssignment.PolicyCategory -replace '<', '<' -replace '>', '>')$($policyAssignment.PolicyAssignmentParameters)$($policyAssignment.PolicyAssignmentEnforcementMode)$($policyAssignment.PolicyAssignmentNonComplianceMessages)$($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)
    +
    + +"@) } - $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 + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $(($policySetsAssigned).count) PolicySet assignments

    +"@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsPolicySetAssignments - #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" - #if ($cnter % 5000 -eq 0) { - #[System.GC]::Collect() - #} + #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 } ) } - $scope = $null - if ($rbac.RoleAssignmentPIM -eq "true") { - $pim = $true - $pimAssignmentType = $rbac.RoleAssignmentPIMAssignmentType - $pimSlotStart = $($rbac.RoleAssignmentPIMSlotStart) - $pimSlotEnd = $($rbac.RoleAssignmentPIMSlotEnd) + if ($scopePolicyAssignmentsLimit.PolicyAndPolicySetAssignmentAtScopeCount -gt (($limit) * $LimitCriticalPercentage / 100)) { + $faImage = "" } else { - $pim = $false - $pimAssignmentType = "" - $pimSlotStart = "" - $pimSlotEnd = "" + $faimage = "" } + [void]$htmlScopeInsights.AppendLine(@" +

    $faImage Policy Assignment Limit: $($scopePolicyAssignmentsLimit.PolicyAndPolicySetAssignmentAtScopeCount)/$($limit)

    +"@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsPolicyAssignmentsLimit - if ($rbac.RoleAssignmentId -like "/providers/Microsoft.Management/managementGroups/*") { - $tenOrMgOrSubOrRGOrRes = "Mg" - if (-not [String]::IsNullOrEmpty($rbac.SubscriptionId)) { - $scope = "inherited $($rbac.RoleAssignmentScopeName)" + #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 { - if (($rbac.RoleAssignmentScopeName) -eq $rbac.MgId) { - $scope = "thisScope MG" - } - else { - $scope = "inherited $($rbac.RoleAssignmentScopeName)" - } + $faIcon = "" } } - - if ($rbac.RoleAssignmentId -like "/subscriptions/*") { - $scope = "thisScope Sub" - $tenOrMgOrSubOrRGOrRes = "Sub" - } - - if ($rbac.RoleAssignmentId -like "/subscriptions/*/resourcegroups/*") { - $scope = "thisScope Sub RG" - $tenOrMgOrSubOrRGOrRes = "RG" - } - - if ($rbac.RoleAssignmentId -like "/subscriptions/*/resourcegroups/*/providers/*/providers/*") { - $scope = "thisScope Sub RG Res" - $tenOrMgOrSubOrRGOrRes = "Res" - } - - if ($rbac.RoleAssignmentId -like "/providers/Microsoft.Authorization/roleAssignments/*") { - $scope = "inherited Tenant" - $tenOrMgOrSubOrRGOrRes = "Ten" - } - - $objectTypeUserType = "" - if ($rbac.RoleAssignmentIdentityObjectType -eq "User") { - if ($htUserTypesGuest.($rbac.RoleAssignmentIdentityObjectId)) { - $objectTypeUserType = "Guest" + if ($mgOrSub -eq 'sub') { + $LimitPOLICYPolicyScoped = $LimitPOLICYPolicyDefinitionsScopedSubscription + if ($scopePoliciesCount -gt (($LimitPOLICYPolicyScoped * $LimitCriticalPercentage) / 100)) { + $faIcon = "" } else { - $objectTypeUserType = "Member" + $faIcon = "" } } - if (-not [string]::IsNullOrEmpty($rbac.RoleDataActions) -or -not [string]::IsNullOrEmpty($rbac.RoleNotDataActions)) { - $roleManageData = "true" - } - else { - $roleManageData = "false" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + + + +"@) + $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) + } + @" + + + + + + + + + +"@ } - - $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 - RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName - RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG - RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes - 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 - 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 - TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes - 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 ($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 - RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName - RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG - RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes - 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 - 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 - TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes - 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 - RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName - RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG - RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes - 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 - 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 = "AzGovViz:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))" - ObjectSignInName = "AzGovViz:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))" - ObjectId = "AzGovViz:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))" - ObjectType = "unresolved" - TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes - RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment - RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear - RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner - RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP - RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments - }) - } - } - + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsScopedPolicies) + [void]$htmlScopeInsights.AppendLine(@" + +
    Policy DisplayNamePolicyIdCategoryPolicy effectRole definitionsUnique assignmentsUsed in PolicySets
    $($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicyUsedInPolicySets)
    +
    + +"@) + } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $scopePoliciesCount Custom Policy definitions scoped

    +"@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsScopedPolicies - #noaadgroupmemberresolve - $null = $script:rbacAll.Add([PSCustomObject]@{ - Level = $rbac.Level - RoleAssignmentId = $rbac.RoleAssignmentId - RoleAssignmentPIMRelated = $pim - RoleAssignmentPIMAssignmentType = $pimAssignmentType - RoleAssignmentPIMAssignmentSlotStart = $pimSlotStart - RoleAssignmentPIMAssignmentSlotEnd = $pimSlotEnd - RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName - RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG - RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes - 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 - 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 - TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes - RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment - RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear - RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner - RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP - RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments - }) - } + #ScopedPolicySets + #region ScopeInsightsScopedPolicySets + if ($mgOrSub -eq 'mg') { + $SIDivContentClass = 'contentSIMG' + $htmlTableIdentifier = $mgChild + $scopePolicySets = $customPolicySetsDetailed.where( { $_.PolicySetDefinitionId -like "*/providers/Microsoft.Management/managementGroups/$mgChild/*" } ) + $scopePolicySetsCount = ($scopePolicySets).count } - #endregion createRBACAll - - 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 = @() - $identityWithRoleAssignment.psobject.properties | ForEach-Object { - if ($_.Value) { - $value = $_.Value - } - else { - $value = "n/a" - } - $arr += "$($_.Name): $value" - } - - $script:htIdentitiesWithRoleAssignmentsUnique.($identityWithRoleAssignment.ObjectId).details = $arr -join "$CsvDelimiterOpposite " - } + if ($mgOrSub -eq 'sub') { + $SIDivContentClass = 'contentSISub' + $htmlTableIdentifier = $subscriptionId + $scopePolicySets = $customPolicySetsDetailed.where( { $_.PolicySetDefinitionId -like "*/subscriptions/$subscriptionId/*" } ) + $scopePolicySetsCount = ($scopePolicySets).count } - #endregion identitiesThatCreatedRoleAssignmentsButDontHaveARoleAssignmentThemselve - #enrich rbacAll with createdBy and UpdatedBy identity information - #region enrichrbacAll - $htNonResolvedIdentities = @{} - foreach ($rbac in $rbacAll) { - $createdBy = $rbac.createdBy - if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) { - $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details - $rbac.CreatedBy = $createdBy - } - else { - if (-not $htNonResolvedIdentities.($rbac.createdBy)) { - $htNonResolvedIdentities.($rbac.createdBy) = @{} + 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 = "" } - } - } - #endregion enrichrbacAll - - $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 + else { + $faIcon = "" } } - $arrayUnresolvedIdentitiesCount = $arrayUnresolvedIdentities.Count - Write-Host " $arrayUnresolvedIdentitiesCount unresolved identities that have a value" - if ($arrayUnresolvedIdentitiesCount -gt 0) { - $nonResolvedIdentitiesToCheck = '"{0}"' -f ($arrayUnresolvedIdentities -join '","') - Write-Host " IdentitiesToCheck: $nonResolvedIdentitiesToCheck" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/directoryObjects/getByIds" - $method = "POST" - $body = @" - { - "ids":[$($nonResolvedIdentitiesToCheck)] + if ($mgOrSub -eq 'sub') { + $LimitPOLICYPolicySetScoped = $LimitPOLICYPolicySetDefinitionsScopedSubscription + if ($scopePolicySetsCount -gt (($LimitPOLICYPolicySetScoped * $LimitCriticalPercentage) / 100)) { + $faIcon = "" + } + else { + $faIcon = "" + } } + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + +"@) + $htmlScopeInsightsScopedPolicySets = $null + $htmlScopeInsightsScopedPolicySets = foreach ($custompolicySet in $scopePolicySets | Sort-Object @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { + @" + + + + + + + "@ - - $script:htResolvedIdentities = @{} - function resolveIdentitiesRBAC($currentTask) { - $resolvedIdentities = AzAPICall -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)" - } - else { - if ($resolvedIdentity.servicePrincipalType -eq "Application") { - $sptype = "App" - if ($resolvedIdentity.appOwnerOrganizationId -eq $checkContext.Tenant.Id) { - $custObjectType = "ObjectType: SP $sptype INT, ObjectDisplayName: $($resolvedIdentity.displayName), ObjectSignInName: n/a, ObjectId: $($resolvedIdentity.id) (r)" - } - else { - $custObjectType = "ObjectType: SP $sptype EXT, ObjectDisplayName: $($resolvedIdentity.displayName), ObjectSignInName: n/a, ObjectId: $($resolvedIdentity.id) (r)" - } - } - 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)" - - $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 - } - } - - 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 AzGovViz - if you see this line, please file an issue on GitHub - thank you." -ForegroundColor Yellow - } - } - } - } + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsScopedPolicySets) + [void]$htmlScopeInsights.AppendLine(@" + +
    PolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies Used
    $($custompolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')$($custompolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')$($custompolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')$($custompolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')$($custompolicySet.PoliciesUsed)
    +
    + +"@) } - - $endUnResolvedIdentitiesCreatedBy = Get-Date - Write-Host " UnresolvedIdentities (createdBy) duration: $((NEW-TIMESPAN -Start $startUnResolvedIdentitiesCreatedBy -End $endUnResolvedIdentitiesCreatedBy).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startUnResolvedIdentitiesCreatedBy -End $endUnResolvedIdentitiesCreatedBy).TotalSeconds) seconds)" - - $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 - - #region tenantSummaryPolicy - $htmlTenantSummary = [System.Text.StringBuilder]::new() - [void]$htmlTenantSummary.AppendLine(@" - -
    - Anything which can help you learn Azure Policy GitHub
    + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $scopePolicySetsCount Custom PolicySet definitions scoped

    "@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsScopedPolicySets - #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 - if ($htPolicyWithAssignmentsBase.($tenantPolicy.PolicyDefinitionId)) { - $policyUniqueAssignments = $htPolicyWithAssignmentsBase.($tenantPolicy.PolicyDefinitionId).Assignments - $policyUniqueAssignmentsCount = ($policyUniqueAssignments).count - } - else { - $policyUniqueAssignmentsCount = 0 - } - - $uniqueAssignments = $null - if ($policyUniqueAssignmentsCount -gt 0) { - $policyUniqueAssignmentsList = "($($policyUniqueAssignments -join "$CsvDelimiterOpposite "))" - $uniqueAssignments = "$policyUniqueAssignmentsCount $policyUniqueAssignmentsList" - } - else { - $uniqueAssignments = $policyUniqueAssignmentsCount - } - - #PolicyUsedInPolicySet - $usedInPolicySet = "0" - $usedInPolicySet4CSV = "" - $usedInPolicySetCount = "0" - if (($htPoliciesUsedInPolicySets).($tenantPolicy.PolicyDefinitionId)) { - $hlpPolicySetUsed = ($htPoliciesUsedInPolicySets).($tenantPolicy.PolicyDefinitionId) - $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)" - } - else { - $effect = "Fixed: $($tenantPolicy.effectFixedValue)" - } - - 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.Type -eq "Custom") { + #BlueprintAssignments + #region ScopeInsightsBlueprintAssignments + if ($mgOrSub -eq 'sub') { + if ($blueprintsAssignedCount -gt 0) { - $createdOn = "" - $createdBy = "" - $updatedOn = "" - $updatedBy = "" - if ($tenantPolicy.Json.properties.metadata.createdOn) { - $createdOn = $tenantPolicy.Json.properties.metadata.createdOn - } - if ($tenantPolicy.Json.properties.metadata.createdBy) { - $createdBy = $tenantPolicy.Json.properties.metadata.createdBy - if ($createdBy -ne "n/a") { - if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) { - $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details - } - } - } - if ($tenantPolicy.Json.properties.metadata.updatedOn) { - $updatedOn = $tenantPolicy.Json.properties.metadata.updatedOn + if ($mgOrSub -eq 'mg') { + $htmlTableIdentifier = $mgChild } - if ($tenantPolicy.Json.properties.metadata.updatedBy) { - $updatedBy = $tenantPolicy.Json.properties.metadata.updatedBy - if ($updatedBy -ne "n/a") { - if ($htIdentitiesWithRoleAssignmentsUnique.($updatedBy)) { - $updatedBy = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).details - } - } + if ($mgOrSub -eq 'sub') { + $htmlTableIdentifier = $subscriptionId } - - $null = $script:customPoliciesDetailed.Add([PSCustomObject]@{ - Type = "Custom" - ScopeMGLevel = $tenantPolicy.ScopeMGLevel - Scope = $tenantPolicy.ScopeMgSub - ScopeId = $tenantPolicy.ScopeId - PolicyDisplayName = $tenantPolicy.DisplayName - PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId - 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 - #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.PolicyDefinitionId -replace ".*/" - PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId - PolicyEffect = $effect - PolicyCategory = $tenantPolicy.Category - UniqueAssignments = $policyUniqueAssignmentsCount - UsedInPolicySetsCount = $usedInPolicySetCount - UsedInPolicySets = $usedInPolicySet - UsedInPolicySet4CSV = $usedInPolicySet4CSV - CreatedOn = $createdOn - CreatedBy = $createdBy - UpdatedOn = $updatedOn - UpdatedBy = $updatedBy - #Json = [string]($tenantPolicy.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings) - }) - } - else { - $null = $script:tenantPoliciesDetailed.Add([PSCustomObject]@{ - Type = "BuiltIn" - ScopeMGLevel = $tenantPolicy.ScopeMGLevel - Scope = "" - ScopeId = "" - PolicyDisplayName = $tenantPolicy.DisplayName - PolicyDefinitionName = $tenantPolicy.PolicyDefinitionId -replace ".*/" - PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId - PolicyEffect = $effect - PolicyCategory = $tenantPolicy.Category - UniqueAssignments = $policyUniqueAssignmentsCount - UsedInPolicySetsCount = $usedInPolicySetCount - UsedInPolicySets = $usedInPolicySet - UsedInPolicySet4CSV = $usedInPolicySet4CSV - CreatedOn = "" - CreatedBy = "" - UpdatedOn = "" - UpdatedBy = "" - #Json = [string]($tenantPolicy.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings) - }) - } - } - - 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 UsedInPolicySets | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation - } - - if ($getMgParentName -eq "Tenant Root") { - - if ($tenantCustomPoliciesCount -gt 0) { - $tfCount = $tenantCustomPoliciesCount - $htmlTableId = "TenantSummary_customPolicies" + $htmlTableId = "ScopeInsights_BlueprintAssignment_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')" $randomFunctionName = "func_$htmlTableId" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma +
    - - - - - - - - - - - - - + + + + + + "@) - $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) - } + $htmlScopeInsightsBlueprintAssignments = $null + $htmlScopeInsightsBlueprintAssignments = foreach ($blueprintAssigned in $blueprintsAssigned) { @" - - - - - - - - - - - - - + + + + + + "@ } - - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYcustompolicies) - $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $htmlTenantSummary = [System.Text.StringBuilder]::new() - [void]$htmlTenantSummary.AppendLine(@" - -
    ScopeScope IdPolicy DisplayNamePolicyIdCategoryEffectRole definitionsUnique assignmentsUsed in PolicySetsCreatedOnCreatedByUpdatedOnUpdatedByBlueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdBlueprint VersionBlueprint AssignmentId
    $($customPolicy.Scope)$($customPolicy.ScopeId)$($customPolicy.PolicyDisplayName -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyDefinitionId -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyCategory -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace "<", "<" -replace ">", ">")$($customPolicyUsedInPolicySets)$($customPolicy.CreatedOn)$($customPolicy.CreatedBy)$($customPolicy.UpdatedOn)$($customPolicy.UpdatedBy)$($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(@" +btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, + col_types: [ + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring' + ], +extensions: [{ name: 'sort' }] + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { - [void]$htmlTenantSummary.AppendLine(@" -

    $tenantCustomPoliciesCount Custom Policy definitions ($scopeNamingSummary)

    + [void]$htmlScopeInsights.AppendLine(@" +

    $blueprintsAssignedCount Blueprints assigned

    "@) } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) } - #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) - } - } + #endregion ScopeInsightsBlueprintAssignments - 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) + #BlueprintsScoped + #region ScopeInsightsBlueprintsScoped + if ($blueprintsScopedCount -gt 0) { + $tfCount = $blueprintsScopedCount + if ($mgOrSub -eq 'mg') { + $SIDivContentClass = 'contentSIMG' + $htmlTableIdentifier = $mgChild } - else { - $customPoliciesFromSuperiorMGs = "0" + if ($mgOrSub -eq 'sub') { + $SIDivContentClass = 'contentSISub' + $htmlTableIdentifier = $subscriptionId } - - if ($tenantCustomPoliciesCount -gt 0) { - $tfCount = $tenantCustomPoliciesCount - $htmlTableId = "TenantSummary_customPolicies" - $randomFunctionName = "func_$htmlTableId" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - + $htmlTableId = "ScopeInsights_BlueprintScoped_$($htmlTableIdentifier -replace '\(','_' -replace '\)','_' -replace '-','_' -replace '\.','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma +
    - - - - - - - - - - - - - + + + + "@) - $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) - } - @" + $htmlScopeInsightsBlueprintsScoped = $null + $htmlScopeInsightsBlueprintsScoped = foreach ($blueprintScoped in $blueprintsScoped) { + @" - - - - - - - - - - - - - + + + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYcustompolicies) - [void]$htmlTenantSummary.AppendLine(@" - -
    ScopeScope IdPolicy DisplayNamePolicyIdCategoryPolicy EffectRole definitionsUnique assignmentsUsed in PolicySetsCreatedOnCreatedByUpdatedOnUpdatedByBlueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintId
    $($customPolicy.Scope)$($customPolicy.ScopeId)$($customPolicy.PolicyDisplayName -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyDefinitionId -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyCategory -replace "<", "<" -replace ">", ">")$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace "<", "<" -replace ">", ">")$($customPolicyUsedInPolicySets)$($customPolicy.CreatedOn)$($customPolicy.CreatedBy)$($customPolicy.UpdatedOn)$($customPolicy.UpdatedBy)$($blueprintScoped.BlueprintName -replace '<', '<' -replace '>', '>')$($blueprintScoped.BlueprintDisplayName -replace '<', '<' -replace '>', '>')$($blueprintScoped.BlueprintDescription -replace '<', '<' -replace '>', '>')$($blueprintScoped.BlueprintId -replace '<', '<' -replace '>', '>')
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $tenantCustomPoliciesCount Custom Policy definitions ($scopeNamingSummary)

    + } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $blueprintsScopedCount Blueprints scoped

    "@) - } } - $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 + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsBlueprintsScoped - $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) + #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 -eq 'User') { + $rolesAssignedUser++ + } + if ($roleAssignment.ObjectType -eq 'Group') { + $rolesAssignedGroup++ + } + if ($roleAssignment.ObjectType -eq 'ServicePrincipal') { + $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) - $arrayCustomPoliciesOrphanedFinal = [System.Collections.ArrayList]@() - foreach ($customPolicyOrphaned in $customPoliciesOrphaned) { - if ($customPolicyOrphaned.Id) { - if (-not $htPoliciesUsedInPolicySets.($customPolicyOrphaned.Id)) { - $null = $arrayCustomPoliciesOrphanedFinal.Add($customPolicyOrphaned) - } + $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++ } - else { - Write-Host "!!!!!!!!!!!!!!!!!!!!! no Id" - Write-Host "## all:" - $customPoliciesOrphaned - Write-Host "## customPolicyOrphaned no Id:" - $customPolicyOrphaned + if ($roleAssignment.ObjectType -like 'User*') { + $rolesAssignedUser++ } - } - - #rgchange - $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() - foreach ($customPolicyOrphanedFinal in $arrayCustomPoliciesOrphanedFinal) { - if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $customPolicyOrphanedFinal.PolicyDefinitionId) { - $null = $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups.Add($customPolicyOrphanedFinal) + 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 (($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count -gt 0) { - $tfCount = ($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count - $htmlTableId = "TenantSummary_customPoliciesOrphaned" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - + $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(@" + +
    +   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 +
    - - + + + + + + + + + + + + + + + + + "@) - $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = $null - $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = foreach ($customPolicyOrphaned in $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { - @" + $htmlScopeInsightsRoleAssignments = $null + $htmlScopeInsightsRoleAssignments = foreach ($roleAssignment in ($rolesAssigned | Sort-Object -Property Level, MgName, MgId, SubscriptionName, SubscriptionId, Scope, Role, RoleId, ObjectId, RoleAssignmentId)) { + @" - - + + + + + + + + + + + + + + + + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustomPoliciesOrphandedTenantRoot) - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsRoleAssignments) + [void]$htmlScopeInsights.AppendLine(@"
    Policy DisplayNamePolicyIdScopeRoleRoleIdRole TypeDataCan do Role assignmentIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsRole AssignmentIdRelated Policy Assignment $noteOrNotCreatedOnCreatedBy
    $($customPolicyOrphaned.DisplayName)$($customPolicyOrphaned.PolicyDefinitionId)$($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)
    @@ -10073,755 +8631,1056 @@ extensions: [{ name: 'sort' }] var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - [void]$htmlTenantSummary.AppendLine(@" + if ($tfCount -gt 10) { + $spectrum = "10, $tfCount" + if ($tfCount -gt 50) { + $spectrum = "10, 25, 50, $tfCount" + } + if ($tfCount -gt 100) { + $spectrum = "10, 30, 50, 100, $tfCount" + } + if ($tfCount -gt 500) { + $spectrum = "10, 30, 50, 100, 250, $tfCount" + } + if ($tfCount -gt 1000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" + } + if ($tfCount -gt 2000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + if ($tfCount -gt 3000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" + } + [void]$htmlScopeInsights.AppendLine(@" paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) - } - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlScopeInsights.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, + linked_filters: true, + col_3: 'select', + col_4: 'select', + col_5: 'select', + col_9: 'multiple', + col_10: 'select', + locale: 'en-US', col_types: [ 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'date', 'caseinsensitivestring' ], -extensions: [{ name: 'sort' }] + watermark: ['', 'try owner||reader', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + extensions: [{ name: 'colsVisibility', text: 'Columns: ', enable_tick_all: true },{ name: 'sort' }] }; var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); tf.init();}} "@) - } - 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) - } - } - } + [void]$htmlScopeInsights.AppendLine(@" +

    $(($rbacAll).count) Role assignments

    - $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) - } + [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 } + } - $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() - foreach ($customPolicyOrphanedFinal in $arrayCustomPoliciesOrphanedFinal) { - if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $customPolicyOrphanedFinal.PolicyDefinitionId) { - $null = $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups.Add($customPolicyOrphanedFinal) - } + 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 + #[System.GC]::Collect() } + } - if (($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count -gt 0) { - $tfCount = ($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count - $htmlTableId = "TenantSummary_customPoliciesOrphaned" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - -"@) - $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = $null - $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = foreach ($customPolicyOrphaned in $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { - @" - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustomPoliciesOrphandedTenantRoot) - [void]$htmlTenantSummary.AppendLine(@" - -
    Policy DisplayNamePolicyId
    $($customPolicyOrphaned.DisplayName)$($customPolicyOrphaned.PolicyDefinitionId)
    -
    - -"@) } else { - [void]$htmlTenantSummary.AppendLine(@" -

    $($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups.count) Orphaned Custom Policy definitions ($scopeNamingSummary)

    -"@) + $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).relatedPolicyAssignment = 'none' + $htRoleAssignmentRelatedPolicyAssignments.($roleAssignmentIdUnique.RoleAssignmentId).relatedPolicyAssignmentClear = 'none' } - } - #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)) { + 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 { - $policySetUniqueAssignments = $policyPolicySetBaseQueryUniqueAssignments.where( { $_.PolicyDefinitionId -eq $tenantPolicySet.Id }).PolicyAssignmentId - $policySetUniqueAssignmentsArray = [System.Collections.ArrayList]@() - foreach ($policySetUniqueAssignment in $policySetUniqueAssignments) { - $null = $policySetUniqueAssignmentsArray.Add($policySetUniqueAssignment) + 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 } - $policySetUniqueAssignmentsCount = ($policySetUniqueAssignments).count - if ($policySetUniqueAssignmentsCount -gt 0) { - $policySetUniqueAssignmentsList = "($($policySetUniqueAssignmentsArray -join "$CsvDelimiterOpposite "))" - $policySetUniqueAssignment = "$policySetUniqueAssignmentsCount $policySetUniqueAssignmentsList" + } + $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" + #if ($cnter % 5000 -eq 0) { + #[System.GC]::Collect() + #} + } + $scope = $null + + if ($rbac.RoleAssignmentPIM -eq 'true') { + $pim = $true + $pimAssignmentType = $rbac.RoleAssignmentPIMAssignmentType + $pimSlotStart = $($rbac.RoleAssignmentPIMSlotStart) + $pimSlotEnd = $($rbac.RoleAssignmentPIMSlotEnd) } else { - $policySetUniqueAssignment = $policySetUniqueAssignmentsCount + $pim = $false + $pimAssignmentType = '' + $pimSlotStart = '' + $pimSlotEnd = '' } - $policySetPoliciesArray = [System.Collections.ArrayList]@() - $policySetPoliciesArrayClean = [System.Collections.ArrayList]@() - foreach ($policyPolicySet in $tenantPolicySet.PolicySetPolicyIds) { - $hlpPolicyDef = ($htCacheDefinitionsPolicy).($policyPolicySet) - - if ($hlpPolicyDef.Type -eq "Builtin") { - $null = $policySetPoliciesArray.Add("$($hlpPolicyDef.LinkToAzAdvertizer) ($policyPolicySet)") + if ($rbac.RoleAssignmentId -like '/providers/Microsoft.Management/managementGroups/*') { + $tenOrMgOrSubOrRGOrRes = 'Mg' + if (-not [String]::IsNullOrEmpty($rbac.SubscriptionId)) { + $scope = "inherited $($rbac.RoleAssignmentScopeName)" } else { - if ($hlpPolicyDef.DisplayName) { - if ([string]::IsNullOrEmpty($hlpPolicyDef.DisplayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = $hlpPolicyDef.DisplayName - } + if (($rbac.RoleAssignmentScopeName) -eq $rbac.MgId) { + $scope = 'thisScope MG' } else { - $displayName = "noDisplayNameGiven" + $scope = "inherited $($rbac.RoleAssignmentScopeName)" } - $null = $policySetPoliciesArray.Add("$($displayName -replace "<", "<" -replace ">", ">") ($policyPolicySet)") } - if ($hlpPolicyDef.DisplayName) { - if ([string]::IsNullOrEmpty($hlpPolicyDef.DisplayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = $hlpPolicyDef.DisplayName - } + } + + if ($rbac.RoleAssignmentId -like '/subscriptions/*') { + $scope = 'thisScope Sub' + $tenOrMgOrSubOrRGOrRes = 'Sub' + } + + if ($rbac.RoleAssignmentId -like '/subscriptions/*/resourcegroups/*') { + $scope = 'thisScope Sub RG' + $tenOrMgOrSubOrRGOrRes = 'RG' + } + + if ($rbac.RoleAssignmentId -like '/subscriptions/*/resourcegroups/*/providers/*/providers/*') { + $scope = 'thisScope Sub RG Res' + $tenOrMgOrSubOrRGOrRes = 'Res' + } + + if ($rbac.RoleAssignmentId -like '/providers/Microsoft.Authorization/roleAssignments/*') { + $scope = 'inherited Tenant' + $tenOrMgOrSubOrRGOrRes = 'Ten' + } + + $objectTypeUserType = '' + if ($rbac.RoleAssignmentIdentityObjectType -eq 'User') { + if ($htUserTypesGuest.($rbac.RoleAssignmentIdentityObjectId)) { + $objectTypeUserType = 'Guest' } else { - $displayName = "noDisplayNameGiven" + $objectTypeUserType = 'Member' } - $null = $policySetPoliciesArrayClean.Add("$($displayName) ($policyPolicySet)") } - $policySetPoliciesCount = ($policySetPoliciesArray).count - if ($policySetPoliciesCount -gt 0) { - $policiesUsed = "$policySetPoliciesCount ($(($policySetPoliciesArray | sort-Object) -join "$CsvDelimiterOpposite "))" - $policiesUsedClean = "$policySetPoliciesCount ($(($policySetPoliciesArrayClean | sort-Object) -join "$CsvDelimiterOpposite "))" + if (-not [string]::IsNullOrEmpty($rbac.RoleDataActions) -or -not [string]::IsNullOrEmpty($rbac.RoleNotDataActions)) { + $roleManageData = 'true' } else { - $policiesUsed = "0 really?" - $policiesUsedClean = "0 really?" + $roleManageData = 'false' } - 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) - } - } + $hlpRoleAssignmentRelatedPolicyAssignments = $htRoleAssignmentRelatedPolicyAssignments.($rbac.RoleAssignmentId) - $createdOn = "" - $createdBy = "" - $updatedOn = "" - $updatedBy = "" - if ($tenantPolicySet.Json.properties.metadata.createdOn) { - $createdOn = $tenantPolicySet.Json.properties.metadata.createdOn.ToString("yyyy-MM-dd HH:mm:ss") - } - if ($tenantPolicySet.Json.properties.metadata.createdBy) { - $createdBy = $tenantPolicySet.Json.properties.metadata.createdBy - if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) { - $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details - } - } - if ($tenantPolicySet.Json.properties.metadata.updatedOn) { - $updatedOn = $tenantPolicySet.Json.properties.metadata.updatedOn.ToString("yyyy-MM-dd HH:mm:ss") - } - if ($tenantPolicySet.Json.properties.metadata.updatedBy) { - $updatedBy = $tenantPolicySet.Json.properties.metadata.updatedBy - if ($htIdentitiesWithRoleAssignmentsUnique.($updatedBy)) { - $updatedBy = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).details - } - } + if (-not $NoAADGroupsResolveMembers) { + if ($rbac.RoleAssignmentIdentityObjectType -eq 'Group') { - $null = $script:customPolicySetsDetailed.Add([PSCustomObject]@{ - Type = "Custom" - ScopeMGLevel = $tenantPolicySet.ScopeMGLevel - Scope = $tenantPolicySet.ScopeMgSub - ScopeId = $tenantPolicySet.ScopeId - PolicySetDisplayName = $tenantPolicySet.DisplayName - 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) - }) + $grpHlpr = $htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId) + $null = $script:rbacAll.Add([PSCustomObject]@{ + Level = $rbac.Level + RoleAssignmentId = $rbac.RoleAssignmentId + RoleAssignmentPIMRelated = $pim + RoleAssignmentPIMAssignmentType = $pimAssignmentType + RoleAssignmentPIMAssignmentSlotStart = $pimSlotStart + RoleAssignmentPIMAssignmentSlotEnd = $pimSlotEnd + RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName + RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG + RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes + 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 + 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 + TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes + RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment + RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear + RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner + RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP + RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments + }) - $null = $script:tenantPolicySetsDetailed.Add([PSCustomObject]@{ - Type = "Custom" - ScopeMGLevel = $tenantPolicySet.ScopeMGLevel - Scope = $tenantPolicySet.ScopeMgSub - ScopeId = $tenantPolicySet.ScopeId - PolicySetDisplayName = $tenantPolicySet.DisplayName - PolicySetDefinitionName = $tenantPolicySet.PolicyDefinitionId -replace ".*/" - PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId - PolicySetCategory = $tenantPolicySet.Category - UniqueAssignments = $policySetUniqueAssignmentsCount - PoliciesUsed = $policySetPoliciesCount - CreatedOn = $createdOn - CreatedBy = $createdBy - UpdatedOn = $updatedOn - UpdatedBy = $updatedBy - #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings) - }) - } - else { - $null = $script:tenantPolicySetsDetailed.Add([PSCustomObject]@{ - Type = "BuiltIn" - ScopeMGLevel = $tenantPolicySet.ScopeMGLevel - Scope = "" - ScopeId = "" - PolicySetDisplayName = $tenantPolicySet.DisplayName - PolicySetDefinitionName = $tenantPolicySet.PolicyDefinitionId -replace ".*/" - PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId - PolicySetCategory = $tenantPolicySet.Category - UniqueAssignments = $policySetUniqueAssignmentsCount - PoliciesUsed = $policySetPoliciesCount - CreatedOn = "" - CreatedBy = "" - UpdatedOn = "" - UpdatedBy = "" - #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings) - }) - } - } + if ($grpHlpr.MembersAllCount -gt 0) { - if (-not $NoCsvExport) { - $csvFilename = "$($filename)_PolicySetDefinitions" - Write-Host " Exporting PolicySetDefinitions CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'" - $tenantPolicySetsDetailed | Sort-Object -Property Type, Scope, PolicySetDefinitionId | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation - } + if ($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount -le $AADGroupMembersLimit) { - if ($getMgParentName -eq "Tenant Root") { - if ($tenantCustompolicySetsCount -gt $LimitPOLICYPolicySetDefinitionsScopedTenant * ($LimitCriticalPercentage / 100)) { - $faimage = "" - } - else { - $faimage = "" - } + 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 ($tenantCustompolicySetsCount -gt 0) { - $tfCount = $tenantCustompolicySetsCount - $htmlTableId = "TenantSummary_customPolicySets" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - - - - - - - - - - -"@) - $htmlSUMMARYtenanttotalcustompolicySets = $null - $htmlSUMMARYtenanttotalcustompolicySets = foreach ($customPolicySet in $customPolicySetsDetailed | Sort-Object @{Expression = { $_.Scope } }, @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { - @" - - - - - - - - - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustompolicySets) - [void]$htmlTenantSummary.AppendLine(@" - -
    ScopeScopeIdPolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies used in PolicySetCreatedOnCreatedByUpdatedOnUpdatedBy
    $($customPolicySet.Scope)$($customPolicySet.ScopeId)$($customPolicySet.PolicySetDisplayName -replace "<", "<" -replace ">", ">")$($customPolicySet.PolicySetDefinitionId -replace "<", "<" -replace ">", ">")$($customPolicySet.PolicySetCategory -replace "<", "<" -replace ">", ">")$($customPolicySet.UniqueAssignments -replace "<", "<" -replace ">", ">")$($customPolicySet.PoliciesUsed)$($customPolicySet.CreatedOn)$($customPolicySet.CreatedBy)$($customPolicySet.UpdatedOn)$($customPolicySet.UpdatedBy)
    -
    - -"@) } else { - [void]$htmlTenantSummary.AppendLine(@" -

    $tenantCustomPolicySetsCount Custom PolicySet definitions ($scopeNamingSummary)

    -"@) + + 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 + RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName + RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG + RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes + 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 + 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 + TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes + RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment + RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear + RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner + RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP + RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments + }) } } - #SUMMARY NOT tenant total custom policySet definitions - else { - $faimage = "" - if ($tenantCustompolicySetsCount -gt $LimitPOLICYPolicySetDefinitionsScopedTenant * ($LimitCriticalPercentage / 100)) { - $faimage = "" - } - else { - $faimage = "" - } + #endregion createRBACAll - if ($tenantCustompolicySetsCount -gt 0) { - $custompolicySetsFromSuperiorMGs = $tenantCustompolicySetsCount - (($custompolicySetsInScopeArray).count) - } - else { - $custompolicySetsFromSuperiorMGs = "0" - } + 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 ($tenantCustompolicySetsCount -gt 0) { - $tfCount = $tenantCustompolicySetsCount - $htmlTableId = "TenantSummary_customPolicySets" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - - - - - - - - - - -"@) - $htmlSUMMARYtenanttotalcustompolicySets = $null - $htmlSUMMARYtenanttotalcustompolicySets = foreach ($customPolicySet in $customPolicySetsDetailed | Sort-Object @{Expression = { $_.Scope } }, @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { - @" - - - - - - - - - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustompolicySets) - [void]$htmlTenantSummary.AppendLine(@" - -
    ScopeScope IdPolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies used in PolicySetCreatedOnCreatedByUpdatedOnUpdatedBy
    $($customPolicySet.Scope)$($customPolicySet.ScopeId)$($customPolicySet.PolicySetDisplayName -replace "<", "<" -replace ">", ">")$($customPolicySet.PolicySetDefinitionId -replace "<", "<" -replace ">", ">")$($customPolicySet.PolicySetCategory -replace "<", "<" -replace ">", ">")$($customPolicySet.UniqueAssignments -replace "<", "<" -replace ">", ">")$($customPolicySet.PoliciesUsed)$($customPolicySet.CreatedOn)$($customPolicySet.CreatedBy)$($customPolicySet.UpdatedOn)$($customPolicySet.UpdatedBy)
    -
    - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $tenantCustomPolicySetsCount Custom PolicySet definitions ($scopeNamingSummary)

    -"@) + + $script:htIdentitiesWithRoleAssignmentsUnique.($identityWithRoleAssignment.ObjectId).details = $arr -join "$CsvDelimiterOpposite " + $script:htIdentitiesWithRoleAssignmentsUnique.($identityWithRoleAssignment.ObjectId).detailsJson = $ht } } - $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 + #endregion identitiesThatCreatedRoleAssignmentsButDontHaveARoleAssignmentThemselve - #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) - } + #enrich rbacAll with createdBy and UpdatedBy identity information + #region enrichrbacAll + $htNonResolvedIdentities = @{} + foreach ($rbac in $rbacAll) { + $createdBy = $rbac.createdBy + if ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) { + $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details + $rbac.CreatedBy = $createdBy + } + else { + if (-not $htNonResolvedIdentities.($rbac.createdBy)) { + $htNonResolvedIdentities.($rbac.createdBy) = @{} } } + } + #endregion enrichrbacAll - $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() - foreach ($customPolicySetOrphaned in $custompolicySetSetsOrphaned) { - if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $customPolicySetOrphaned.PolicyDefinitionId) { - $null = $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups.Add($customPolicySetOrphaned) + $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) { - if (($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count -gt 0) { - $tfCount = ($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count - $htmlTableId = "TenantSummary_customPolicySetsOrphaned" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - -"@) - $htmlSUMMARYCustompolicySetOrphandedTenantRoot = $null - $htmlSUMMARYCustompolicySetOrphandedTenantRoot = foreach ($custompolicySetOrphaned in $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { - @" - - - - + $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 -join '","') + Write-Host " IdentitiesToCheck: Batch #$batchCnt/$($ObjectBatchCount) ($(($batch.Group).Count))" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/v1.0/directoryObjects/getByIds" + $method = 'POST' + $body = @" + { + "ids":[$($nonResolvedIdentitiesToCheck)] + } "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustompolicySetOrphandedTenantRoot) - [void]$htmlTenantSummary.AppendLine(@" - -
    PolicySet DisplayNamePolicySetId
    $($custompolicySetOrphaned.DisplayName)$($custompolicySetOrphaned.PolicyDefinitionId)
    -
    - -"@) + $policyRoleDefinitionsClearArray = @() + $policyRoleDefinitionsClearArray = foreach ($roleDefinitionId in $tenantPolicy.RoleDefinitionIds | Sort-Object) { + ($htCacheDefinitionsRole).($roleDefinitionId -replace '.*/').Name + } + $policyRoleDefinitions = $policyRoleDefinitionsArray -join "$CsvDelimiterOpposite " + $policyRoleDefinitionsClear = $policyRoleDefinitionsClearArray -join "$CsvDelimiterOpposite " } else { - [void]$htmlTenantSummary.AppendLine(@" -

    $(($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)

    -"@) + $policyRoleDefinitions = 'n/a' + $policyRoleDefinitionsClear = 'n/a' } - } - #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 ($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 ($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 ($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 - 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(@" - -
    - Download CSV semicolon | comma - - - - - + $null = $script:customPoliciesDetailed.Add([PSCustomObject]@{ + Type = 'Custom' + ScopeMGLevel = $tenantPolicy.ScopeMGLevel + Scope = $tenantPolicy.ScopeMgSub + ScopeId = $tenantPolicy.ScopeId + PolicyDisplayName = $tenantPolicy.DisplayName + PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId + 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 + #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.PolicyDefinitionId -replace '.*/' + PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId + 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 + }) + } + else { + $null = $script:tenantPoliciesDetailed.Add([PSCustomObject]@{ + Type = 'BuiltIn' + ScopeMGLevel = $null + Scope = $null + ScopeId = $null + PolicyDisplayName = $tenantPolicy.DisplayName + PolicyDefinitionName = $tenantPolicy.PolicyDefinitionId -replace '.*/' + PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId + 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 + }) + } + } + + 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 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation + } + + if ($getMgParentName -eq 'Tenant Root') { + + if ($tenantCustomPoliciesCount -gt 0) { + $tfCount = $tenantCustomPoliciesCount + $htmlTableId = 'TenantSummary_customPolicies' + $randomFunctionName = "func_$htmlTableId" + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma +
    PolicySet DisplayNamePolicySetId
    + + + + + + + + + + + + + + + "@) - $htmlSUMMARYCustompolicySetOrphandedTenantRoot = $null - $htmlSUMMARYCustompolicySetOrphandedTenantRoot = foreach ($custompolicySetOrphaned in $arraycustompolicySetsOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { + $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) + } @" - - + + + + + + + + + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustompolicySetOrphandedTenantRoot) + + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYcustompolicies) + $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $htmlTenantSummary = [System.Text.StringBuilder]::new() [void]$htmlTenantSummary.AppendLine(@"
    ScopeScope IdPolicy DisplayNamePolicyIdCategoryEffectRole definitionsUnique assignmentsUsed in PolicySetsCreatedOnCreatedByUpdatedOnUpdatedBy
    $($custompolicySetOrphaned.DisplayName)$($custompolicySetOrphaned.policyDefinitionId)$($customPolicy.Scope)$($customPolicy.ScopeId)$($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicyUsedInPolicySets)$($customPolicy.CreatedOn)$($customPolicy.CreatedBy)$($customPolicy.UpdatedOn)$($customPolicy.UpdatedBy)
    @@ -10857,12 +9716,32 @@ paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_ "@) } [void]$htmlTenantSummary.AppendLine(@" -btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, + btn_reset: true, + highlight_keywords: true, + alternate_rows: true, + auto_filter: { + delay: 1100 + }, + no_results_message: true, + col_widths: ['', '150px', '150px', '250px', '150px', '150px', '150px', '150px', '250px', '', '150px', '', '150px'], + col_0: 'select', + locale: 'en-US', col_types: [ 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'date', + 'caseinsensitivestring', + 'date', 'caseinsensitivestring' ], -extensions: [{ name: 'sort' }] + extensions: [{ name: 'sort' }] }; var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); tf.init();}} @@ -10871,77 +9750,98 @@ extensions: [{ name: 'sort' }] } else { [void]$htmlTenantSummary.AppendLine(@" -

    $(($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)

    +

    $tenantCustomPoliciesCount Custom Policy definitions ($scopeNamingSummary)

    "@) } } - #endregion SUMMARYCustompolicySetOrphandedTenantRoot + #SUMMARY NOT tenant total custom policy definitions + else { + $faimage = "" - $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 ($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 (($policySetsDeprecated).count -gt 0) { - $tfCount = ($policySetsDeprecated).count - $htmlTableId = "TenantSummary_policySetsDeprecated" - [void]$htmlTenantSummary.AppendLine(@" - + if ($tenantCustomPoliciesCount -gt 0) { + $tfCount = $tenantCustomPoliciesCount + $htmlTableId = 'TenantSummary_customPolicies' + $randomFunctionName = "func_$htmlTableId" + [void]$htmlTenantSummary.AppendLine(@" +
    Download CSV semicolon | comma - +
    - - + + - + + + + + + + + + "@) - $htmlSUMMARYPolicySetsDeprecatedPolicy = $null - $htmlSUMMARYPolicySetsDeprecatedPolicy = foreach ($policySetDeprecated in $policySetsDeprecated | Sort-Object @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { - - if ($policySetDeprecated.DeprecatedProperty -eq $true) { - $deprecatedProperty = "true" - } - else { - $deprecatedProperty = "false" - } - @" + $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) + } + @" - - - - - + + + + + + + + + + + + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicySetsDeprecatedPolicy) - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYcustompolicies) + [void]$htmlTenantSummary.AppendLine(@"
    PolicySet DisplayNamePolicySetIdScopeScope Id Policy DisplayName PolicyIdDeprecated PropertyCategoryPolicy EffectRole definitionsUnique assignmentsUsed in PolicySetsCreatedOnCreatedByUpdatedOnUpdatedBy
    $($policySetDeprecated.PolicySetDisplayName -replace "<", "<" -replace ">", ">")$($policySetDeprecated.PolicySetDefinitionId -replace "<", "<" -replace ">", ">")$($policySetDeprecated.PolicyDisplayName -replace "<", "<" -replace ">", ">")$($policySetDeprecated.PolicyId -replace "<", "<" -replace ">", ">")$deprecatedProperty$($customPolicy.Scope)$($customPolicy.ScopeId)$($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicyUsedInPolicySets)$($customPolicy.CreatedOn)$($customPolicy.CreatedBy)$($customPolicy.UpdatedOn)$($customPolicy.UpdatedBy)
    @@ -10951,37 +9851,47 @@ extensions: [{ name: 'sort' }] var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - [void]$htmlTenantSummary.AppendLine(@" + if ($tfCount -gt 10) { + $spectrum = "10, $tfCount" + if ($tfCount -gt 50) { + $spectrum = "10, 25, 50, $tfCount" + } + if ($tfCount -gt 100) { + $spectrum = "10, 30, 50, 100, $tfCount" + } + if ($tfCount -gt 500) { + $spectrum = "10, 30, 50, 100, 250, $tfCount" + } + if ($tfCount -gt 1000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" + } + if ($tfCount -gt 2000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + if ($tfCount -gt 3000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" + } + [void]$htmlTenantSummary.AppendLine(@" paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) - } - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, + col_0: 'select', + locale: 'en-US', col_types: [ 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'date', + 'caseinsensitivestring', + 'date', 'caseinsensitivestring' ], extensions: [{ name: 'sort' }] @@ -10990,106 +9900,84 @@ extensions: [{ name: 'sort' }] tf.init();}} "@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $(($policySetsDeprecated).count) PolicySets / deprecated Built-in Policy

    + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $tenantCustomPoliciesCount Custom Policy definitions ($scopeNamingSummary)

    "@) + } } - #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) { + $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 - $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 - }) - } + $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) } } } - #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" - }) + $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 (($policyAssignmentsDeprecated).count -gt 0) { - $tfCount = ($policyAssignmentsDeprecated).count - $htmlTableId = "TenantSummary_policyAssignmentsDeprecated" - [void]$htmlTenantSummary.AppendLine(@" - + if (($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count -gt 0) { + $tfCount = ($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count + $htmlTableId = 'TenantSummary_customPoliciesOrphaned' + [void]$htmlTenantSummary.AppendLine(@" +
    Download CSV semicolon | comma - +
    - - - - - - "@) - $htmlSUMMARYPolicyAssignmentsDeprecatedPolicy = $null - $htmlSUMMARYPolicyAssignmentsDeprecatedPolicy = foreach ($policyAssignmentDeprecated in $policyAssignmentsDeprecated | Sort-Object @{Expression = { $_.PolicyAssignmentDisplayName } }, @{Expression = { $_.PolicyAssignmentId } }) { - @" + $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = $null + $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = foreach ($customPolicyOrphaned in $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { + @" - - - - - - - - + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyAssignmentsDeprecatedPolicy) - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustomPoliciesOrphandedTenantRoot) + [void]$htmlTenantSummary.AppendLine(@"
    Policy Assignment DisplayNamePolicy AssignmentIdPolicy/PolicySetPolicySet DisplayNamePolicySetId Policy DisplayName PolicyIdDeprecated Property
    $($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)$($customPolicyOrphaned.DisplayName)$($customPolicyOrphaned.PolicyDefinitionId)
    @@ -11099,40 +9987,33 @@ extensions: [{ name: 'sort' }] var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - [void]$htmlTenantSummary.AppendLine(@" + if ($tfCount -gt 10) { + $spectrum = "10, $tfCount" + if ($tfCount -gt 50) { + $spectrum = "10, 25, 50, $tfCount" + } + if ($tfCount -gt 100) { + $spectrum = "10, 30, 50, 100, $tfCount" + } + if ($tfCount -gt 500) { + $spectrum = "10, 30, 50, 100, 250, $tfCount" + } + if ($tfCount -gt 1000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" + } + if ($tfCount -gt 2000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + if ($tfCount -gt 3000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" + } + [void]$htmlTenantSummary.AppendLine(@" paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) - } - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - col_2: 'select', col_types: [ - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring' ], @@ -11142,162 +10023,85 @@ extensions: [{ name: 'sort' }] tf.init();}} "@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $(($policyAssignmentsDeprecated).count) Policy assignments / deprecated Built-in Policy

    + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $(($customPoliciesOrphaned).count) Orphaned Custom Policy definitions ($scopeNamingSummary)

    "@) + } } - #endregion SUMMARYPolicyAssignmentsDeprecatedPolicy - $endcustpolassdeprpol = Get-Date - Write-Host " processing PolicyAssignmentsDeprecatedPolicy duration: $((NEW-TIMESPAN -Start $startcustpolassdeprpol -End $endcustpolassdeprpol).TotalSeconds) seconds" + #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) + } + } + } - #region SUMMARYPolicyExemptions - Write-Host " processing TenantSummary Policy exemptions" - $policyExemptionsCount = ($htPolicyAssignmentExemptions.Keys).Count + $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) + } + } + } - if ($policyExemptionsCount -gt 0) { - $tfCount = $policyExemptionsCount - $htmlTableId = "TenantSummary_policyExemptions" + $arrayCustomPoliciesOrphanedFinal = [System.Collections.ArrayList]@() + foreach ($customPolicyOrphanedInScopeArray in $customPoliciesOrphanedInScopeArray) { + if (-not $htPoliciesUsedInPolicySets.($customPolicyOrphanedInScopeArray.Id)) { + $null = $arrayCustomPoliciesOrphanedFinal.Add($customPolicyOrphanedInScopeArray) + } + } - $expiredExemptionsCount = ($htPolicyAssignmentExemptions.Keys | where-object { $htPolicyAssignmentExemptions.($_).exemption.properties.expiresOn -and $htPolicyAssignmentExemptions.($_).exemption.properties.expiresOn -lt (Get-Date).ToUniversalTime() } | Measure-Object).count + $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() + foreach ($customPolicyOrphanedFinal in $arrayCustomPoliciesOrphanedFinal) { + if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $customPolicyOrphanedFinal.PolicyDefinitionId) { + $null = $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups.Add($customPolicyOrphanedFinal) + } + } - [void]$htmlTenantSummary.AppendLine(@" - + if (($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count -gt 0) { + $tfCount = ($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups).count + $htmlTableId = 'TenantSummary_customPoliciesOrphaned' + [void]$htmlTenantSummary.AppendLine(@" +
    Download CSV semicolon | comma - +
    - - - - - - - - - - - - + + "@) - - $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 = "" - } - - if (-not $NoCsvExport) { - $null = $exemptionData4CSVExport.Add([PSCustomObject]@{ - Scope = $exemptionScope - ManagementGroupId = $mgId - ManagementGroupName = $mgName - SubscriptionId = $subId - SubscriptionName = $subName - ResourceGroup = $rgName - ResourceName_ResourceType = $resName - DisplayName = $exemption.properties.DisplayName - Category = $exemption.properties.exemptionCategory - ExpiresOn_UTC = $exemptionExpiresOn - Id = $exemption.Id - PolicyAssignmentId = $exemption.properties.policyAssignmentId - }) - } - - @" + $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = $null + $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = foreach ($customPolicyOrphaned in $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { + @" - - - - - - - - - - - - + + "@ - } - - 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(@" + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustomPoliciesOrphandedTenantRoot) + [void]$htmlTenantSummary.AppendLine(@"
    ScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameResourceGroupResourceName / ResourceTypeDisplayNameCategoryExpiresOn (UTC)IdPolicy AssignmentIdPolicy DisplayNamePolicyId
    $($exemptionScope)$($mgId)$($mgName -replace "<", "<" -replace ">", ">")$($subId)$($subName)$($rgName)$($resName)$($exemption.properties.DisplayName -replace "<", "<" -replace ">", ">")$($exemption.properties.exemptionCategory -replace "<", "<" -replace ">", ">")$($exemptionExpiresOn)$($exemption.Id)$($exemption.properties.policyAssignmentId -replace "<", "<" -replace ">", ">")$($customPolicyOrphaned.DisplayName)$($customPolicyOrphaned.PolicyDefinitionId)
    @@ -11307,44 +10111,33 @@ extensions: [{ name: 'sort' }] var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - [void]$htmlTenantSummary.AppendLine(@" + if ($tfCount -gt 10) { + $spectrum = "10, $tfCount" + if ($tfCount -gt 50) { + $spectrum = "10, 25, 50, $tfCount" + } + if ($tfCount -gt 100) { + $spectrum = "10, 30, 50, 100, $tfCount" + } + if ($tfCount -gt 500) { + $spectrum = "10, 30, 50, 100, 250, $tfCount" + } + if ($tfCount -gt 1000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" + } + if ($tfCount -gt 2000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + if ($tfCount -gt 3000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" + } + [void]$htmlTenantSummary.AppendLine(@" paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) - } - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - col_0: 'select', col_types: [ - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring' ], @@ -11354,927 +10147,629 @@ extensions: [{ name: 'sort' }] tf.init();}} "@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $($policyExemptionsCount) Policy exemptions

    + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $($arrayCustomPoliciesOrphanedFinalIncludingResourceGroups.count) Orphaned Custom Policy definitions ($scopeNamingSummary)

    "@) + } } - #endregion SUMMARYPolicyExemptions + #endregion SUMMARYCustomPoliciesOrphandedTenantRoot + $endcustpolorph = Get-Date + Write-Host " processing TenantSummary Custom Policy definitions orphaned duration: $((NEW-TIMESPAN -Start $startcustpolorph -End $endcustpolorph).TotalSeconds) seconds" - #region SUMMARYPolicyAssignmentsOrphaned - Write-Host " processing TenantSummary PolicyAssignments orphaned" + #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)) { - if ($policyAssignmentsOrphanedCount -gt 0) { - $tfCount = $policyAssignmentsOrphanedCount - $htmlTableId = "TenantSummary_policyAssignmentsOrphaned" + $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 + } - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - -"@) + $policySetPoliciesArray = [System.Collections.ArrayList]@() + $policySetPoliciesArrayClean = [System.Collections.ArrayList]@() + $policySetPoliciesArrayIdOnly = [System.Collections.ArrayList]@() + foreach ($policyPolicySet in $tenantPolicySet.PolicySetPolicyIds) { + $hlpPolicyDef = ($htCacheDefinitionsPolicy).($policyPolicySet) - $htmlSUMMARYPolicyassignmentsOrphaned = $null - $htmlSUMMARYPolicyassignmentsOrphaned = foreach ($orphanedPolicyAssignment in $policyAssignmentsOrphaned | Sort-Object -Property PolicyAssignmentId) { - @" - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyassignmentsOrphaned) - [void]$htmlTenantSummary.AppendLine(@" - -
    Policy AssignmentIdPolicy/Set definition
    $($orphanedPolicyAssignment.policyAssignmentId -replace "<", "<" -replace ">", ">")$($orphanedPolicyAssignment.PolicyDefinitionId -replace "<", "<" -replace ">", ">")
    -
    - -"@) - } - 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) + $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?' + } - #this - if (-not $htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower())) { - $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()) = @{} + 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) + } } - if (($htCacheDefinitionsRole).($roleAssignment.RoleDefinitionId).IsCustom) { - $roleDefinitionType = "custom" - } - else { - $roleDefinitionType = "builtin" + $createdOn = '' + $createdBy = '' + $createdByJson = '' + $updatedOn = '' + $updatedBy = '' + $updatedByJson = '' + if ($tenantPolicySet.Json.properties.metadata.createdOn) { + $createdOn = $tenantPolicySet.Json.properties.metadata.createdOn.ToString('yyyy-MM-dd HH:mm:ss') } - - $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 + 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 + } } - else { - $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments = $array + if ($tenantPolicySet.Json.properties.metadata.updatedOn) { + $updatedOn = $tenantPolicySet.Json.properties.metadata.updatedOn.ToString('yyyy-MM-dd HH:mm:ss') } - } - } - - if ($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 - }) + 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 - #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) { + $null = $script:customPolicySetsDetailed.Add([PSCustomObject]@{ + Type = 'Custom' + ScopeMGLevel = $tenantPolicySet.ScopeMGLevel + Scope = $tenantPolicySet.ScopeMgSub + ScopeId = $tenantPolicySet.ScopeId + PolicySetDisplayName = $tenantPolicySet.DisplayName + 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) + }) - #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))" - } - } - } + $null = $script:tenantPolicySetsDetailed.Add([PSCustomObject]@{ + Type = 'Custom' + ScopeMGLevel = $tenantPolicySet.ScopeMGLevel + Scope = $tenantPolicySet.ScopeMgSub + ScopeId = $tenantPolicySet.ScopeId + PolicySetDisplayName = $tenantPolicySet.DisplayName + PolicySetDefinitionName = $tenantPolicySet.PolicyDefinitionId -replace '.*/' + PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId + PolicySetCategory = $tenantPolicySet.Category + UniqueAssignmentsCount = $policySetUniqueAssignmentsCount + UniqueAssignments = $policySetUniqueAssignments + PoliciesUsedCount = $policySetPoliciesCount + PoliciesUsed = $policySetPoliciesArrayClean + PoliciesUsed4JSON = $policySetPoliciesArrayIdOnly + CreatedOn = $createdOn + CreatedBy = $createdBy + CreatedByJson = $createdByJson + UpdatedOn = $updatedOn + UpdatedBy = $updatedBy + UpdatedByJson = $updatedByJson + #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings) + Json = $tenantPolicySet.Json + }) - $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 - } - } + $null = $script:tenantPolicySetsDetailed.Add([PSCustomObject]@{ + Type = 'BuiltIn' + ScopeMGLevel = $null + Scope = $null + ScopeId = $null + PolicySetDisplayName = $tenantPolicySet.DisplayName + PolicySetDefinitionName = $tenantPolicySet.PolicyDefinitionId -replace '.*/' + PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId + PolicySetCategory = $tenantPolicySet.Category + UniqueAssignmentsCount = $policySetUniqueAssignmentsCount + UniqueAssignments = $policySetUniqueAssignments + PoliciesUsedCount = $policySetPoliciesCount + PoliciesUsed = $policySetPoliciesArrayClean + PoliciesUsed4JSON = $policySetPoliciesArrayIdOnly + CreatedOn = '' + CreatedBy = '' + CreatedByJson = $null + UpdatedOn = '' + UpdatedBy = '' + UpdatedByJson = $null + #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings) + Json = $tenantPolicySet.Json + }) } - #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" - #if ($cnter % 5000 -eq 0) { - #[System.GC]::Collect() - #} - } + if (-not $NoCsvExport) { + $csvFilename = "$($filename)_PolicySetDefinitions" + Write-Host " Exporting PolicySetDefinitions CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'" + $tenantPolicySetsDetailed | Select-Object -ExcludeProperty UniqueAssignments, PoliciesUsed, CreatedByJson, UpdatedByJson, Json | Sort-Object -Property Type, Scope, PolicySetDefinitionId | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation + } - #region AzAdvertizerLinkOrNot - if ($policyAssignmentAll.PolicyType -eq "builtin") { - if ($policyAssignmentAll.PolicyVariant -eq "Policy") { - $azaLinkOrNot = "$($policyAssignmentAll.Policy)" - } - else { - $azaLinkOrNot = "$($policyAssignmentAll.Policy)" - } + if ($getMgParentName -eq 'Tenant Root') { + if ($tenantCustompolicySetsCount -gt $LimitPOLICYPolicySetDefinitionsScopedTenant * ($LimitCriticalPercentage / 100)) { + $faimage = "" } 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" - } - } - } + $faimage = "" } - #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 '.*/')" + if ($tenantCustompolicySetsCount -gt 0) { + $tfCount = $tenantCustompolicySetsCount + $htmlTableId = 'TenantSummary_customPolicySets' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + +"@) + $htmlSUMMARYtenanttotalcustompolicySets = $null + $htmlSUMMARYtenanttotalcustompolicySets = foreach ($customPolicySet in $customPolicySetsDetailed | Sort-Object @{Expression = { $_.Scope } }, @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { + @" + + + + + + + + + + + + + +"@ } - else { - if (($policyAssignmentAll.PolicyAssignmentScope -replace '.*/') -eq $policyAssignmentAll.MgId) { - $scope = "thisScope Mg" + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustompolicySets) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeScopeIdPolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies used in PolicySetCreatedOnCreatedByUpdatedOnUpdatedBy
    $($customPolicySet.Scope)$($customPolicySet.ScopeId)$($customPolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')$($customPolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')$($customPolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicySet.PoliciesUsed)$($customPolicySet.CreatedOn)$($customPolicySet.CreatedBy)$($customPolicySet.UpdatedOn)$($customPolicySet.UpdatedBy)
    +
    + +"@) } - - if ($policyAssignmentAll.PolicyAssignmentId -like "/subscriptions/*" -and $policyAssignmentAll.PolicyAssignmentId -notlike "/subscriptions/*/resourcegroups/*") { - $scope = "thisScope Sub" + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $tenantCustomPolicySetsCount Custom PolicySet definitions ($scopeNamingSummary)

    +"@) } - - if ($policyAssignmentAll.PolicyAssignmentId -like "/subscriptions/*/resourcegroups/*") { - $scope = "thisScope Sub RG" + } + #SUMMARY NOT tenant total custom policySet definitions + else { + $faimage = "" + if ($tenantCustompolicySetsCount -gt $LimitPOLICYPolicySetDefinitionsScopedTenant * ($LimitCriticalPercentage / 100)) { + $faimage = "" + } + else { + $faimage = "" } - #endregion inheritance - #region effect - $effect = "unknown" - if ($policyAssignmentAll.PolicyVariant -eq "Policy") { + if ($tenantCustompolicySetsCount -gt 0) { + $custompolicySetsFromSuperiorMGs = $tenantCustompolicySetsCount - (($custompolicySetsInScopeArray).count) + } + else { + $custompolicySetsFromSuperiorMGs = '0' + } - $test0 = $policyAssignmentAll.PolicyAssignmentParameters.effect.value - if ($test0) { - $effect = $test0 + if ($tenantCustompolicySetsCount -gt 0) { + $tfCount = $tenantCustompolicySetsCount + $htmlTableId = 'TenantSummary_customPolicySets' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + +"@) + $htmlSUMMARYtenanttotalcustompolicySets = $null + $htmlSUMMARYtenanttotalcustompolicySets = foreach ($customPolicySet in $customPolicySetsDetailed | Sort-Object @{Expression = { $_.Scope } }, @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { + @" + + + + + + + + + + + + + +"@ } - else { - $test1 = $policyAssignmentAll.PolicyDefinitionEffectDefault - if ($test1 -ne "n/a") { - $effect = $test1 + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustompolicySets) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeScope IdPolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies used in PolicySetCreatedOnCreatedByUpdatedOnUpdatedBy
    $($customPolicySet.Scope)$($customPolicySet.ScopeId)$($customPolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')$($customPolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')$($customPolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicySet.PoliciesUsed)$($customPolicySet.CreatedOn)$($customPolicySet.CreatedBy)$($customPolicySet.UpdatedOn)$($customPolicySet.UpdatedBy)
    +
    + +"@) } else { - $effect = "n/a" + [void]$htmlTenantSummary.AppendLine(@" +

    $tenantCustomPolicySetsCount Custom PolicySet definitions ($scopeNamingSummary)

    +"@) } - #endregion effect + } + $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 mgOrSubOrRG - if ([String]::IsNullOrEmpty($policyAssignmentAll.SubscriptionId)) { - $mgOrSubOrRG = "Mg" - } - else { - if ($scope -like "*RG") { - $mgOrSubOrRG = "RG" + #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 { - $mgOrSubOrRG = "Sub" + if ($policyPolicySetBaseQueryUniqueCustomDefinitions -notcontains ($custompolicySetAll.Id)) { + $null = $custompolicySetSetsOrphaned.Add($custompolicySetAll) + } } } - #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 + $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() + foreach ($customPolicySetOrphaned in $custompolicySetSetsOrphaned) { + if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $customPolicySetOrphaned.PolicyDefinitionId) { + $null = $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups.Add($customPolicySetOrphaned) } } - else { - $createdBy = "" - } - #UpdatedBy - if ($policyAssignmentAll.PolicyAssignmentUpdatedBy) { - $updatedBy = $policyAssignmentAll.PolicyAssignmentUpdatedBy - if ($htIdentitiesWithRoleAssignmentsUnique.($updatedBy)) { - $updatedBy = $htIdentitiesWithRoleAssignmentsUnique.($updatedBy).details + if (($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count -gt 0) { + $tfCount = ($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count + $htmlTableId = 'TenantSummary_customPolicySetsOrphaned' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + +"@) + $htmlSUMMARYCustompolicySetOrphandedTenantRoot = $null + $htmlSUMMARYCustompolicySetOrphandedTenantRoot = foreach ($custompolicySetOrphaned in $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { + @" + + + + +"@ } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustompolicySetOrphandedTenantRoot) + [void]$htmlTenantSummary.AppendLine(@" + +
    PolicySet DisplayNamePolicySetId
    $($custompolicySetOrphaned.DisplayName)$($custompolicySetOrphaned.PolicyDefinitionId)
    +
    + +"@) } else { - $updatedBy = "" - } - #endregion createdByUpdatedBy - - #region policyAssignmentNotScopes - if ($policyAssignmentAll.PolicyAssignmentNotScopes) { - $policyAssignmentNotScopes = $policyAssignmentAll.PolicyAssignmentNotScopes -join $CsvDelimiterOpposite - } - else { - $policyAssignmentNotScopes = "n/a" + [void]$htmlTenantSummary.AppendLine(@" +

    $(($arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)

    +"@) } - #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))" + } + #SUMMARY Custom policySetSets Orphanded NOT TenantRoot + else { + $arraycustompolicySetsOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() + foreach ($custompolicySetAll in $tenantCustomPolicySets) { + $isOrphaned = 'unknown' + if (($policyPolicySetBaseQueryUniqueCustomDefinitions).count -eq 0) { + $isOrphaned = 'potentially' } - } - #endregion - - if ($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 { + if ($policyPolicySetBaseQueryUniqueCustomDefinitions -notcontains $custompolicySetAll.Id) { + $isOrphaned = 'potentially' } - 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 + 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' } } - } - - #sub/rg - if (-not [String]::IsNullOrEmpty($policyAssignmentAll.subscriptionId)) { - if (($htCachePolicyComplianceResponseTooLargeSUB).($policyAssignmentAll.SubscriptionId)) { - $NonCompliantPolicies = "skipped" - $CompliantPolicies = "skipped" - $NonCompliantResources = "skipped" - $CompliantResources = "skipped" - $ConflictingResources = "skipped" + elseif ($custompolicySetAll.PolicyDefinitionId -like '/subscriptions/*') { + $policySetScopedMgSub = $custompolicySetAll.PolicyDefinitionId -replace '/subscriptions/', '' -replace '/.*' + if ($mgsAndSubs.SubscriptionId -contains ($policySetScopedMgSub)) { + $isInScope = 'inScope' + } } else { - $compliance = ($htCachePolicyComplianceSUB).($policyAssignmentAll.SubscriptionId).($policyAssignmentIdToLower) - $NonCompliantPolicies = $compliance.NonCompliantPolicies - $CompliantPolicies = $compliance.CompliantPolicies - $NonCompliantResources = $compliance.NonCompliantResources - $CompliantResources = $compliance.CompliantResources - $ConflictingResources = $compliance.ConflictingResources + write-host 'unexpected' + } - if (!$NonCompliantPolicies) { - $NonCompliantPolicies = 0 - } - if (!$CompliantPolicies) { - $CompliantPolicies = 0 - } - if (!$NonCompliantResources) { - $NonCompliantResources = 0 - } - if (!$CompliantResources) { - $CompliantResources = 0 - } - if (!$ConflictingResources) { - $ConflictingResources = 0 + if ($isInScope -eq 'inScope') { + if (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values.properties.PolicyDefinitionId -notcontains $custompolicySetAll.PolicyDefinitionId) { + $null = $arraycustompolicySetsOrphanedFinalIncludingResourceGroups.Add($custompolicySetAll) } } } - #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 - 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 - 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) { - $nonResolvedIdentitiesToCheck = '"{0}"' -f ($arrayUnresolvedIdentities -join '","') - Write-Host " IdentitiesToCheck: $nonResolvedIdentitiesToCheck" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/directoryObjects/getByIds" - $method = "POST" - $body = @" - { - "ids":[$($nonResolvedIdentitiesToCheck)] - } -"@ - - $script:htResolvedIdentitiesPolicy = @{} - - function resolveIdentitiesPolicy($currentTask) { - $resolvedIdentities = AzAPICall -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 $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 ($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 AzGovViz - 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 ($htParameters.LargeTenant -or $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(@" - -
    - 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 -DoNotIncludeResourceGroupsAnsResourcesOnRBAC
    - Check the parameters documentation AzGovViz docs -
    -"@) - } - else { - - $htmlTableId = "TenantSummary_policyAssignmentsAll" - $noteOrNot = "" + if (($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count -gt 0) { + $tfCount = ($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count + $htmlTableId = 'TenantSummary_customPolicySetsOrphaned' [void]$htmlTenantSummary.AppendLine(@" - +
    - 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 - + Download CSV semicolon | comma +
    - - - - - - - - - - - - - - - - - - -"@) - - if ($htParameters.NoPolicyComplianceStates -eq $false) { - [void]$htmlTenantSummary.AppendLine(@" - - - - - -"@) - } - - [void]$htmlTenantSummary.AppendLine(@" - - - - - - - - - - + + "@) - - $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 ($htParameters.LargeTenant -or $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 - } + $htmlSUMMARYCustompolicySetOrphandedTenantRoot = $null + $htmlSUMMARYCustompolicySetOrphandedTenantRoot = foreach ($custompolicySetOrphaned in $arraycustompolicySetsOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { @" - - - - - - - - - - - - - - - - - - -"@ - - if ($htParameters.NoPolicyComplianceStates -eq $false) { - @" - - - - - -"@ - } - - @" - - - - - - - - - - + + "@ } - - $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" - #[System.GC]::Collect() - + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustompolicySetOrphandedTenantRoot) [void]$htmlTenantSummary.AppendLine(@"
    ScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameInheritanceScopeExcludedExemption appliesPolicy/Set DisplayNamePolicy/Set DescriptionPolicy/SetIdPolicy/SetTypeCategoryEffectParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignment DescriptionAssignmentIdAssignedByCreatedOnCreatedByUpdatedOnUpdatedByPolicySet DisplayNamePolicySetId
    $($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 -replace "<", "<" -replace ">", ">")$($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)$($custompolicySetOrphaned.DisplayName)$($custompolicySetOrphaned.policyDefinitionId)
    "@) } - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $(($arrayPolicyAssignmentsEnriched).count) Policy assignments

    + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $(($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)

    "@) + } } - $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 - - [void]$htmlTenantSummary.AppendLine(@" -
    -"@) - #endregion tenantSummaryPolicy - - #region tenantSummaryRBAC - [void]$htmlTenantSummary.AppendLine(@" - -
    -"@) + #endregion SUMMARYCustompolicySetOrphandedTenantRoot - #region SUMMARYtenanttotalcustomroles - Write-Host " processing TenantSummary Custom Roles" - if ($tenantCustomRolesCount -gt $LimitRBACCustomRoleDefinitionsTenant * ($LimitCriticalPercentage / 100)) { - $faimage = "" - } - else { - $faimage = "" + $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 ($tenantCustomRolesCount -gt 0) { - $tfCount = $tenantCustomRolesCount - $htmlTableId = "TenantSummary_customRoles" + if (($policySetsDeprecated).count -gt 0) { + $tfCount = ($policySetsDeprecated).count + $htmlTableId = 'TenantSummary_policySetsDeprecated' [void]$htmlTenantSummary.AppendLine(@" - +
    Download CSV semicolon | comma - +
    - - - - - - - - + + + + + "@) - $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" - } + $htmlSUMMARYPolicySetsDeprecatedPolicy = $null + $htmlSUMMARYPolicySetsDeprecatedPolicy = foreach ($policySetDeprecated in $policySetsDeprecated | Sort-Object @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { - $createdOn = $cachedTenantCustomRole.Json.properties.createdOn - $createdOnFormated = $createdOn - $updatedOn = $cachedTenantCustomRole.Json.properties.updatedOn - if ($updatedOn -eq $createdOn) { - $updatedOnFormated = "" - $updatedByRemoveNoiseOrNot = "" + if ($policySetDeprecated.DeprecatedProperty -eq $true) { + $deprecatedProperty = 'true' } 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" - } + $deprecatedProperty = 'false' } @" - - - - - - - - + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustomroles) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicySetsDeprecatedPolicy) [void]$htmlTenantSummary.AppendLine(@"
    Role NameRoleIdAssignable ScopesDataCreatedOnCreatedByUpdatedOnUpdatedByPolicySet DisplayNamePolicySetIdPolicy DisplayNamePolicyIdDeprecated Property
    $($cachedTenantCustomRole.Name -replace "<", "<" -replace ">", ">")$($cachedTenantCustomRole.Id)$(($cachedTenantCustomRole.AssignableScopes).count) ($($cachedTenantCustomRole.AssignableScopes -join "$CsvDelimiterOpposite "))$($roleManageData)$createdOnFormated$createdBy$updatedOnFormated$updatedByRemoveNoiseOrNot$($policySetDeprecated.PolicySetDisplayName -replace '<', '<' -replace '>', '>')$($policySetDeprecated.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')$($policySetDeprecated.PolicyDisplayName -replace '<', '<' -replace '>', '>')$($policySetDeprecated.PolicyId -replace '<', '<' -replace '>', '>')$deprecatedProperty
    @@ -12546,16 +10915,11 @@ paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_ } [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - col_3: 'select', - locale: 'en-US', col_types: [ 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring', - 'select', - 'date', 'caseinsensitivestring', - 'date', 'caseinsensitivestring' ], extensions: [{ name: 'sort' }] @@ -12567,77 +10931,103 @@ extensions: [{ name: 'sort' }] } else { [void]$htmlTenantSummary.AppendLine(@" -

    $tenantCustomRolesCount Custom Role definitions ($scopeNamingSummary)

    +

    $(($policySetsDeprecated).count) PolicySets / deprecated Built-in Policy

    "@) } - #endregion SUMMARYtenanttotalcustomroles + #endregion SUMMARYPolicySetsDeprecatedPolicy + $endcustpolsetdeprpol = Get-Date + Write-Host " processing PolicySetsDeprecatedPolicy duration: $((NEW-TIMESPAN -Start $startcustpolsetdeprpol -End $endcustpolsetdeprpol).TotalSeconds) seconds" - #region SUMMARYOrphanedCustomRoles - $startSUMMARYOrphanedCustomRoles = Get-Date - Write-Host " processing TenantSummary Custom Roles orphaned" - if ($getMgParentName -eq "Tenant Root") { - $arrayCustomRolesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() + $startcustpolassdeprpol = Get-Date + #region SUMMARYPolicyAssignmentsDeprecatedPolicy + Write-Host ' processing TenantSummary PolicyAssignments using deprecated Policy' + $policyAssignmentsDeprecated = [System.Collections.ArrayList]@() + foreach ($policyAssignmentAll in ($htCacheAssignmentsPolicy).Values) { - if (($tenantCustomRoles).count -gt 0) { - $mgSubRoleAssignmentsArrayRoleDefinitionIdUnique = $mgSubRoleAssignmentsArrayFromHTValues.RoleDefinitionId | Sort-Object -Unique - if ($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - $rgResRoleAssignmentsArrayRoleDefinitionIdUnique = $rgResRoleAssignmentsArrayFromHTValues.RoleDefinitionId | Sort-Object -Unique - } - foreach ($customRoleAll in $tenantCustomRoles) { - $roleIsUsed = $false - if (($mgSubRoleAssignmentsArrayRoleDefinitionIdUnique) -contains ($customRoleAll.Id)) { - $roleIsUsed = $true - } - - if ($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 + $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 + }) + } } + } + } - if ($roleIsUsed -eq $false) { - $null = $arrayCustomRolesOrphanedFinalIncludingResourceGroups.Add($customRoleAll) + #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 (($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count -gt 0) { - $tfCount = ($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count - $htmlTableId = "TenantSummary_customRolesOrphaned" - [void]$htmlTenantSummary.AppendLine(@" -
    Download CSV semicolon | comma - +
    - - - + + + + + + + + "@) - $htmlSUMMARYOrphanedCustomRoles = $null - $htmlSUMMARYOrphanedCustomRoles = foreach ($customRoleOrphaned in $arrayCustomRolesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.Name } }) { - @" + $htmlSUMMARYPolicyAssignmentsDeprecatedPolicy = $null + $htmlSUMMARYPolicyAssignmentsDeprecatedPolicy = foreach ($policyAssignmentDeprecated in $policyAssignmentsDeprecated | Sort-Object @{Expression = { $_.PolicyAssignmentDisplayName } }, @{Expression = { $_.PolicyAssignmentId } }) { + @" - - - + + + + + + + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedCustomRoles) - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyAssignmentsDeprecatedPolicy) + [void]$htmlTenantSummary.AppendLine(@"
    Role NameRoleIdAssignable ScopesPolicy Assignment DisplayNamePolicy AssignmentIdPolicy/PolicySetPolicySet DisplayNamePolicySetIdPolicy DisplayNamePolicyIdDeprecated Property
    $($customRoleOrphaned.Name -replace "<", "<" -replace ">", ">")$($customRoleOrphaned.Id)$(($customRoleOrphaned.AssignableScopes).count) ($($customRoleOrphaned.AssignableScopes -join "$CsvDelimiterOpposite "))$($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)
    @@ -12647,33 +11037,39 @@ extensions: [{ name: 'sort' }] var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - [void]$htmlTenantSummary.AppendLine(@" -paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ -"@) + if ($tfCount -gt 10) { + $spectrum = "10, $tfCount" + if ($tfCount -gt 50) { + $spectrum = "10, 25, 50, $tfCount" + } + if ($tfCount -gt 100) { + $spectrum = "10, 30, 50, 100, $tfCount" + } + if ($tfCount -gt 500) { + $spectrum = "10, 30, 50, 100, 250, $tfCount" + } + if ($tfCount -gt 1000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" + } + if ($tfCount -gt 2000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + if ($tfCount -gt 3000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" } [void]$htmlTenantSummary.AppendLine(@" +paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ +"@) + } + [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, + col_2: 'select', col_types: [ + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring' @@ -12684,187 +11080,258 @@ extensions: [{ name: 'sort' }] tf.init();}} "@) - } - 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]@() + [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" - $mgSubRoleAssignmentsArrayRoleDefinitionIdUnique = $mgSubRoleAssignmentsArrayFromHTValues.RoleDefinitionId | Sort-Object -Unique - if ($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 ($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - if ($roleIsUsed -eq $false) { - if (($rgResRoleAssignmentsArrayRoleDefinitionIdUnique) -contains ($customRoleAll.Id)) { - $roleIsUsed = $true - } - } - } + #region SUMMARYPolicyExemptions + Write-Host ' processing TenantSummary Policy exemptions' + $policyExemptionsCount = ($htPolicyAssignmentExemptions.Keys).Count - #role used in a policyDef (rule roledefinitionIds) - if ($htRoleDefinitionIdsUsedInPolicy.Keys -contains "/providers/Microsoft.Authorization/roleDefinitions/$($customRoleAll.Id)") { - $roleIsUsed = $true - } + if ($policyExemptionsCount -gt 0) { + $tfCount = $policyExemptionsCount + $htmlTableId = 'TenantSummary_policyExemptions' - if ($roleIsUsed -eq $false) { - $null = $arrayCustomRolesOrphanedFinalIncludingResourceGroups.Add($customRoleAll) - } - } - } + $expiredExemptionsCount = ($htPolicyAssignmentExemptions.Keys | where-object { $htPolicyAssignmentExemptions.($_).exemption.properties.expiresOn -and $htPolicyAssignmentExemptions.($_).exemption.properties.expiresOn -lt (Get-Date).ToUniversalTime() } | Measure-Object).count - if (($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count -gt 0) { - $tfCount = ($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count - $htmlTableId = "TenantSummary_customRolesOrphaned" - [void]$htmlTenantSummary.AppendLine(@" -
    Download CSV semicolon | comma - +
    - - - + + + + + + + + + + + + "@) - $htmlSUMMARYOrphanedCustomRoles = $null - $htmlSUMMARYOrphanedCustomRoles = foreach ($inScopeCustomRole in $arrayCustomRolesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.Name } }) { - @" - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedCustomRoles) - [void]$htmlTenantSummary.AppendLine(@" - -
    Role NameRoleIdRole Assignable ScopesScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameResourceGroupResourceName / ResourceTypeDisplayNameCategoryExpiresOn (UTC)IdPolicy AssignmentId
    $($inScopeCustomRole.Name -replace "<", "<" -replace ">", ">")$($inScopeCustomRole.Id)$(($inScopeCustomRole.AssignableScopes).count) ($($inScopeCustomRole.AssignableScopes -join "$CsvDelimiterOpposite "))
    -
    - "@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $(($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count) Orphaned Custom Role definitions ($scopeNamingSummary)

    + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $($policyExemptionsCount) Policy exemptions

    "@) - } } - $endSUMMARYOrphanedCustomRoles = Get-Date - Write-Host " SUMMARYOrphanedCustomRoles duration: $((NEW-TIMESPAN -Start $startSUMMARYOrphanedCustomRoles -End $endSUMMARYOrphanedCustomRoles).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startSUMMARYOrphanedCustomRoles -End $endSUMMARYOrphanedCustomRoles).TotalSeconds) seconds)" + #endregion SUMMARYPolicyExemptions - #endregion SUMMARYOrphanedCustomRoles + #region SUMMARYPolicyAssignmentsOrphaned + Write-Host ' processing TenantSummary PolicyAssignments orphaned' - #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 ($policyAssignmentsOrphanedCount -gt 0) { + $tfCount = $policyAssignmentsOrphanedCount + $htmlTableId = 'TenantSummary_policyAssignmentsOrphaned' - if (($roleAssignmentsOrphanedUnique).count -gt 0) { - $tfCount = ($roleAssignmentsOrphanedUnique).count - $htmlTableId = "TenantSummary_roleAssignmentsOrphaned" [void]$htmlTenantSummary.AppendLine(@" -
    Download CSV semicolon | comma - - - - + + "@) - $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 += @" + + $htmlSUMMARYPolicyassignmentsOrphaned = $null + $htmlSUMMARYPolicyassignmentsOrphaned = foreach ($orphanedPolicyAssignment in $policyAssignmentsOrphaned | Sort-Object -Property PolicyAssignmentId) { + @" - - - - + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedRoleAssignments) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyassignmentsOrphaned) [void]$htmlTenantSummary.AppendLine(@"
    Role AssignmentIdRole NameRoleIdImpacted Mg/SubPolicy AssignmentIdPolicy/Set definition
    $($roleAssignmentOrphanedUnique.RoleAssignmentId)$($roleAssignmentOrphanedUnique.RoleDefinitionName -replace "<", "<" -replace ">", ">")$($roleAssignmentOrphanedUnique.RoleDefinitionId)Mg: $(($impactedMgs).count); Sub: $(($impactedSubs).count)$($orphanedPolicyAssignment.policyAssignmentId -replace '<', '<' -replace '>', '>')$($orphanedPolicyAssignment.PolicyDefinitionId -replace '<', '<' -replace '>', '>')
    @@ -12902,9 +11369,6 @@ paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_ [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, col_types: [ - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', 'caseinsensitivestring' ], extensions: [{ name: 'sort' }] @@ -12916,957 +11380,890 @@ extensions: [{ name: 'sort' }] } else { [void]$htmlTenantSummary.AppendLine(@" -

    $(($roleAssignmentsOrphanedUnique).count) Orphaned Role assignments ($scopeNamingSummary)

    +

    $($policyAssignmentsOrphanedCount) Policy assignments orphaned

    "@) } - #endregion SUMMARYOrphanedRoleAssignments + #endregion SUMMARYPolicyAssignmentsOrphaned - #region SUMMARYRoleAssignmentsAll - $startRoleAssignmentsAll = Get-Date - Write-Host " processing TenantSummary RoleAssignments" + #region SUMMARYPolicyAssignmentsAll + $startSummaryPolicyAssignmentsAll = Get-Date + $allPolicyAssignments = ($policyBaseQuery).count + Write-Host " processing TenantSummary PolicyAssignments (all $allPolicyAssignments)" - $startCreateRBACAllHTMLbeforeForeach = Get-Date + $script:arrayPolicyAssignmentsEnriched = [System.Collections.ArrayList]@() + $cnter = 0 - if ($htParameters.LargeTenant -or $htParameters.RBACAtScopeOnly) { - $rbacAllAtScope = ($rbacAll.where( { ((-not [string]::IsNullOrEmpty($_.SubscriptionId) -and $_.scope -notlike "inherited *")) -or ([string]::IsNullOrEmpty($_.SubscriptionId)) })) - $rbacAllCount = $rbacAllAtScope.Count - } - else { - $rbacAllCount = $rbacAll.Count - } + #region PolicyAssignmentsRoleAssignmentMapping + $startPolicyAssignmentsRoleAssignmentMapping = Get-Date + Write-Host ' processing PolicyAssignmentsRoleAssignmentMapping' + $script:htPolicyAssignmentRoleAssignmentMapping = @{} + foreach ($roleassignmentId in ($htCacheAssignmentsRole).keys | Sort-Object) { + $roleAssignment = ($htCacheAssignmentsRole).($roleassignmentId).Assignment - if ($rbacAllCount -gt 0) { - $uniqueRoleAssignmentsCount = ($rbacAll.RoleAssignmentId | Sort-Object -Unique).count - $tfCount = $rbacAllCount + if ($htManagedIdentityForPolicyAssignment.($roleAssignment.ObjectId)) { + $mi = $htManagedIdentityForPolicyAssignment.($roleAssignment.ObjectId) - if (-not $NoCsvExport) { - $startCreateRBACAllCSV = Get-Date + #this + if (-not $htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower())) { + $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()) = @{} + } - $csvFilename = "$($filename)_RoleAssignments" - Write-Host " Exporting RoleAssignments CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'" - if ($CsvExportUseQuotesAsNeeded) { - if ($htParameters.LargeTenant -or $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 - } + if (($htCacheDefinitionsRole).($roleAssignment.RoleDefinitionId).IsCustom) { + $roleDefinitionType = 'custom' } else { - if ($htParameters.LargeTenant -or $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 - } + $roleDefinitionType = 'builtin' } - $endCreateRBACAllCSV = Get-Date - Write-Host " CreateRBACAll CSV duration: $((NEW-TIMESPAN -Start $startCreateRBACAllCSV -End $endCreateRBACAllCSV).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startCreateRBACAllCSV -End $endCreateRBACAllCSV).TotalSeconds) seconds)" - } + $array = [System.Collections.ArrayList]@() + $null = $array.Add([PSCustomObject]@{ + roleassignmentId = $roleassignmentId + roleDefinitionId = $roleAssignment.RoleDefinitionId + roleDefinitionName = $roleAssignment.RoleDefinitionName + roleDefinitionType = $roleDefinitionType + }) - 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(@" - -
    - 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 -DoNotIncludeResourceGroupsAnsResourcesOnRBAC
    - Check the parameters documentation AzGovViz docs -
    -"@) + #this + if ($htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments) { + $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments += $array + } + else { + $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()).roleassignments = $array + } } - else { - $htmlTableId = "TenantSummary_roleAssignmentsAll" - $noteOrNot = "" - [void]$htmlTenantSummary.AppendLine(@" - -
    - 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(@" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"@) - $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 ($htParameters.LargeTenant -or $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 - } + if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + foreach ($roleassignmentId in ($htCacheAssignmentsRBACOnResourceGroupsAndResources).keys | Sort-Object) { + $roleAssignment = ($htCacheAssignmentsRBACOnResourceGroupsAndResources).($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)" + if ($htManagedIdentityForPolicyAssignment.($roleAssignment.ObjectId)) { + $mi = $htManagedIdentityForPolicyAssignment.($roleAssignment.ObjectId) - $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() - #[System.GC]::Collect() - } + #this + if (-not $htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower())) { + $script:htPolicyAssignmentRoleAssignmentMapping.(($mi.policyAssignmentId).ToLower()) = @{} } - if ($roleAssignment.RoleType -eq "Custom") { - $roleName = ($roleAssignment.Role -replace "<", "<" -replace ">", ">") + if (($htCacheDefinitionsRole).($roleAssignment.RoleDefinitionId).IsCustom) { + $roleDefinitionType = 'custom' } else { - $roleName = $roleAssignment.Role + $roleDefinitionType = 'builtin' } - [void]$htmlSummaryRoleAssignmentsAll.AppendFormat( - @' - - - - - - - - - - - - - - - - - - - - - - - - - - - - -'@, $roleAssignment.TenOrMgOrSubOrRGOrRes, - $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 - ) + $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 + } } - $start = Get-Date - [void]$htmlTenantSummary.AppendLine($htmlSummaryRoleAssignmentsAll) + } + } + $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 - $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $htmlSummaryRoleAssignmentsAll = $null #cleanup - $htmlTenantSummary = [System.Text.StringBuilder]::new() - $end = Get-Date + #region PolicyAssignmentsUniqueRelations + $startPolicyAssignmnetsUniqueRelations = Get-Date + Write-Host ' processing PolicyAssignmnetsUniqueRelations' + $htPolicyAssignmentRelatedRoleAssignments = @{} + $htPolicyAssignmentRelatedExemptions = @{} - $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)" + foreach ($policyAssignmentIdUnique in $policyBaseQueryUniqueAssignments) { - [void]$htmlTenantSummary.AppendLine(@" - -
    ScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameAssignment ScopeRoleRole IdRole TypeDataCan do Role assignmentIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsPIMPIM assignment typePIM startPIM endRole AssignmentIdRelated Policy Assignment $noteOrNotCreatedOnCreatedBy
    {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}
    -
    - -"@) - } + #endregion exemptions } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $($rbacAllCount) Role assignments

    -"@) - } + $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 - $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 PolicyAssignmentsAllCreateEnriched + $startPolicyAssignmentsAllCreateEnriched = Get-Date + Write-Host ' processing PolicyAssignmentsAllCreateEnriched' + foreach ($policyAssignmentAll in $policyBaseQuery) { - #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(@" - -
    - Download CSV semicolon | comma - - - - - - - - - - -"@) - $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 "))" + $cnter++ + if ($cnter % 1000 -eq 0) { + $etappeSummaryPolicyAssignmentsAll = Get-Date + Write-Host " $cnter of $allPolicyAssignments PolicyAssignments processed: $((NEW-TIMESPAN -Start $startSummaryPolicyAssignmentsAll -End $etappeSummaryPolicyAssignmentsAll).TotalSeconds) seconds" + #if ($cnter % 5000 -eq 0) { + #[System.GC]::Collect() + #} + } + + #region AzAdvertizerLinkOrNot + if ($policyAssignmentAll.PolicyType -eq 'builtin') { + if ($policyAssignmentAll.PolicyVariant -eq 'Policy') { + $azaLinkOrNot = "$($policyAssignmentAll.Policy)" } else { - $customRoleRoleAssignmentsOutput = "$customRoleOwnersAllAssignmentsCount" + $azaLinkOrNot = "$($policyAssignmentAll.Policy)" } - $htmlSUMMARYSecurityCustomRoles += @" - - - - - - -"@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityCustomRoles) - [void]$htmlTenantSummary.AppendLine(@" - -
    Role NameRoleIdRole assignmentsAssignable Scopes
    $($customRole.Name -replace "<", "<" -replace ">", ">")$($customRole.Id)$($customRoleRoleAssignmentsOutput)$(($customRole.AssignableScopes).count) ($($customRole.AssignableScopes -join "$CsvDelimiterOpposite "))
    -
    - -"@) - } - 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) { + if ($policyAssignmentAll.PolicyAssignmentId -like '/subscriptions/*' -and $policyAssignmentAll.PolicyAssignmentId -notlike '/subscriptions/*/resourcegroups/*') { + $scope = 'thisScope Sub' + } - #$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) + if ($policyAssignmentAll.PolicyAssignmentId -like '/subscriptions/*/resourcegroups/*') { + $scope = 'thisScope Sub RG' } + #endregion inheritance - $tfCount = $tenantAllRolesCanDoRoleAssignmentsCount - $htmlTableId = "TenantSummary_RolesCanDoRoleAssignments" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - - - - -"@) - $htmlSUMMARYSecurityRolesCanDoRoleAssignments = $null - foreach ($role in ($tenantAllRolesCanDoRoleAssignments | Sort-Object -Property Name)) { - if ($role.IsCustom) { - $roleType = "Custom" - $roleAssignableScopes = "$(($role.AssignableScopes).count) ($($role.AssignableScopes -join "$CsvDelimiterOpposite "))" + #region effect + $effect = 'unknown' + if ($policyAssignmentAll.PolicyVariant -eq 'Policy') { + + $test0 = $policyAssignmentAll.PolicyAssignmentParameters.effect.value + if ($test0) { + $effect = $test0 } else { - $roleType = "BuiltIn" - $roleAssignableScopes = "" + $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 - if ($htRoleAssignments4RolesCanDoRoleAssignments.($role.Id).roleAssignments.Count -gt 0) { - $roleAssignments = "$($htRoleAssignments4RolesCanDoRoleAssignments.($role.Id).roleAssignments.Count) ($($htRoleAssignments4RolesCanDoRoleAssignments.($role.Id).roleAssignments -join ", "))" + #region mgOrSubOrRG + if ([String]::IsNullOrEmpty($policyAssignmentAll.SubscriptionId)) { + $mgOrSubOrRG = 'Mg' + } + else { + if ($scope -like '*RG') { + $mgOrSubOrRG = 'RG' } else { - $roleAssignments = 0 + $mgOrSubOrRG = 'Sub' } + } + #endregion mgOrSubOrRG - $htmlSUMMARYSecurityRolesCanDoRoleAssignments += @" - - - - - - - -"@ + #region category + if ([string]::IsNullOrEmpty($policyAssignmentAll.PolicyCategory)) { + $policyCategory = 'n/a' } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityRolesCanDoRoleAssignments) - [void]$htmlTenantSummary.AppendLine(@" - -
    Role NameRoleIdTypeRole assignmentsAssignable Scopes
    $($role.Name -replace "<", "<" -replace ">", ">")$($role.Id)$($roleType)$($roleAssignments)$($roleAssignableScopes)
    -
    - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $($tenantAllRolesCanDoRoleAssignmentsCount) Role definitions can apply Role assignments

    -"@) - } - #endregion SUMMARYSecurityRolesCanDoRoleAssignments + else { + $updatedBy = '' + } + #endregion createdByUpdatedBy - #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(@" - -
    - Download CSV semicolon | comma - - - - - - - - - - - -"@) - $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 - @" - - - - - - - -"@ + #region policyAssignmentNotScopes + if ($policyAssignmentAll.PolicyAssignmentNotScopes) { + $policyAssignmentNotScopes = $policyAssignmentAll.PolicyAssignmentNotScopes -join $CsvDelimiterOpposite } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityOwnerAssignmentSP) - [void]$htmlTenantSummary.AppendLine(@" - -
    Role NameRoleIdRole AssignmentServicePrincipal (ObjId)Impacted Mg/Sub
    $($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)
    -
    - -"@) - } - 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 + #endregion - #region SUMMARYSecurityOwnerAssignmentNotGroup - Write-Host " processing TenantSummary RoleAssignments security (owner notGroup)" - $startSUMMARYSecurityOwnerAssignmentNotGroup = Get-Date + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + #region policyCompliance + $policyAssignmentIdToLower = ($policyAssignmentAll.policyAssignmentId).ToLower() - $roleAssignmentsOwnerAssignmentNotGroup = $rbacBaseQueryArrayListNotGroupOwner | Sort-Object -Property RoleAssignmentId -Unique - $roleAssignmentsOwnerAssignmentNotGroupGrouped = ($rbacBaseQueryArrayListNotGroupOwner | Group-Object -property roleassignmentId) + #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 (($roleAssignmentsOwnerAssignmentNotGroup).count -gt 0) { - $tfCount = ($roleAssignmentsOwnerAssignmentNotGroup).count - $htmlTableId = "TenantSummary_roleAssignmentsOwnerAssignmentNotGroup" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - - - - - - - -"@) - $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) }) - @" - - - - - - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityOwnerAssignmentNotGroup) - [void]$htmlTenantSummary.AppendLine(@" - -
    Role NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdImpacted Mg/Sub
    $($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)
    -
    - -"@) - } - 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 + $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 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) + #region PolicyAssignmentsAllResolveIdentities + Write-Host ' processing unresoved Identities (createdBy/updatedBy)' + $startUnResolvedIdentitiesCreatedByUpdatedByPolicy = Get-Date - if (($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count -gt 0) { - $tfCount = ($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count - $htmlTableId = "TenantSummary_roleAssignmentsUserAccessAdministratorAssignmentNotGroup" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - - - - - - - -"@) - $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) }) - @" - - - - - - - - - - -"@ + $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) = @{} } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup) - [void]$htmlTenantSummary.AppendLine(@" - -
    Role NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdImpacted Mg/Sub
    $($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)
    -
    - -"@) } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $(($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count) UserAccessAdministrator permission assignments to notGroup ($scopeNamingSummary)

    + + $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(@" + +
    + 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 -DoNotIncludeResourceGroupsAnsResourcesOnRBAC
    + Check the parameters documentation AzGovViz docs +
    "@) - } - $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 + } + else { - #region SUMMARYSecurityGuestUserHighPriviledgesAssignments + $htmlTableId = 'TenantSummary_policyAssignmentsAll' + $noteOrNot = '' - $startSUMMARYSecurityGuestUserHighPriviledgesAssignments = Get-Date - Write-Host " processing TenantSummary RoleAssignments security (high priviledged Guest User)" - $highPriviledgedGuestUserRoleAssignments = $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 - $highPriviledgedGuestUserRoleAssignmentsCount = ($highPriviledgedGuestUserRoleAssignments).Count - if ($highPriviledgedGuestUserRoleAssignmentsCount -gt 0) { - $tfCount = $highPriviledgedGuestUserRoleAssignmentsCount - $htmlTableId = "TenantSummary_SecurityGuestUserHighPriviledgesAssignments" - [void]$htmlTenantSummary.AppendLine(@" - + [void]$htmlTenantSummary.AppendLine(@" +
    - Download CSV semicolon | comma + 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 - - - - - - - - + + + + + + + + + + + + + + + + + + +"@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + + + + + +'@) + } + + [void]$htmlTenantSummary.AppendLine(@" + + + + + + + + + + "@) - $htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments = $null - $htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments = foreach ($highPriviledgedGuestUserRoleAssignment in ($highPriviledgedGuestUserRoleAssignments)) { - if ($highPriviledgedGuestUserRoleAssignment.AssignmentType -eq "indirect") { - $assignmentInfo = "indirect / AAD Group Membership '$($highPriviledgedGuestUserRoleAssignment.AssignmentInheritFrom)'" - } - else { - $assignmentInfo = "direct" - } - @" + + $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 + } + @" - - - - - - - - - + + + + + + + + + + + + + + + + + + +"@ + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + @" + + + + + +"@ + } + + @" + + + + + + + + + + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments) - [void]$htmlTenantSummary.AppendLine(@" - -
    Role NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdAssignment direct/indirectScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameInheritanceScopeExcludedExemption appliesPolicy/Set DisplayNamePolicy/Set DescriptionPolicy/SetIdPolicy/SetTypeCategoryEffectParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignment DescriptionAssignmentIdAssignedByCreatedOnCreatedByUpdatedOnUpdatedBy
    $($highPriviledgedGuestUserRoleAssignment.Role -replace "<", "<" -replace ">", ">")$($highPriviledgedGuestUserRoleAssignment.RoleId)$($highPriviledgedGuestUserRoleAssignment.RoleAssignmentId)$($highPriviledgedGuestUserRoleAssignment.ObjectType)$($highPriviledgedGuestUserRoleAssignment.ObjectDisplayName)$($highPriviledgedGuestUserRoleAssignment.ObjectSignInName)$($highPriviledgedGuestUserRoleAssignment.ObjectId)$assignmentInfo
    $($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 -replace '<', '<' -replace '>', '>')$($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)
    -
    - "@) + } } else { [void]$htmlTenantSummary.AppendLine(@" -

    $($highPriviledgedGuestUserRoleAssignmentsCount) Guest Users with high permissions ($scopeNamingSummary)

    +

    $(($arrayPolicyAssignmentsEnriched).count) Policy assignments

    "@) } - $endSUMMARYSecurityGuestUserHighPriviledgesAssignments = Get-Date - Write-Host " TenantSummary RoleAssignments security (high priviledged Guest User) duration: $((NEW-TIMESPAN -Start $startSUMMARYSecurityGuestUserHighPriviledgesAssignments -End $endSUMMARYSecurityGuestUserHighPriviledgesAssignments).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startSUMMARYSecurityGuestUserHighPriviledgesAssignments -End $endSUMMARYSecurityGuestUserHighPriviledgesAssignments).TotalSeconds) seconds)" - #endregion SUMMARYSecurityGuestUserHighPriviledgesAssignments - + $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 - [void]$htmlTenantSummary.AppendLine(@" + [void]$htmlTenantSummary.AppendLine(@'
    -"@) - #endregion tenantSummaryRBAC +'@) + #endregion tenantSummaryPolicy - #region tenantSummaryBlueprints - [void]$htmlTenantSummary.AppendLine(@" - + showMemoryUsage + + #region tenantSummaryRBAC + [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" + #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(@" - +
    Download CSV semicolon | comma - - - - + + + + + + + + "@) - $htmlSUMMARYBlueprintDefinitions = $null - $htmlSUMMARYBlueprintDefinitions = foreach ($blueprintDefinition in $blueprintDefinitions | Sort-Object -Property BlueprintName, BlueprintDisplayName) { + $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' + } + } @" - - - - + + + + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintDefinitions) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustomroles) [void]$htmlTenantSummary.AppendLine(@" - -
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdRole NameRoleIdAssignable ScopesDataCreatedOnCreatedByUpdatedOnUpdatedBy
    $($blueprintDefinition.BlueprintName -replace "<", "<" -replace ">", ">")$($blueprintDefinition.BlueprintDisplayName -replace "<", "<" -replace ">", ">")$($blueprintDefinition.BlueprintDescription -replace "<", "<" -replace ">", ">")$($blueprintDefinition.BlueprintId -replace "<", "<" -replace ">", ">")$($cachedTenantCustomRole.Name -replace '<', '<' -replace '>', '>')$($cachedTenantCustomRole.Id)$(($cachedTenantCustomRole.AssignableScopes).count) ($($cachedTenantCustomRole.AssignableScopes -join "$CsvDelimiterOpposite "))$($roleManageData)$createdOnFormated$createdBy$updatedOnFormated$updatedByRemoveNoiseOrNot
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    $blueprintDefinitionsCount Blueprint definitions

    +

    $tenantCustomRolesCount Custom Role definitions ($scopeNamingSummary)

    "@) } - #endregion SUMMARYBlueprintDefinitions + #endregion SUMMARYtenanttotalcustomroles - #region SUMMARYBlueprintAssignments - Write-Host " processing TenantSummary BlueprintAssignments" - $blueprintAssignments = ($blueprintBaseQuery | Where-Object { -not [String]::IsNullOrEmpty($_.BlueprintAssignmentId) }) - $blueprintAssignmentsCount = ($blueprintAssignments).count + #region SUMMARYOrphanedCustomRoles + $startSUMMARYOrphanedCustomRoles = Get-Date + Write-Host ' processing TenantSummary Custom Roles orphaned' + if ($getMgParentName -eq 'Tenant Root') { + $arrayCustomRolesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() - if ($blueprintAssignmentsCount -gt 0) { - $htmlTableId = "TenantSummary_BlueprintAssignments" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - - - - - -"@) - $htmlSUMMARYBlueprintAssignments = $null - $htmlSUMMARYBlueprintAssignments = foreach ($blueprintAssignment in $blueprintAssignments | Sort-Object -Property level, BlueprintAssignmentId) { - @" - - - - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintAssignments) - [void]$htmlTenantSummary.AppendLine(@" - -
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdBlueprint VersionBlueprint AssignmentId
    $($blueprintAssignment.BlueprintName -replace "<", "<" -replace ">", ">")$($blueprintAssignment.BlueprintDisplayName -replace "<", "<" -replace ">", ">")$($blueprintAssignment.BlueprintDescription -replace "<", "<" -replace ">", ">")$($blueprintAssignment.BlueprintId -replace "<", "<" -replace ">", ">")$($blueprintAssignment.BlueprintAssignmentVersion)$($blueprintAssignment.BlueprintAssignmentId -replace "<", "<" -replace ">", ">")
    -
    - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $blueprintAssignmentsCount Blueprint assignments

    -"@) - } - #endregion SUMMARYBlueprintAssignments + foreach ($customRoleAll in $tenantCustomRoles) { + $roleIsUsed = $false + if (($mgSubRoleAssignmentsArrayRoleDefinitionIdUnique) -contains ($customRoleAll.Id)) { + $roleIsUsed = $true + } - #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 + 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) } } } - else { - $blueprintDefinitionsOrphanedArray += foreach ($blueprintDefinition in $blueprintDefinitions) { - $blueprintDefinition - } - } - } - $blueprintDefinitionsOrphanedCount = ($blueprintDefinitionsOrphanedArray).count - - if ($blueprintDefinitionsOrphanedCount -gt 0) { - $htmlTableId = "TenantSummary_BlueprintsOrphaned" - [void]$htmlTenantSummary.AppendLine(@" - + if (($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count -gt 0) { + $tfCount = ($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count + $htmlTableId = 'TenantSummary_customRolesOrphaned' + [void]$htmlTenantSummary.AppendLine(@" +
    Download CSV semicolon | comma - - - - + + + "@) - $htmlSUMMARYBlueprintsOrphaned = $null - $htmlSUMMARYBlueprintsOrphaned = foreach ($blueprintDefinition in $blueprintDefinitionsOrphanedArray | Sort-Object -Property BlueprintId) { - @" + $htmlSUMMARYOrphanedCustomRoles = $null + $htmlSUMMARYOrphanedCustomRoles = foreach ($customRoleOrphaned in $arrayCustomRolesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.Name } }) { + @" - - - - + + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintsOrphaned) - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedCustomRoles) + [void]$htmlTenantSummary.AppendLine(@"
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdRole NameRoleIdAssignable Scopes
    $($blueprintDefinition.BlueprintName -replace "<", "<" -replace ">", ">")$($blueprintDefinition.BlueprintDisplayName -replace "<", "<" -replace ">", ">")$($blueprintDefinition.BlueprintDescription -replace "<", "<" -replace ">", ">")$($blueprintDefinition.BlueprintId -replace "<", "<" -replace ">", ">")$($customRoleOrphaned.Name -replace '<', '<' -replace '>', '>')$($customRoleOrphaned.Id)$(($customRoleOrphaned.AssignableScopes).count) ($($customRoleOrphaned.AssignableScopes -join "$CsvDelimiterOpposite "))
    @@ -14149,34 +12597,33 @@ extensions: [{ name: 'sort' }] var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - [void]$htmlTenantSummary.AppendLine(@" + if ($tfCount -gt 10) { + $spectrum = "10, $tfCount" + if ($tfCount -gt 50) { + $spectrum = "10, 25, 50, $tfCount" + } + if ($tfCount -gt 100) { + $spectrum = "10, 30, 50, 100, $tfCount" + } + if ($tfCount -gt 500) { + $spectrum = "10, 30, 50, 100, 250, $tfCount" + } + if ($tfCount -gt 1000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" + } + if ($tfCount -gt 2000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + if ($tfCount -gt 3000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" + } + [void]$htmlTenantSummary.AppendLine(@" paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) - } - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, col_types: [ - 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring', 'caseinsensitivestring' @@ -14187,174 +12634,187 @@ extensions: [{ name: 'sort' }] tf.init();}} "@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $(($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count) Orphaned Custom Role definitions ($scopeNamingSummary)

    +"@) + } + #not renant root } else { - [void]$htmlTenantSummary.AppendLine(@" -

    $blueprintDefinitionsOrphanedCount Orphaned Blueprint definitions

    -"@) - } - #endregion SUMMARYBlueprintsOrphaned + $mgs = (($optimizedTableForPathQueryMg.where( { $_.mgId -ne '' -and $_.Level -ne '0' })) | select-object MgId -unique) + $arrayCustomRolesOrphanedFinalIncludingResourceGroups = [System.Collections.ArrayList]@() - [void]$htmlTenantSummary.AppendLine(@" -
    -"@) - #endregion tenantSummaryBlueprints + $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 + } + } + } - #region tenantSummaryManagementGroups - [void]$htmlTenantSummary.AppendLine(@" - -
    -"@) + #role used in a policyDef (rule roledefinitionIds) + if ($htRoleDefinitionIdsUsedInPolicy.Keys -contains "/providers/Microsoft.Authorization/roleDefinitions/$($customRoleAll.Id)") { + $roleIsUsed = $true + } - #region SUMMARYMGs - $startSUMMARYMGs = Get-Date - Write-Host " processing TenantSummary ManagementGroups" + if ($roleIsUsed -eq $false) { + $null = $arrayCustomRolesOrphanedFinalIncludingResourceGroups.Add($customRoleAll) + } + } + } - $summaryManagementGroups = $optimizedTableForPathQueryMg | Sort-Object -Property Level, mgid, mgParentId - $summaryManagementGroupsCount = ($summaryManagementGroups).Count - if ($summaryManagementGroupsCount -gt 0) { - $tfCount = $summaryManagementGroupsCount - $htmlTableId = "TenantSummary_ManagementGroups" - [void]$htmlTenantSummary.AppendLine(@" - + if (($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count -gt 0) { + $tfCount = ($arrayCustomRolesOrphanedFinalIncludingResourceGroups).count + $htmlTableId = 'TenantSummary_customRolesOrphaned' + [void]$htmlTenantSummary.AppendLine(@" +
    Download CSV semicolon | comma - - - - - - - -"@) - if ($htParameters.NoMDfCSecureScore -eq $false) { - [void]$htmlTenantSummary.AppendLine(@" - -"@) - } - if ($htParameters.DoAzureConsumption -eq $true) { - [void]$htmlTenantSummary.AppendLine(@" - -"@) - } - [void]$htmlTenantSummary.AppendLine(@" - + + + "@) - $htmlSUMMARYManagementGroups = $null - $cnter = 0 - $htmlSUMMARYManagementGroups = foreach ($summaryManagementGroup in $summaryManagementGroups) { - - $mgPath = $htManagementGroupsMgPath.($summaryManagementGroup.mgId).pathDelimited - - if ($summaryManagementGroup.mgid -eq $mgSubPathTopMg -and ($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" + $htmlSUMMARYOrphanedCustomRoles = $null + $htmlSUMMARYOrphanedCustomRoles = foreach ($inScopeCustomRole in $arrayCustomRolesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.Name } }) { + @" + + + + + +"@ } - else { - - if ($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" - } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedCustomRoles) + [void]$htmlTenantSummary.AppendLine(@" + +
    LevelManagementGroupManagementGroup IdMg children (total)Mg children (direct)Sub children (total)Sub children (direct)MG MDfC ScoreCost ($($AzureConsumptionPeriod)d)PathRole NameRoleIdRole Assignable Scopes
    $($inScopeCustomRole.Name -replace '<', '<' -replace '>', '>')$($inScopeCustomRole.Id)$(($inScopeCustomRole.AssignableScopes).count) ($($inScopeCustomRole.AssignableScopes -join "$CsvDelimiterOpposite "))
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + - - - - - - - -"@ - if ($htParameters.NoMDfCSecureScore -eq $false) { - @" - -"@ - } - if ($htParameters.DoAzureConsumption -eq $true) { - @" - -"@ - } - @" - + + + + + + + +"@) + $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 += @" + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYManagementGroups) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedRoleAssignments) [void]$htmlTenantSummary.AppendLine(@"
    $($summaryManagementGroup.level)$($summaryManagementGroup.mgName -replace "<", "<" -replace ">", ">")$($summaryManagementGroup.mgId)$($mgAllChildMgsCountTotal)$($mgAllChildMgsCountDirect)$($mgAllChildSubscriptionsCountTotal)$($mgAllChildSubscriptionsCountDirect)$($mgSecureScore)$($arrayTotalCostSummaryMgSummary -join ", ")$($pathhlper)Role AssignmentIdRole NameRoleIdImpacted Mg/Sub
    $($roleAssignmentOrphanedUnique.RoleAssignmentId)$($roleAssignmentOrphanedUnique.RoleDefinitionName -replace '<', '<' -replace '>', '>')$($roleAssignmentOrphanedUnique.RoleDefinitionId)Mg: $(($impactedMgs).count); Sub: $(($impactedSubs).count)
    @@ -14391,27 +12851,10 @@ paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_ } [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - col_0: 'select', col_types: [ - 'number', 'caseinsensitivestring', 'caseinsensitivestring', - 'number', - 'number', - 'number', - 'number', -"@) - if ($htParameters.NoMDfCSecureScore -eq $false) { - [void]$htmlTenantSummary.AppendLine(@" - 'caseinsensitivestring', -"@) - } - if ($htParameters.DoAzureConsumption -eq $true) { - [void]$htmlTenantSummary.AppendLine(@" 'caseinsensitivestring', -"@) - } - [void]$htmlTenantSummary.AppendLine(@" 'caseinsensitivestring' ], extensions: [{ name: 'sort' }] @@ -14419,327 +12862,371 @@ extensions: [{ name: 'sort' }] var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); tf.init();}} - "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    $($summaryManagementGroupsCount) Management Groups

    +

    $(($roleAssignmentsOrphanedUnique).count) Orphaned Role assignments ($scopeNamingSummary)

    "@) } - $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 + #endregion SUMMARYOrphanedRoleAssignments - #region SUMMARYMGdefault - Write-Host " processing TenantSummary ManagementGroups - default Management Group" - [void]$htmlTenantSummary.AppendLine(@" -

    Hierarchy Settings | Default Management Group Id: '$($defaultManagementGroupId)' docs

    -"@) - #endregion SUMMARYMGdefault + #region SUMMARYRoleAssignmentsAll + $startRoleAssignmentsAll = Get-Date + Write-Host ' processing TenantSummary RoleAssignments' - #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 + $startCreateRBACAllHTMLbeforeForeach = Get-Date - [void]$htmlTenantSummary.AppendLine(@" -
    -"@) - #endregion tenantSummaryManagementGroups + 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 + } + else { + $rbacAllCount = $rbacAll.Count + } - #region tenantSummarySubscriptions - [void]$htmlTenantSummary.AppendLine(@" - -
    -"@) + if ($rbacAllCount -gt 0) { + $uniqueRoleAssignmentsCount = ($rbacAll.RoleAssignmentId | Sort-Object -Unique).count + $tfCount = $rbacAllCount - #region SUMMARYSubs - Write-Host " processing TenantSummary Subscriptions" - $summarySubscriptions = $optimizedTableForPathQueryMgAndSub | Sort-Object -Property Subscription - $summarySubscriptionsCount = ($summarySubscriptions).Count - if ($summarySubscriptionsCount -gt 0) { - $tfCount = $summarySubscriptionsCount - $htmlTableId = "TenantSummary_subs" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Supported Microsoft Azure offers docs
    - Understand Microsoft Defender for Cloud Secure Score Video , Blog , docs
    - Download CSV semicolon | comma - - - - - - - - - -"@) - if ($htParameters.DoAzureConsumption -eq $true) { - [void]$htmlTenantSummary.AppendLine(@" - - -"@) - } - [void]$htmlTenantSummary.AppendLine(@" - - - - -"@) - $htmlSUMMARYSubs = $null - $htmlSUMMARYSubs = foreach ($summarySubscription in $summarySubscriptions) { - $subPath = $htSubscriptionsMgPath.($summarySubscription.subscriptionId).pathDelimited - $subscriptionTagsArray = [System.Collections.ArrayList]@() - foreach ($tag in ($htSubscriptionTags).($summarySubscription.subscriptionId).keys) { - $null = $subscriptionTagsArray.Add("'$($tag)':'$(($htSubscriptionTags).$($summarySubscription.subscriptionId).$tag)'") - } + if (-not $NoCsvExport) { + $startCreateRBACAllCSV = Get-Date - if ($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 + $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 { - $totalCost = "0" - $currency = "n/a" + $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 { - $totalCost = "n/a" - $currency = "n/a" - } - @" - - - - - - - -"@ - if ($htParameters.DoAzureConsumption -eq $true) { - @" - - -"@ - } - @" - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubs) - [void]$htmlTenantSummary.AppendLine(@" - -
    SubscriptionSubscriptionIdQuotaIdRole assignment limitTagsSub MDfC ScoreCost ($($AzureConsumptionPeriod)d)CurrencyPath
    $($summarySubscription.subscription -replace "<", "<" -replace ">", ">")$($summarySubscription.subscriptionId)$($summarySubscription.SubscriptionQuotaId)$($htSubscriptionsRoleAssignmentLimit.($summarySubscription.subscriptionId))$(($subscriptionTagsArray | Sort-Object) -join "$CsvDelimiterOpposite ")$($summarySubscription.SubscriptionASCSecureScore)$totalCost$currency $subPath
    -
    - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $($summarySubscriptionsCount) Subscriptions

    -"@) - } - #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(@" - -
    - Download CSV semicolon | comma - + [void]$htmlTenantSummary.AppendLine(@" +
    - + + + - - + + + + + + + + + + + + + + + + + + + + + + "@) - $htmlSUMMARYOutOfScopeSubscriptions = $null - $htmlSUMMARYOutOfScopeSubscriptions = foreach ($outOfScopeSubscription in $outOfScopeSubscriptions) { - @" + $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() + #[System.GC]::Collect() + } + } + + if ($roleAssignment.RoleType -eq 'Custom') { + $roleName = ($roleAssignment.Role -replace '<', '<' -replace '>', '>') + } + else { + $roleName = $roleAssignment.Role + } + + [void]$htmlSummaryRoleAssignmentsAll.AppendFormat( + @' - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOutOfScopeSubscriptions) - [void]$htmlTenantSummary.AppendLine(@" +'@, $roleAssignment.TenOrMgOrSubOrRGOrRes, + $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(@"
    Subscription NameScopeManagement Group IdManagement Group Name SubscriptionIdout-of-scope reasonManagement GroupSubscription NameAssignment ScopeRoleRole IdRole TypeDataCan do Role assignmentIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsPIMPIM assignment typePIM startPIM endRole AssignmentIdRelated Policy Assignment $noteOrNotCreatedOnCreatedBy
    $($outOfScopeSubscription.SubscriptionName)$($outOfScopeSubscription.SubscriptionId)$($outOfScopeSubscription.outOfScopeReason) $($outOfScopeSubscription.ManagementGroupName -replace "<", "<" -replace ">", ">") ($($outOfScopeSubscription.ManagementGroupId)){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}
    "@) + + } } else { [void]$htmlTenantSummary.AppendLine(@" -

    $outOfScopeSubscriptionsCount Subscriptions out-of-scope

    +

    $($rbacAllCount) Role assignments

    "@) } - #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" + $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 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(@" - +
    - Resource naming and tagging decision guide docs
    Download CSV semicolon | comma - - - + + + + "@) - $htmlSUMMARYtagsUsage = $null - $htmlSUMMARYtagsUsage = foreach ($tagEntry in $arrayTagList | Sort-Object -Property Scope, TagName -CaseSensitive) { - @" + $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 += @" - - - + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtagsUsage) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityCustomRoles) [void]$htmlTenantSummary.AppendLine(@"
    ScopeTagNameCountRole NameRoleIdRole assignmentsAssignable Scopes
    $($tagEntry.Scope)$($tagEntry.TagName)$($tagEntry.TagCount)$($customRole.Name -replace '<', '<' -replace '>', '>')$($customRole.Id)$($customRoleRoleAssignmentsOutput)$(($customRole.AssignableScopes).count) ($($customRole.AssignableScopes -join "$CsvDelimiterOpposite "))
    @@ -14749,7 +13236,6 @@ extensions: [{ name: 'sort' }] window.helpertfConfig4$htmlTableId =1; var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, - "@) if ($tfCount -gt 10) { $spectrum = "10, $tfCount" @@ -14777,11 +13263,11 @@ paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_ } [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - col_0: 'multiple', col_types: [ 'caseinsensitivestring', 'caseinsensitivestring', - 'number' + 'caseinsensitivestring', + 'caseinsensitivestring' ], extensions: [{ name: 'sort' }] }; @@ -14792,319 +13278,282 @@ extensions: [{ name: 'sort' }] } else { [void]$htmlTenantSummary.AppendLine(@" -

    Tag Name Usage ($tagsUsageCount Tags) docs

    +

    $(($customRolesOwnerHtAll).count) Custom Role definitions Owner permissions ($scopeNamingSummary)

    "@) } - #endregion SUMMARYTagNameUsage + #endregion SUMMARYSecurityCustomRoles - if ($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 + #region SUMMARYSecurityRolesCanDoRoleAssignments + Write-Host ' processing TenantSummary Roles security (can apply Role assignments)' + if ($tenantAllRolesCanDoRoleAssignmentsCount -gt 0) { - if ($resourcesResourceTypeCount -gt 0) { - $tfCount = ($resourcesAllGroupedByType | Measure-Object).Count - $htmlTableId = "TenantSummary_resources" - [void]$htmlTenantSummary.AppendLine(@" -
    Download CSV semicolon | comma - - + + + + + "@) - $htmlSUMMARYResources = $null - $htmlSUMMARYResources = foreach ($resourceAllSummarized in $resourcesAllGroupedByType) { - $type = $resourceAllSummarized.Name - $script:htDailySummary."ResourceType_$($resourceAllSummarized.Name)" = ($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum - @" + $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 += @" - - + + + + + "@ - - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResources) - [void]$htmlTenantSummary.AppendLine(@" - -
    ResourceTypeResource CountRole NameRoleIdTypeRole assignmentsAssignable Scopes
    $($type)$(($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum)$($role.Name -replace '<', '<' -replace '>', '>')$($role.Id)$($roleType)$($roleAssignments)$($roleAssignableScopes)
    -
    - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    Resources ($resourcesResourceTypeCount ResourceTypes)

    + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) - } - - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    Resources (0 ResourceTypes)

    + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $($tenantAllRolesCanDoRoleAssignmentsCount) Role definitions can apply Role assignments

    "@) - } - $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 } + #endregion SUMMARYSecurityRolesCanDoRoleAssignments - if ($htParameters.NoResources -eq $false) { - #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(@" -
    Download CSV semicolon | comma - +
    - - - + + + + + "@) - $htmlSUMMARYResources = $null - $htmlSUMMARYResources = foreach ($resourceAllSummarized in $resourcesAllGroupedByTypeLocation) { - $typeLocation = $resourceAllSummarized.Name.Split(', ') - $type = $typeLocation[0] - $location = $typeLocation[1] - @" + $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 + @" - - - + + + + + "@ - - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResources) - [void]$htmlTenantSummary.AppendLine(@" - -
    ResourceTypeLocationResource CountRole NameRoleIdRole AssignmentServicePrincipal (ObjId)Impacted Mg/Sub
    $($type)$($location)$(($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum)$($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)
    -
    - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    Resources ($resourcesResourceTypeCount ResourceTypes)

    + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) - } - - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    Resources (0 ResourceTypes)

    + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $(($roleAssignmentsOwnerAssignmentSP).count) Owner permission assignments to ServicePrincipal ($scopeNamingSummary)

    "@) - } - $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 } + $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 SUMMARYSubResourceProviders - $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 - }) - } + #region SUMMARYSecurityOwnerAssignmentNotGroup + Write-Host ' processing TenantSummary RoleAssignments security (owner notGroup)' + $startSUMMARYSecurityOwnerAssignmentNotGroup = Get-Date - $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 + $roleAssignmentsOwnerAssignmentNotGroup = $rbacBaseQueryArrayListNotGroupOwner | Sort-Object -Property RoleAssignmentId -Unique + $roleAssignmentsOwnerAssignmentNotGroupGrouped = ($rbacBaseQueryArrayListNotGroupOwner | Group-Object -property roleassignmentId) - $providersNotRegisteredUniqueCount = 0 - foreach ($uniqueNamespace in $uniqueNamespaces) { - if ($providersRegistered -notcontains ($uniqueNamespace.namespace)) { - $providersNotRegisteredUniqueCount++ - } - } - $tfCount = $uniqueNamespacesCount - $htmlTableId = "TenantSummary_SubResourceProviders" + if (($roleAssignmentsOwnerAssignmentNotGroup).count -gt 0) { + $tfCount = ($roleAssignmentsOwnerAssignmentNotGroup).count + $htmlTableId = 'TenantSummary_roleAssignmentsOwnerAssignmentNotGroup' [void]$htmlTenantSummary.AppendLine(@" - +
    Download CSV semicolon | comma - +
    - - - - - + + + + + + + + "@) - $htmlSUMMARYSubResourceProviders = $null - $htmlSUMMARYSubResourceProviders = foreach ($provider in ($providerSummary | Sort-Object -Property Provider)) { + $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) }) @" - - - - - + + + + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubResourceProviders) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityOwnerAssignmentNotGroup) [void]$htmlTenantSummary.AppendLine(@"
    ProviderRegisteredRegisteringNotRegisteredUnregisteringRole NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdImpacted Mg/Sub
    $($provider.Provider)$($provider.Registered)$($provider.Registering)$($provider.NotRegistered)$($provider.Unregistering)$($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)
    @@ -15135,18 +13584,23 @@ extensions: [{ name: 'sort' }] if ($tfCount -gt 3000) { $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" } + [void]$htmlTenantSummary.AppendLine(@" paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) } [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, + col_3: 'multiple', col_types: [ 'caseinsensitivestring', - 'number', - 'number', - 'number', - 'number' + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring' ], extensions: [{ name: 'sort' }] }; @@ -15157,93 +13611,62 @@ extensions: [{ name: 'sort' }] } else { [void]$htmlTenantSummary.AppendLine(@" -

    $resourceProvidersAllCount Resource Providers

    +

    $(($roleAssignmentsOwnerAssignmentNotGroup).count) Owner permission assignments to notGroup ($scopeNamingSummary)

    "@) } - $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 + $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 SUMMARYSubResourceProvidersDetailed - if ($htParameters.NoResourceProvidersDetailed -eq $false) { + #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) - 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(@" - + if (($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count -gt 0) { + $tfCount = ($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count + $htmlTableId = 'TenantSummary_roleAssignmentsUserAccessAdministratorAssignmentNotGroup' + [void]$htmlTenantSummary.AppendLine(@" +
    Download CSV semicolon | comma - +
    - - - - + + + + + + + + "@) - - } - 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 - }) - } - - @" + $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) }) + @" - - - - - + + + + + + + + "@ - } - } - - #region exportCSV - if (-not $NoCsvExport) { - $csvFilename = "$($filename)_ResourceProviders" - Write-Host " Exporting ResourceProviders CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'" - $arrayResourceProvidersDetailedForCSVExport | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation - $arrayResourceProvidersDetailedForCSVExport = $null - } - #endregion exportCSV - - if ($tfCount -lt $HtmlTableRowsLimit) { - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubResourceProvidersDetailed) - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup) + [void]$htmlTenantSummary.AppendLine(@"
    SubscriptionSubscriptionIdSubscription MG path -ProviderStateRole NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdImpacted Mg/Sub
    $($subscriptionResProvDetails.DisplayName)$($subscriptionResProv)$($subscriptionResProvDetails.pathDelimited)$($provider.namespace)$($provider.registrationState)$($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)
    @@ -15252,113 +13675,6 @@ extensions: [{ name: 'sort' }] window.helpertfConfig4$htmlTableId =1; var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, - -"@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - [void]$htmlTenantSummary.AppendLine(@" -paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ -"@) - } - [void]$htmlTenantSummary.AppendLine(@" -btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - col_4: 'select', - col_types: [ - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring' - ], -extensions: [{ name: 'sort' }] - }; - var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); - tf.init();}} - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" - -
    - 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 AzGovViz 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 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(@" - -
    - Considerations before applying locks docs - - - - - - - - - - - - - - - - -
    Lock scopeLock typepresence
    SubscriptionCannotDelete$($subscriptionLocksCannotDeleteCount) of $totalSubCount Subscriptions
    SubscriptionReadOnly$($subscriptionLocksReadOnlyCount) of $totalSubCount Subscriptions
    ResourceGroupCannotDelete$($resourceGroupsLocksCannotDeleteCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourceGroupsLocksCannotDeleteCount | Measure-Object -Sum).Sum))
    ResourceGroupReadOnly$($resourceGroupsLocksReadOnlyCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourceGroupsLocksReadOnlyCount | Measure-Object -Sum).Sum))
    ResourceCannotDelete$($resourcesLocksCannotDeleteCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourcesLocksCannotDeleteCount | Measure-Object -Sum).Sum))
    ResourceReadOnly$($resourcesLocksReadOnlyCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourcesLocksReadOnlyCount | Measure-Object -Sum).Sum))
    - -
    "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    No Resource Locks at all docs

    +

    $(($roleAssignmentsUserAccessAdministratorAssignmentNotGroup).count) UserAccessAdministrator permission assignments to notGroup ($scopeNamingSummary)

    "@) } - $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 - - #SUMMARYSubDefenderPlansSubscriptionNotRegistered - if ($arrayDefenderPlansSubscriptionNotRegistered.Count -gt 0) { - #region SUMMARYSubDefenderPlansSubscriptionNotRegistered - Write-Host " processing TenantSummary Subscriptions Microsoft Defender for Cloud plans SubscriptionNotRegistered" - - $tfCount = $defenderPlansGroupedByPlanCount - $startDefenderPlans = Get-Date + $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 - $htmlTableId = "TenantSummary_DefenderPlansSubscriptionNotRegistered" + #region SUMMARYSecurityGuestUserHighPriviledgesAssignments + $startSUMMARYSecurityGuestUserHighPriviledgesAssignments = Get-Date + Write-Host ' processing TenantSummary RoleAssignments security (high priviledged Guest User)' + $highPriviledgedGuestUserRoleAssignments = $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 + $highPriviledgedGuestUserRoleAssignmentsCount = ($highPriviledgedGuestUserRoleAssignments).Count + if ($highPriviledgedGuestUserRoleAssignmentsCount -gt 0) { + $tfCount = $highPriviledgedGuestUserRoleAssignmentsCount + $htmlTableId = 'TenantSummary_SecurityGuestUserHighPriviledgesAssignments' [void]$htmlTenantSummary.AppendLine(@" - +
    - Register Resource Provider 'Microsoft.Security' docs
    - Microsoft Defender for Cloud's enhanced security features docs
    Download CSV semicolon | comma - +
    - - - + + + + + + + + "@) - - foreach ($subscription in $arrayDefenderPlansSubscriptionNotRegistered | Sort-Object -Property subscriptionName) { - [void]$htmlTenantSummary.AppendLine(@" - - - - - -"@) + $htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments = $null + $htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments = foreach ($highPriviledgedGuestUserRoleAssignment in ($highPriviledgedGuestUserRoleAssignments)) { + if ($highPriviledgedGuestUserRoleAssignment.AssignmentType -eq 'indirect') { + $assignmentInfo = "indirect / AAD Group Membership '$($highPriviledgedGuestUserRoleAssignment.AssignmentInheritFrom)'" + } + else { + $assignmentInfo = 'direct' + } + @" + + + + + + + + + + +"@ } - + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments) [void]$htmlTenantSummary.AppendLine(@" - -
    Subscription NameSubscription IdSubscription MG pathRole NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdAssignment direct/indirect
    $($subscription.subscriptionName)$($subscription.subscriptionId)$($subscription.subscriptionMgPath)
    $($highPriviledgedGuestUserRoleAssignment.Role <#-replace "<", "<" -replace ">", ">"#>)$($highPriviledgedGuestUserRoleAssignment.RoleId)$($highPriviledgedGuestUserRoleAssignment.RoleAssignmentId)$($highPriviledgedGuestUserRoleAssignment.ObjectType)$($highPriviledgedGuestUserRoleAssignment.ObjectDisplayName)$($highPriviledgedGuestUserRoleAssignment.ObjectSignInName)$($highPriviledgedGuestUserRoleAssignment.ObjectId)$assignmentInfo
    - -
    "@) - - $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 SUMMARYSubDefenderPlansSubscriptionNotRegistered } - - #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" - + else { [void]$htmlTenantSummary.AppendLine(@" - -
    +

    $($highPriviledgedGuestUserRoleAssignmentsCount) Guest Users with high permissions ($scopeNamingSummary)

    "@) + } + $endSUMMARYSecurityGuestUserHighPriviledgesAssignments = Get-Date + Write-Host " TenantSummary RoleAssignments security (high priviledged Guest User) duration: $((NEW-TIMESPAN -Start $startSUMMARYSecurityGuestUserHighPriviledgesAssignments -End $endSUMMARYSecurityGuestUserHighPriviledgesAssignments).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startSUMMARYSecurityGuestUserHighPriviledgesAssignments -End $endSUMMARYSecurityGuestUserHighPriviledgesAssignments).TotalSeconds) seconds)" + #endregion SUMMARYSecurityGuestUserHighPriviledgesAssignments - if ($defenderPlanDeprecatedContainerRegistry) { - [void]$htmlTenantSummary.AppendLine(@" - Using deprecated plan 'Container registries' docs
    -"@) - } - if ($defenderPlanDeprecatedKubernetesService) { - [void]$htmlTenantSummary.AppendLine(@" - Using deprecated plan 'Kubernetes' docs
    -"@) - } + [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(@" - Microsoft Defender for Cloud's enhanced security features docs
    + +
    Download CSV semicolon | comma - - + + + + "@) - - foreach ($defenderCapabilityAndTier in $defenderPlansGroupedByPlan | Sort-Object -Property Name) { - if ($defenderCapabilityAndTier.Name -eq "ContainerRegistry, Standard" -or $defenderCapabilityAndTier.Name -eq "KubernetesService, Standard") { - $thisDefenderPlan = " $($defenderCapabilityAndTier.Name)" + $htmlSUMMARYBlueprintDefinitions = $null + $htmlSUMMARYBlueprintDefinitions = foreach ($blueprintDefinition in $blueprintDefinitions | Sort-Object -Property BlueprintName, BlueprintDisplayName) { + @" + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintDefinitions) + [void]$htmlTenantSummary.AppendLine(@" + +
    Plan/TierSubscription CountBlueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintId
    $($blueprintDefinition.BlueprintName -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintDisplayName -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintDescription -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintId -replace '<', '<' -replace '>', '>')
    +
    + -
    +btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, + col_types: [ + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring' + ], +extensions: [{ name: 'sort' }] + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    No Microsoft Defender for Cloud plans at all

    +

    $blueprintDefinitionsCount Blueprint definitions

    "@) } - $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 + #endregion SUMMARYBlueprintDefinitions - if (($arrayDefenderPlans).Count -gt 0) { - $htmlTableId = "TenantSummary_DefenderPlansBySubscription" + #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(@" - +
    -"@) - - 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 - - - -"@) - - foreach ($defenderCapability in $defenderCapabilities) { - if (($defenderPlanDeprecatedContainerRegistry -and $defenderCapability -eq "ContainerRegistry") -or ($defenderPlanDeprecatedKubernetesService -and $defenderCapability -eq "KubernetesService")) { - $thisDefenderCapability = " $($defenderCapability)" - } - else { - $thisDefenderCapability = $defenderCapability - } - [void]$htmlTenantSummary.AppendLine(@" - -"@) - - } - - [void]$htmlTenantSummary.AppendLine(@" + + + + + + "@) - - foreach ($sub in $defenderPlansGroupedBySub) { - $nameSplit = $sub.Name.split(', ') - [void]$htmlTenantSummary.AppendLine(@" - - - - - -"@) - - foreach ($plan in $sub.Group | Sort-Object -Property defenderPlan) { - [void]$htmlTenantSummary.AppendLine(@" - -"@) - } - [void]$htmlTenantSummary.AppendLine(@" - -"@) + $htmlSUMMARYBlueprintAssignments = $null + $htmlSUMMARYBlueprintAssignments = foreach ($blueprintAssignment in $blueprintAssignments | Sort-Object -Property level, BlueprintAssignmentId) { + @" + + + + + + + + +"@ } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintAssignments) [void]$htmlTenantSummary.AppendLine(@" - -
    SubscriptionSubscriptionIdSubscription MG path$($thisDefenderCapability)Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdBlueprint VersionBlueprint AssignmentId
    $($nameSplit[0])$($nameSplit[1])$($nameSplit[2])$($plan.defenderPlanTier)
    $($blueprintAssignment.BlueprintName -replace '<', '<' -replace '>', '>')$($blueprintAssignment.BlueprintDisplayName -replace '<', '<' -replace '>', '>')$($blueprintAssignment.BlueprintDescription -replace '<', '<' -replace '>', '>')$($blueprintAssignment.BlueprintId -replace '<', '<' -replace '>', '>')$($blueprintAssignment.BlueprintAssignmentVersion)$($blueprintAssignment.BlueprintAssignmentId -replace '<', '<' -replace '>', '>')
    - "@) - } + } + else { [void]$htmlTenantSummary.AppendLine(@" - col_types: [ - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', -"@) - $cnt = 0 - foreach ($defenderCapability in $defenderCapabilities) { - $cnt++ - if ($cnt -ne $defenderCapabilitiesCount) { - [void]$htmlTenantSummary.AppendLine(@" - 'caseinsensitivestring', -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" - 'caseinsensitivestring' -"@) - } - - } - [void]$htmlTenantSummary.AppendLine(@" - ], -extensions: [{ name: 'sort' }] - }; - var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); - tf.init();}} - -
    -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    No Microsoft Defender for Cloud plans at all

    +

    $blueprintAssignmentsCount Blueprint assignments

    "@) } - $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 ($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) { + #endregion SUMMARYBlueprintAssignments - $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++ + #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 - $htmlTableId = "TenantSummary_UserAssignedIdentities4Resources" + if ($blueprintDefinitionsOrphanedCount -gt 0) { - [void]$htmlTenantSummary.AppendLine(@" - + $htmlTableId = 'TenantSummary_BlueprintsOrphaned' + [void]$htmlTenantSummary.AppendLine(@" +
    - Managed identity 'user-assigned' vs 'system-assigned' docs
    Download CSV semicolon | comma - - - - - - - - - - - - - - - - - -"@) - - [void]$htmlTenantSummary.AppendLine(@" + + + + "@) - - $userAssignedIdentities4Resources4CSVExport = [System.Collections.ArrayList]@() - foreach ($miResEntry in $arrayUserAssignedIdentities4Resources | Sort-Object -Property miResourceId, resourceId) { - [void]$htmlTenantSummary.AppendLine(@" - - - - - - - - - - - - - - - - - - - -"@) - - 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 - 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, resourceId | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_UserAssignedIdentities4Resources.csv" -Delimiter "$csvDelimiter" -NoTypeInformation - } - - [void]$htmlTenantSummary.AppendLine(@" - -
    MI NameMI MgPathMI Subscription NameMI Subscription IdMI ResourceGroupMI ResourceIdMI AAD SP objectIdMI AAD SP applicationIdMI count Res assignmentsRes NameRes TypeRes MgPathRes Subscription NameRes Subscription IdRes ResourceGroupRes IdRes count assigned MIsBlueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintId
    $($miResEntry.miResourceName)$($miResEntry.miMgPath)$($miResEntry.miSubscriptionName)$($miResEntry.miSubscriptionId)$($miResEntry.miResourceGroupName)$($miResEntry.miResourceId)$($miResEntry.miPrincipalId)$($miResEntry.miClientId)$($htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount)$($miResEntry.resourceName)$($miResEntry.resourceType)$($miResEntry.resourceMgPath)$($miResEntry.resourceSubscriptionName)$($miResEntry.resourceSubscriptionId)$($miResEntry.resourceResourceGroupName)$($miResEntry.resourceId)$($htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount)
    - -
    "@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    No UserAssigned Managed Identities assigned to Resources / vice versa - at all

    + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $blueprintDefinitionsOrphanedCount Orphaned Blueprint definitions

    "@) - } - $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 } + #endregion SUMMARYBlueprintsOrphaned - [void]$htmlTenantSummary.AppendLine(@" + [void]$htmlTenantSummary.AppendLine(@'
    -"@) - #endregion tenantSummarySubscriptions - - #region tenantSummaryDiagnostics - [void]$htmlTenantSummary.AppendLine(@" - -
    -"@) +'@) + #endregion tenantSummaryBlueprints - [void]$htmlTenantSummary.AppendLine( @" -

    Management Groups

    -"@) + showMemoryUsage - #region SUMMARYDiagnosticsManagementGroups - Write-Host " processing TenantSummary Diagnostics Management Groups" + #region tenantSummaryManagementGroups + [void]$htmlTenantSummary.AppendLine(@' + +
    +'@) - #hasDiag - if ($diagnosticSettingsMgCount -gt 0) { - $tfCount = $diagnosticSettingsMgCount - $htmlTableId = "TenantSummary_DiagnosticsManagementGroups" + #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(@" - +
    - Management Group Diagnostic Settings - Create Or Update - REST API docs
    Download CSV semicolon | comma - +
    - - - - - - - + + + + + + + "@) - - foreach ($logCategory in $diagnosticSettingsMgCategories) { + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + +'@) + } + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { [void]$htmlTenantSummary.AppendLine(@" - + "@) } - - [void]$htmlTenantSummary.AppendLine(@" + [void]$htmlTenantSummary.AppendLine(@' + -"@) - $htmlSUMMARYDiagnosticsManagementGroups = $null - $htmlSUMMARYDiagnosticsManagementGroups = foreach ($entry in $diagnosticSettingsMg | Sort-Object -Property ScopeMgPath, DiagnosticsInheritedFrom, DiagnosticSettingName, DiagnosticTargetType, DiagnosticTargetId) { +'@) + $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' + } + } @" - - - - - - - + + + + + + + "@ - foreach ($logCategory in $diagnosticSettingsMgCategories) { - if ($entry.DiagnosticCategoriesHt.($logCategory)) { - @" - + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + @" + "@ - } - else { - @" - + } + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + @" + "@ - } } @" + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYDiagnosticsManagementGroups) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYManagementGroups) [void]$htmlTenantSummary.AppendLine(@" - -
    Management Group NameManagement Group IdDiagnostic settingInheritanceInherited fromTargetTargetIdLevelManagementGroupManagementGroup IdMg children (total)Mg children (direct)Sub children (total)Sub children (direct)MG MDfC Score$logCategoryCost ($($AzureConsumptionPeriod)d)Path
    $($entry.ScopeName -replace "<", "<" -replace ">", ">")$($entry.ScopeId)$($entry.DiagnosticSettingName)$($entry.DiagnosticsInheritedOrnot)$($entry.DiagnosticsInheritedFrom)$($entry.DiagnosticTargetType)$($entry.DiagnosticTargetId)$($summaryManagementGroup.level)$($summaryManagementGroup.mgName -replace '<', '<' -replace '>', '>')$($summaryManagementGroup.mgId)$($mgAllChildMgsCountTotal)$($mgAllChildMgsCountDirect)$($mgAllChildSubscriptionsCountTotal)$($mgAllChildSubscriptionsCountDirect)$($entry.DiagnosticCategoriesHt.($logCategory))$($mgSecureScore)n/a$($arrayTotalCostSummaryMgSummary -join ', ')$($pathhlper)
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + + "@) } else { - [void]$htmlTenantSummary.AppendLine(@" -

    No Management Groups configured for Diagnostic settings docs

    +

    $($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 - #hasNoDiag - if ($arrayMgsWithoutDiagnosticsCount -gt 0) { - $tfCount = $arrayMgsWithoutDiagnosticsCount - $htmlTableId = "TenantSummary_NoDiagnosticsManagementGroups" + #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 tenantSummarySubscriptions + [void]$htmlTenantSummary.AppendLine(@' + +
    +'@) + + #region SUMMARYSubs + Write-Host ' processing TenantSummary Subscriptions' + $summarySubscriptions = $optimizedTableForPathQueryMgAndSub | Sort-Object -Property Subscription + $summarySubscriptionsCount = ($summarySubscriptions).Count + if ($summarySubscriptionsCount -gt 0) { + $tfCount = $summarySubscriptionsCount + $htmlTableId = 'TenantSummary_subs' [void]$htmlTenantSummary.AppendLine(@" - +
    - Management Group Diagnostic Settings - Create Or Update - REST API docs
    + Supported Microsoft Azure offers docs
    + Understand Microsoft Defender for Cloud Secure Score Video , Blog , docs
    Download CSV semicolon | comma - +
    - - - + + + + + + +"@) + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + [void]$htmlTenantSummary.AppendLine(@" + + +"@) + } + [void]$htmlTenantSummary.AppendLine(@' + -"@) - $htmlSUMMARYNoDiagnosticsManagementGroups = $null - $htmlSUMMARYNoDiagnosticsManagementGroups = foreach ($entry in $arrayMgsWithoutDiagnostics | Sort-Object -Property ScopeMgPath) { +'@) + $htmlSUMMARYSubs = $null + $htmlSUMMARYSubs = foreach ($summarySubscription in $summarySubscriptions) { + $subPath = $htSubscriptionsMgPath.($summarySubscription.subscriptionId).pathDelimited + $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 ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + @" + + +"@ + } + @" + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNoDiagnosticsManagementGroups) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubs) [void]$htmlTenantSummary.AppendLine(@" - -
    Management Group NameManagement Group IdManagement Group pathSubscriptionSubscriptionIdQuotaIdRole assignment limitTagsSub MDfC ScoreCost ($($AzureConsumptionPeriod)d)CurrencyPath
    $($entry.ScopeName -replace "<", "<" -replace ">", ">")$($entry.ScopeId)$($entry.ScopeMgPath)$($summarySubscription.subscription -replace '<', '<' -replace '>', '>')$($summarySubscription.subscriptionId)$($summarySubscription.SubscriptionQuotaId)$($htSubscriptionsRoleAssignmentLimit.($summarySubscription.subscriptionId))$(($subscriptionTagsArray | Sort-Object) -join "$CsvDelimiterOpposite ")$($summarySubscription.SubscriptionASCSecureScore)$totalCost$currency $subPath
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + + "@) } else { - [void]$htmlTenantSummary.AppendLine(@" -

    All Management Groups are configured for Diagnostic settings docs

    +

    $($summarySubscriptionsCount) Subscriptions

    "@) } - #endregion SUMMARYDiagnosticsManagementGroups - - #region subscriptions - [void]$htmlTenantSummary.AppendLine( @" -

    Subscriptions

    -"@) - - #region SUMMARYDiagnosticsSubscriptions - Write-Host " processing TenantSummary Diagnostics Subscriptions" + #endregion SUMMARYSubs - #hasDiag - if ($diagnosticSettingsSubCount -gt 0) { - $tfCount = $diagnosticSettingsSubCount - $htmlTableId = "TenantSummary_DiagnosticsSubscriptions" + #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(@" - +
    - Create diagnostic setting docs
    Download CSV semicolon | comma - +
    - + - - - - -"@) - - foreach ($logCategory in $diagnosticSettingsSubCategories) { - [void]$htmlTenantSummary.AppendLine(@" - -"@) - } - - [void]$htmlTenantSummary.AppendLine(@" + + "@) - $htmlSUMMARYDiagnosticsSubscriptions = $null - $htmlSUMMARYDiagnosticsSubscriptions = foreach ($entry in $diagnosticSettingsSub | Sort-Object -Property ScopeName, DiagnosticTargetType, DiagnosticSettingName) { - + $htmlSUMMARYOutOfScopeSubscriptions = $null + $htmlSUMMARYOutOfScopeSubscriptions = foreach ($outOfScopeSubscription in $outOfScopeSubscriptions) { @" - - - - - - -"@ - foreach ($logCategory in $diagnosticSettingsSubCategories) { - if ($entry.DiagnosticCategoriesHt.($logCategory)) { - @" - -"@ - } - else { - @" - -"@ - } - } - @" + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYDiagnosticsSubscriptions) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOutOfScopeSubscriptions) [void]$htmlTenantSummary.AppendLine(@" - -
    SubscriptionSubscription Name SubscriptionIdPathDiagnostic settingTargetTargetId$logCategoryout-of-scope reasonManagement Group
    $($entry.ScopeName -replace "<", "<" -replace ">", ">")$($entry.ScopeId) $($entry.ScopeMgPath)$($entry.DiagnosticSettingName)$($entry.DiagnosticTargetType)$($entry.DiagnosticTargetId)$($entry.DiagnosticCategoriesHt.($logCategory))n/a$($outOfScopeSubscription.SubscriptionName)$($outOfScopeSubscription.SubscriptionId)$($outOfScopeSubscription.outOfScopeReason) $($outOfScopeSubscription.ManagementGroupName -replace '<', '<' -replace '>', '>') ($($outOfScopeSubscription.ManagementGroupId))
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { - [void]$htmlTenantSummary.AppendLine(@" -

    No Subscriptions configured for Diagnostic settings docs

    +

    $outOfScopeSubscriptionsCount Subscriptions out-of-scope

    "@) } + #endregion SUMMARYOutOfScopeSubscriptions - #hasNoDiag - if ($diagnosticSettingsSubNoDiagCount -gt 0) { - $tfCount = $diagnosticSettingsSubNoDiagCount - $htmlTableId = "TenantSummary_NoDiagnosticsSubscriptions" + #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(@" - +
    - Create diagnostic setting docs
    + Resource naming and tagging decision guide docs
    Download CSV semicolon | comma - +
    - - - + + + "@) - $htmlSUMMARYNoDiagnosticsSubscriptions = $null - $htmlSUMMARYNoDiagnosticsSubscriptions = foreach ($entry in $diagnosticSettingsSubNoDiag | Sort-Object -Property ScopeMgPath) { - + $htmlSUMMARYtagsUsage = $null + $htmlSUMMARYtagsUsage = foreach ($tagEntry in $arrayTagList | Sort-Object -Property Scope, TagName -CaseSensitive) { @" - - - + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNoDiagnosticsSubscriptions) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtagsUsage) [void]$htmlTenantSummary.AppendLine(@" - -
    SubscriptionSubscription IdSubscription Mg pathScopeTagNameCount
    $($entry.ScopeName -replace "<", "<" -replace ">", ">")$($entry.ScopeId)$($entry.ScopeMgPath)$($tagEntry.Scope)$($tagEntry.TagName)$($tagEntry.TagCount)
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { - [void]$htmlTenantSummary.AppendLine(@" -

    All Subscriptions are configured for Diagnostic settings docs

    +

    Tag Name Usage ($tagsUsageCount Tags) docs

    "@) } - #endregion SUMMARYDiagnosticsSubscriptions - - #endregion subscriptions + #endregion SUMMARYTagNameUsage - if ($htParameters.NoResources -eq $false) { - #region resources - [void]$htmlTenantSummary.AppendLine( @" -

    Resources

    -"@) + 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 - #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(@" - + if ($resourcesResourceTypeCount -gt 0) { + $tfCount = ($resourcesAllGroupedByType | Measure-Object).Count + $htmlTableId = 'TenantSummary_resources' + [void]$htmlTenantSummary.AppendLine(@" +
    - 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 - +
    - - - - "@) - $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 - } - } - @" + $htmlSUMMARYResources = $null + $htmlSUMMARYResources = foreach ($resourceAllSummarized in $resourcesAllGroupedByType) { + $type = $resourceAllSummarized.Name + $script:htDailySummary."ResourceType_$($resourceAllSummarized.Name)" = ($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum + @" - - - - - - + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResourcesDiagnosticsCapable) - [void]$htmlTenantSummary.AppendLine(@" + + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResources) + [void]$htmlTenantSummary.AppendLine(@"
    ResourceType Resource CountDiagnostics capableMetricsLogsLogCategories
    $($resourceType.ResourceType)$($resourceType.ResourceCount)$diagnosticsCapable$($resourceType.Metrics)$($resourceType.Logs)$($resourceType.LogCategories -join "$CsvDelimiterOpposite ")$($type)$(($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum)
    @@ -16499,43 +14802,35 @@ extensions: [{ name: 'sort' }] var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - [void]$htmlTenantSummary.AppendLine(@" + if ($tfCount -gt 10) { + $spectrum = "10, $tfCount" + if ($tfCount -gt 50) { + $spectrum = "10, 25, 50, $tfCount" + } + if ($tfCount -gt 100) { + $spectrum = "10, 30, 50, 100, $tfCount" + } + if ($tfCount -gt 500) { + $spectrum = "10, 30, 50, 100, 250, $tfCount" + } + if ($tfCount -gt 1000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" + } + if ($tfCount -gt 2000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + if ($tfCount -gt 3000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" + } + [void]$htmlTenantSummary.AppendLine(@" paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) - } - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - linked_filters: true, - col_2: 'select', - col_3: 'select', - col_4: 'select', col_types: [ 'caseinsensitivestring', - 'number', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring' + 'number' ], extensions: [{ name: 'sort' }] }; @@ -16543,335 +14838,68 @@ extensions: [{ name: 'sort' }] tf.init();}} "@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    Resources ($resourcesResourceTypeCount ResourceTypes)

    +"@) + } + } else { - - [void]$htmlTenantSummary.AppendLine(@" -

    No Resources (1st party) Diagnostics capable

    -"@) + [void]$htmlTenantSummary.AppendLine(@' +

    Resources (0 ResourceTypes)

    +'@) } - #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" - } + $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 + } - if ( (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match "/providers/diagnosticSettings" }).properties.logs ) { + if ($azAPICallConf['htParameters'].NoResources -eq $false) { + #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 - $resourceType = ( (($policy).Json.properties.policyrule.then.details.deployment.properties.template.resources | Where-Object { $_.type -match "/providers/diagnosticSettings" }).type -replace "/providers/diagnosticSettings") + if ($resourcesResourceTypeCount -gt 0) { + $tfCount = ($resourcesAllGroupedByTypeLocation | Measure-Object).Count + $htmlTableId = 'TenantSummary_resourcesByLocation' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYResources = $null + $htmlSUMMARYResources = foreach ($resourceAllSummarized in $resourcesAllGroupedByTypeLocation) { + $typeLocation = $resourceAllSummarized.Name.Split(', ') + $type = $typeLocation[0] + $location = $typeLocation[1] + @" + + + + + +"@ - $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 = "AzGovViz 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 - 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 | Measure-Object).count - - if ($diagnosticsPolicyAnalysisCount -gt 0) { - $tfCount = $diagnosticsPolicyAnalysisCount - - $htmlTableId = "TenantSummary_DiagnosticsLifecycle" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Create Custom Policies for Azure ResourceTypes that support Diagnostics Logs and Metrics Create-AzDiagPolicy
    - Supported categories for Azure Resource Logs docs -
    ResourceTypeLocationResource Count
    $($type)$($location)$(($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum)
    - - - - - - - - - - - - - - - - - - -"@) - - foreach ($diagnosticsFinding in $diagnosticsPolicyAnalysis | Sort-Object -property @{Expression = { $_.Priority } }, @{Expression = { $_.Recommendation } }, @{Expression = { $_.ResourceType } }, @{Expression = { $_.PolicyName } }, @{Expression = { $_.PolicyId } }) { - [void]$htmlTenantSummary.AppendLine(@" - - - - - - - - - - - - - - - -"@) - } - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResources) + [void]$htmlTenantSummary.AppendLine(@"
    PriorityRecommendationResourceTypeResource CountDiagnostics capable (logs)Policy IdPolicy DisplayNameRole definitionsTargetLog Categories not covered by PolicyPolicy assignmentsPolicy used in PolicySetPolicySet assignments
    - $($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) -
    @@ -16881,50 +14909,35 @@ extensions: [{ name: 'sort' }] var tfConfig4$htmlTableId = { base_path: 'https://www.azadvertizer.net/azgovvizv4/tablefilter/', rows_counter: true, "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" - } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" - } - - [void]$htmlTenantSummary.AppendLine(@" - paging: { - results_per_page: ['Records: ', [$spectrum]] - }, - /*state: { types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ + if ($tfCount -gt 10) { + $spectrum = "10, $tfCount" + if ($tfCount -gt 50) { + $spectrum = "10, 25, 50, $tfCount" + } + if ($tfCount -gt 100) { + $spectrum = "10, 30, 50, 100, $tfCount" + } + if ($tfCount -gt 500) { + $spectrum = "10, 30, 50, 100, 250, $tfCount" + } + if ($tfCount -gt 1000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" + } + if ($tfCount -gt 2000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + if ($tfCount -gt 3000) { + $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" + } + [void]$htmlTenantSummary.AppendLine(@" +paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ "@) - } - [void]$htmlTenantSummary.AppendLine(@" + } + [void]$htmlTenantSummary.AppendLine(@" btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - col_0: 'select', col_types: [ 'caseinsensitivestring', 'caseinsensitivestring', - 'caseinsensitivestring', - 'number', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring', - 'number', - 'number', 'number' ], extensions: [{ name: 'sort' }] @@ -16933,121 +14946,130 @@ extensions: [{ name: 'sort' }] tf.init();}} "@) - } - 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

    +

    Resources ($resourcesResourceTypeCount ResourceTypes)

    "@) } - $endsumDiagLifecycle = Get-Date - Write-Host " Resource Diagnostics Policy Lifecycle processing duration: $((NEW-TIMESPAN -Start $startsumDiagLifecycle -End $endsumDiagLifecycle).TotalSeconds) seconds" - } - #endregion SUMMARYDiagnosticsPolicyLifecycle - #endregion resources + } + 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 } - [void]$htmlTenantSummary.AppendLine(@" -
    -"@) - - #endregion tenantSummaryDiagnostics - - #region tenantSummaryLimits - [void]$htmlTenantSummary.AppendLine(@" - -
    -"@) + #region SUMMARYSubResourceProviders + $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' + } - #region tenantSummaryLimitsTenant - [void]$htmlTenantSummary.AppendLine( @" -

    Tenant

    -"@) + if ($hlperProvider.registering) { + $registering = $hlperProvider.registering + } + else { + $registering = '0' + } - #policySets - if ($tenantCustompolicySetsCount -gt (($LimitPOLICYPolicySetDefinitionsScopedTenant * $LimitCriticalPercentage) / 100)) { - [void]$htmlTenantSummary.AppendLine(@" -

    PolicySet definitions: $tenantCustompolicySetsCount/$LimitPOLICYPolicySetDefinitionsScopedTenant docs

    -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    PolicySet definitions: $tenantCustompolicySetsCount/$LimitPOLICYPolicySetDefinitionsScopedTenant docs

    -"@) - } + if ($hlperProvider.notregistered) { + $notregistered = $hlperProvider.notregistered + } + else { + $notregistered = '0' + } - #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

    -"@) - } + if ($hlperProvider.unregistering) { + $unregistering = $hlperProvider.unregistering + } + else { + $unregistering = '0' + } - #endregion tenantSummaryLimitsTenant + $null = $providerSummary.Add([PSCustomObject]@{ + Provider = $provider + Registered = $registered + NotRegistered = $notregistered + Registering = $registering + Unregistering = $unregistering + }) + } - #region tenantSummaryLimitsManagementGroups - [void]$htmlTenantSummary.AppendLine( @" -

    Management Groups

    -"@) + $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 - #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" + $providersNotRegisteredUniqueCount = 0 + foreach ($uniqueNamespace in $uniqueNamespaces) { + if ($providersRegistered -notcontains ($uniqueNamespace.namespace)) { + $providersNotRegisteredUniqueCount++ + } + } + $tfCount = $uniqueNamespacesCount + $htmlTableId = 'TenantSummary_SubResourceProviders' [void]$htmlTenantSummary.AppendLine(@" - +
    - Azure Policy Limits docs
    Download CSV semicolon | comma - +
    - - - + + + + + "@) - $htmlSUMMARYMgsapproachingLimitsPolicyAssignments = $null - $htmlSUMMARYMgsapproachingLimitsPolicyAssignments = foreach ($mgApproachingLimitPolicyAssignments in $mgsApproachingLimitPolicyAssignments) { + $htmlSUMMARYSubResourceProviders = $null + $htmlSUMMARYSubResourceProviders = foreach ($provider in ($providerSummary | Sort-Object -Property Provider)) { @" - - - + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicyAssignments) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubResourceProviders) [void]$htmlTenantSummary.AppendLine(@" - -
    Management Group NameManagement Group IdLimitProviderRegisteredRegisteringNotRegisteredUnregistering
    $($mgApproachingLimitPolicyAssignments.MgName -replace "<", "<" -replace ">", ">")$($mgApproachingLimitPolicyAssignments.MgId)$(($mgApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount/$LimitPOLICYPolicyAssignmentsManagementGroup).tostring("P")) ($($mgApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount)/$($LimitPOLICYPolicyAssignmentsManagementGroup)) ($($mgApproachingLimitPolicyAssignments.PolicyAssignmentAtScopeCount) Policy assignments, $($mgApproachingLimitPolicyAssignments.PolicySetAssignmentAtScopeCount) PolicySet assignments)$($provider.Provider)$($provider.Registered)$($provider.Registering)$($provider.NotRegistered)$($provider.Unregistering)
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    $(($mgsApproachingLimitPolicyAssignments | measure-object).count) Management Groups approaching Limit ($LimitPOLICYPolicyAssignmentsManagementGroup) for PolicyAssignment docs

    +

    $resourceProvidersAllCount Resource Providers

    "@) } - #endregion SUMMARYMgsapproachingLimitsPolicyAssignments + $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 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(@" - -
    - Azure Policy Limits docs
    - Download CSV semicolon | comma - - - - - - + #region SUMMARYSubResourceProvidersDetailed + 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(@" + +
    + Download CSV semicolon | comma +
    Management Group NameManagement Group IdLimit
    + + + + + + "@) - $htmlSUMMARYMgsapproachingLimitsPolicyScope = $null - $htmlSUMMARYMgsapproachingLimitsPolicyScope = foreach ($mgApproachingLimitPolicyScope in $mgsApproachingLimitPolicyScope) { - @" + + } + 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 + }) + } + + @" - - - + + + + + "@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicyScope) - [void]$htmlTenantSummary.AppendLine(@" - -
    SubscriptionSubscriptionIdSubscription MG path +ProviderState
    $($mgApproachingLimitPolicyScope.MgName -replace "<", "<" -replace ">", ">")$($mgApproachingLimitPolicyScope.MgId)$(($mgApproachingLimitPolicyScope.PolicyDefinitionsScopedCount/$LimitPOLICYPolicyDefinitionsScopedManagementGroup).tostring("P")) $($mgApproachingLimitPolicyScope.PolicyDefinitionsScopedCount)/$($LimitPOLICYPolicyDefinitionsScopedManagementGroup)$($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 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation + $arrayResourceProvidersDetailedForCSVExport = $null + } + #endregion exportCSV + + if ($tfCount -lt $HtmlTableRowsLimit) { + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubResourceProvidersDetailed) + [void]$htmlTenantSummary.AppendLine(@" + +
    +"@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" + +
    + 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 AzGovViz 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 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(@" + +
    + Considerations before applying locks docs + + + + + + + + + + + + + + + + +
    Lock scopeLock typepresence
    SubscriptionCannotDelete$($subscriptionLocksCannotDeleteCount) of $totalSubCount Subscriptions
    SubscriptionReadOnly$($subscriptionLocksReadOnlyCount) of $totalSubCount Subscriptions
    ResourceGroupCannotDelete$($resourceGroupsLocksCannotDeleteCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourceGroupsLocksCannotDeleteCount | Measure-Object -Sum).Sum))
    ResourceGroupReadOnly$($resourceGroupsLocksReadOnlyCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourceGroupsLocksReadOnlyCount | Measure-Object -Sum).Sum))
    ResourceCannotDelete$($resourcesLocksCannotDeleteCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourcesLocksCannotDeleteCount | Measure-Object -Sum).Sum))
    ResourceReadOnly$($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 + + #SUMMARYSubDefenderPlansSubscriptionNotRegistered + if ($arrayDefenderPlansSubscriptionNotRegistered.Count -gt 0) { + #region SUMMARYSubDefenderPlansSubscriptionNotRegistered + Write-Host ' processing TenantSummary Subscriptions Microsoft Defender for Cloud plans SubscriptionNotRegistered' + + $tfCount = $defenderPlansGroupedByPlanCount + $startDefenderPlans = Get-Date + + $htmlTableId = 'TenantSummary_DefenderPlansSubscriptionNotRegistered' + [void]$htmlTenantSummary.AppendLine(@" -

    $($mgsApproachingLimitPolicyScope.count) Management Groups approaching Limit ($LimitPOLICYPolicyDefinitionsScopedManagementGroup) for Policy Scope docs

    + +
    + Register Resource Provider 'Microsoft.Security' docs
    + Microsoft Defender for Cloud's enhanced security features docs
    + Download CSV semicolon | comma + + + + + + + + + "@) - } - #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(@" - + foreach ($subscription in $arrayDefenderPlansSubscriptionNotRegistered | Sort-Object -Property subscriptionName) { + [void]$htmlTenantSummary.AppendLine(@" + + + + + +"@) + } + + [void]$htmlTenantSummary.AppendLine(@" + +
    Subscription NameSubscription IdSubscription MG path
    $($subscription.subscriptionName)$($subscription.subscriptionId)$($subscription.subscriptionMgPath)
    + +
    +"@) + + $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 SUMMARYSubDefenderPlansSubscriptionNotRegistered + } + + #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(@" + +
    +"@) + + 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 + + + + + + + + +"@) + + 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(@" + + + + +"@) + } + + [void]$htmlTenantSummary.AppendLine(@" + +
    Plan/TierSubscription Count
    $($thisDefenderPlan)$($defenderCapabilityAndTier.Count)
    + +
    +"@) + } + 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(@" + +
    +"@) + + 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 + + + + + + +"@) + + foreach ($defenderCapability in $defenderCapabilities) { + if (($defenderPlanDeprecatedContainerRegistry -and $defenderCapability -eq 'ContainerRegistry') -or ($defenderPlanDeprecatedKubernetesService -and $defenderCapability -eq 'KubernetesService')) { + $thisDefenderCapability = " $($defenderCapability)" + } + else { + $thisDefenderCapability = $defenderCapability + } + [void]$htmlTenantSummary.AppendLine(@" + +"@) + + } + + [void]$htmlTenantSummary.AppendLine(@' + + + +'@) + + foreach ($sub in $defenderPlansGroupedBySub) { + $nameSplit = $sub.Name.split(', ') + [void]$htmlTenantSummary.AppendLine(@" + + + + + +"@) + + foreach ($plan in $sub.Group | Sort-Object -Property defenderPlan) { + [void]$htmlTenantSummary.AppendLine(@" + +"@) + } + [void]$htmlTenantSummary.AppendLine(@' + +'@) + } + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdSubscription MG path$($thisDefenderCapability)
    $($nameSplit[0])$($nameSplit[1])$($nameSplit[2])$($plan.defenderPlanTier)
    + +
    +"@) + } + 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(@" + +
    + Managed identity 'user-assigned' vs 'system-assigned' docs
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + + +"@) + + [void]$htmlTenantSummary.AppendLine(@' + + + +'@) + + $userAssignedIdentities4Resources4CSVExport = [System.Collections.ArrayList]@() + foreach ($miResEntry in $arrayUserAssignedIdentities4Resources | Sort-Object -Property miResourceId, resourceId) { + [void]$htmlTenantSummary.AppendLine(@" + + + + + + + + + + + + + + + + + + + +"@) + + 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 + 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, resourceId | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_UserAssignedIdentities4Resources.csv" -Delimiter "$csvDelimiter" -NoTypeInformation + } + + [void]$htmlTenantSummary.AppendLine(@" + +
    MI NameMI MgPathMI Subscription NameMI Subscription IdMI ResourceGroupMI ResourceIdMI AAD SP objectIdMI AAD SP applicationIdMI count Res assignmentsRes NameRes TypeRes MgPathRes Subscription NameRes Subscription IdRes ResourceGroupRes IdRes count assigned MIs
    $($miResEntry.miResourceName)$($miResEntry.miMgPath)$($miResEntry.miSubscriptionName)$($miResEntry.miSubscriptionId)$($miResEntry.miResourceGroupName)$($miResEntry.miResourceId)$($miResEntry.miPrincipalId)$($miResEntry.miClientId)$($htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount)$($miResEntry.resourceName)$($miResEntry.resourceType)$($miResEntry.resourceMgPath)$($miResEntry.resourceSubscriptionName)$($miResEntry.resourceSubscriptionId)$($miResEntry.resourceResourceGroupName)$($miResEntry.resourceId)$($htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount)
    + +
    +"@) + } + 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 + } + + [void]$htmlTenantSummary.AppendLine(@' +
    +'@) + #endregion tenantSummarySubscriptions + + 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(@" + +
    + Management Group Diagnostic Settings - Create Or Update - REST API docs
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + + foreach ($logCategory in $diagnosticSettingsMgCategories) { + [void]$htmlTenantSummary.AppendLine(@" + +"@) + } + + [void]$htmlTenantSummary.AppendLine(@' + + + +'@) + $htmlSUMMARYDiagnosticsManagementGroups = $null + $htmlSUMMARYDiagnosticsManagementGroups = foreach ($entry in $diagnosticSettingsMg | Sort-Object -Property ScopeMgPath, DiagnosticsInheritedFrom, DiagnosticSettingName, DiagnosticTargetType, DiagnosticTargetId) { + + @" + + + + + + + + +"@ + foreach ($logCategory in $diagnosticSettingsMgCategories) { + if ($entry.DiagnosticCategoriesHt.($logCategory)) { + @" + +"@ + } + else { + @' + +'@ + } + } + @' + +'@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYDiagnosticsManagementGroups) + [void]$htmlTenantSummary.AppendLine(@" + +
    Management Group NameManagement Group IdDiagnostic settingInheritanceInherited fromTargetTargetId$logCategory
    $($entry.ScopeName -replace '<', '<' -replace '>', '>')$($entry.ScopeId)$($entry.DiagnosticSettingName)$($entry.DiagnosticsInheritedOrnot)$($entry.DiagnosticsInheritedFrom)$($entry.DiagnosticTargetType)$($entry.DiagnosticTargetId)$($entry.DiagnosticCategoriesHt.($logCategory))n/a
    +
    + +"@) + } + 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(@" + +
    + Management Group Diagnostic Settings - Create Or Update - REST API docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYNoDiagnosticsManagementGroups = $null + $htmlSUMMARYNoDiagnosticsManagementGroups = foreach ($entry in $arrayMgsWithoutDiagnostics | Sort-Object -Property ScopeMgPath) { + + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNoDiagnosticsManagementGroups) + [void]$htmlTenantSummary.AppendLine(@" + +
    Management Group NameManagement Group IdManagement Group path
    $($entry.ScopeName -replace '<', '<' -replace '>', '>')$($entry.ScopeId)$($entry.ScopeMgPath)
    +
    + +"@) + } + 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(@" + +
    + Create diagnostic setting docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + + foreach ($logCategory in $diagnosticSettingsSubCategories) { + [void]$htmlTenantSummary.AppendLine(@" + +"@) + } + + [void]$htmlTenantSummary.AppendLine(@' + + + +'@) + $htmlSUMMARYDiagnosticsSubscriptions = $null + $htmlSUMMARYDiagnosticsSubscriptions = foreach ($entry in $diagnosticSettingsSub | Sort-Object -Property ScopeName, DiagnosticTargetType, DiagnosticSettingName) { + + @" + + + + + + + +"@ + foreach ($logCategory in $diagnosticSettingsSubCategories) { + if ($entry.DiagnosticCategoriesHt.($logCategory)) { + @" + +"@ + } + else { + @' + +'@ + } + } + @' + +'@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYDiagnosticsSubscriptions) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdPathDiagnostic settingTargetTargetId$logCategory
    $($entry.ScopeName -replace '<', '<' -replace '>', '>')$($entry.ScopeId) $($entry.ScopeMgPath)$($entry.DiagnosticSettingName)$($entry.DiagnosticTargetType)$($entry.DiagnosticTargetId)$($entry.DiagnosticCategoriesHt.($logCategory))n/a
    +
    + +"@) + } + else { + + [void]$htmlTenantSummary.AppendLine(@' +

    No Subscriptions configured for Diagnostic settings docs

    +'@) + } + + #hasNoDiag + if ($diagnosticSettingsSubNoDiagCount -gt 0) { + $tfCount = $diagnosticSettingsSubNoDiagCount + $htmlTableId = 'TenantSummary_NoDiagnosticsSubscriptions' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Create diagnostic setting docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYNoDiagnosticsSubscriptions = $null + $htmlSUMMARYNoDiagnosticsSubscriptions = foreach ($entry in $diagnosticSettingsSubNoDiag | Sort-Object -Property ScopeMgPath) { + + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNoDiagnosticsSubscriptions) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscription IdSubscription Mg path
    $($entry.ScopeName -replace '<', '<' -replace '>', '>')$($entry.ScopeId)$($entry.ScopeMgPath)
    +
    + +"@) + } + 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(@" + +
    + 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 + + + + + + + + + + + + +"@) + $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 + } + } + @" + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResourcesDiagnosticsCapable) + [void]$htmlTenantSummary.AppendLine(@" + +
    ResourceTypeResource CountDiagnostics capableMetricsLogsLogCategories
    $($resourceType.ResourceType)$($resourceType.ResourceCount)$diagnosticsCapable$($resourceType.Metrics)$($resourceType.Logs)$($resourceType.LogCategories -join "$CsvDelimiterOpposite ")
    +
    + +"@) + } + 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 = 'AzGovViz 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 + 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 | Measure-Object).count + + if ($diagnosticsPolicyAnalysisCount -gt 0) { + $tfCount = $diagnosticsPolicyAnalysisCount + + $htmlTableId = 'TenantSummary_DiagnosticsLifecycle' + [void]$htmlTenantSummary.AppendLine(@" +
    - Azure Policy Limits docs
    - Download CSV semicolon | comma - +Create Custom Policies for Azure ResourceTypes that support Diagnostics Logs and MetricsCreate-AzDiagPolicy
    +Supported categories for Azure Resource Logsdocs +
    - - - + + + + + + + + + + + + + "@) - $htmlSUMMARYMgsapproachingLimitsPolicySetScope = $null - $htmlSUMMARYMgsapproachingLimitsPolicySetScope = foreach ($mgApproachingLimitPolicySetScope in $mgsApproachingLimitPolicySetScope) { - @" - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicySetScope) - [void]$htmlTenantSummary.AppendLine(@" + + foreach ($diagnosticsFinding in $diagnosticsPolicyAnalysis | Sort-Object -property @{Expression = { $_.Priority } }, @{Expression = { $_.Recommendation } }, @{Expression = { $_.ResourceType } }, @{Expression = { $_.PolicyName } }, @{Expression = { $_.PolicyId } }) { + [void]$htmlTenantSummary.AppendLine(@" + + + + + + + + + + + + + + + +"@) + } + [void]$htmlTenantSummary.AppendLine(@"
    Management Group NameManagement Group IdLimitPriorityRecommendationResourceTypeResource CountDiagnostics capable (logs)Policy IdPolicy DisplayNameRole definitionsTargetLog Categories not covered by PolicyPolicy assignmentsPolicy used in PolicySetPolicySet assignments
    $($mgApproachingLimitPolicySetScope.MgName -replace "<", "<" -replace ">", ">")$($mgApproachingLimitPolicySetScope.MgId)$(($mgApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount/$LimitPOLICYPolicySetDefinitionsScopedManagementGroup).tostring("P")) ($($mgApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount)/$($LimitPOLICYPolicySetDefinitionsScopedManagementGroup))
    + $($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) +
    -
    - -"@) - } - 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(@" - -
    - Azure RBAC Limits docs
    - Download CSV semicolon | comma - - - - - - - - - -"@) - $htmlSUMMARYMgsapproachingLimitsRoleAssignment = $null - $htmlSUMMARYMgsapproachingLimitsRoleAssignment = foreach ($mgApproachingRoleAssignmentLimit in $mgsApproachingRoleAssignmentLimit) { - @" - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsRoleAssignment) - [void]$htmlTenantSummary.AppendLine(@" - -
    Management Group NameManagement Group IdLimit
    $($mgApproachingRoleAssignmentLimit.MgName -replace "<", "<" -replace ">", ">")$($mgApproachingRoleAssignmentLimit.MgId)$(($mgApproachingRoleAssignmentLimit.RoleAssignmentsCount/$LimitRBACRoleAssignmentsManagementGroup).tostring("P")) ($($mgApproachingRoleAssignmentLimit.RoleAssignmentsCount)/$($LimitRBACRoleAssignmentsManagementGroup))
    -
    - "@) - if ($tfCount -gt 10) { - $spectrum = "10, $tfCount" - if ($tfCount -gt 50) { - $spectrum = "10, 25, 50, $tfCount" - } - if ($tfCount -gt 100) { - $spectrum = "10, 30, 50, 100, $tfCount" - } - if ($tfCount -gt 500) { - $spectrum = "10, 30, 50, 100, 250, $tfCount" - } - if ($tfCount -gt 1000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, $tfCount" - } - if ($tfCount -gt 2000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, $tfCount" + } + else { + [void]$htmlTenantSummary.AppendLine(@' +

    No ResourceDiagnostics Policy Lifecycle recommendations

    +'@) + } + } + else { + [void]$htmlTenantSummary.AppendLine(@' +

    No ResourceDiagnostics Policy Lifecycle recommendations

    +'@) + } } - if ($tfCount -gt 3000) { - $spectrum = "10, 30, 50, 100, 250, 500, 750, 1000, 1500, 3000, $tfCount" + else { + [void]$htmlTenantSummary.AppendLine(@' +

    No ResourceDiagnostics Policy Lifecycle recommendations

    +'@) } - [void]$htmlTenantSummary.AppendLine(@" -paging: {results_per_page: ['Records: ', [$spectrum]]},/*state: {types: ['local_storage'], filters: true, page_number: true, page_length: true, sort: true},*/ -"@) + $endsumDiagLifecycle = Get-Date + Write-Host " Resource Diagnostics Policy Lifecycle processing duration: $((NEW-TIMESPAN -Start $startsumDiagLifecycle -End $endsumDiagLifecycle).TotalSeconds) seconds" } - [void]$htmlTenantSummary.AppendLine(@" -btn_reset: true, highlight_keywords: true, alternate_rows: true, auto_filter: { delay: 1100 }, no_results_message: true, - col_types: [ - 'caseinsensitivestring', - 'caseinsensitivestring', - 'caseinsensitivestring' - ], -extensions: [{ name: 'sort' }] - }; - var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); - tf.init();}} - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $(($mgApproachingRoleAssignmentLimit | measure-object).count) Management Groups approaching Limit ($LimitRBACRoleAssignmentsManagementGroup) for RoleAssignment docs

    -"@) + #endregion SUMMARYDiagnosticsPolicyLifecycle + + #endregion resources } - #endregion SUMMARYMgsapproachingLimitsRoleAssignment - #endregion tenantSummaryLimitsManagementGroups + [void]$htmlTenantSummary.AppendLine(@' +
    +'@) - #region tenantSummaryLimitsSubscriptions - [void]$htmlTenantSummary.AppendLine( @" -

    Subscriptions

    -"@) + #endregion tenantSummaryDiagnostics - #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(@" - -
    - Azure Subscription Resource Group Limit docs
    - Download CSV semicolon | comma - - - - - - - - - -"@) - $htmlSUMMARYSubsapproachingLimitsResourceGroups = $null - $htmlSUMMARYSubsapproachingLimitsResourceGroups = foreach ($subscriptionApproachingLimitFromResourceGroupsAll in $subscriptionsApproachingLimitFromResourceGroupsAll) { - $subscriptionData = $htSubDetails.($subscriptionApproachingLimitFromResourceGroupsAll.subscriptionId).details - @" - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsResourceGroups) - [void]$htmlTenantSummary.AppendLine(@" - -
    SubscriptionSubscriptionIdLimit
    $($subscriptionData.subscription -replace "<", "<" -replace ">", ">")$($subscriptionData.subscriptionId)$(($subscriptionApproachingLimitFromResourceGroupsAll.count_/$LimitResourceGroups).tostring("P")) ($($subscriptionApproachingLimitFromResourceGroupsAll.count_)/$($LimitResourceGroups))
    -
    - +

    PolicySet definitions: $tenantCustompolicySetsCount/$LimitPOLICYPolicySetDefinitionsScopedTenant docs

    "@) } else { [void]$htmlTenantSummary.AppendLine(@" - $(($subscriptionsApproachingLimitFromResourceGroupsAll | measure-object).count) Subscriptions approaching Limit ($LimitResourceGroups) for ResourceGroups docs

    +

    PolicySet definitions: $tenantCustompolicySetsCount/$LimitPOLICYPolicySetDefinitionsScopedTenant 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(@" - -
    - Azure Subscription Tag Limit docs
    - Download CSV semicolon | comma - - - - - - - - - -"@) - $htmlSUMMARYSubsapproachingLimitsSubscriptionTags = $null - $htmlSUMMARYSubsapproachingLimitsSubscriptionTags = foreach ($subscriptionApproachingLimitTags in $subscriptionsApproachingLimitTags) { - @" - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsSubscriptionTags) - [void]$htmlTenantSummary.AppendLine(@" - -
    SubscriptionSubscriptionIdLimit
    $($subscriptionApproachingLimitTags.subscription -replace "<", "<" -replace ">", ">")$($subscriptionApproachingLimitTags.subscriptionId)$(($subscriptionApproachingLimitTags.SubscriptionTagsCount/$LimitTagsSubscription).tostring("P")) ($($subscriptionApproachingLimitTags.SubscriptionTagsCount)/$($LimitTagsSubscription))
    -
    - +

    Custom Role definitions: $tenantCustomRolesCount/$LimitRBACCustomRoleDefinitionsTenant docs

    "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    $($subscriptionsApproachingLimitTags.count) Subscriptions approaching Limit ($LimitTagsSubscription) for Tags docs

    +

    Custom Role definitions: $tenantCustomRolesCount/$LimitRBACCustomRoleDefinitionsTenant 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" + #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(@" - +
    Azure Policy Limits docs
    Download CSV semicolon | comma - +
    - - + + "@) - $htmlSUMMARYSubsapproachingLimitsPolicyAssignments = $null - $htmlSUMMARYSubsapproachingLimitsPolicyAssignments = foreach ($subscriptionApproachingLimitPolicyAssignments in $subscriptionsApproachingLimitPolicyAssignments) { + $htmlSUMMARYMgsapproachingLimitsPolicyAssignments = $null + $htmlSUMMARYMgsapproachingLimitsPolicyAssignments = foreach ($mgApproachingLimitPolicyAssignments in $mgsApproachingLimitPolicyAssignments) { @" - - - + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicyAssignments) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicyAssignments) [void]$htmlTenantSummary.AppendLine(@"
    SubscriptionSubscriptionIdManagement Group NameManagement Group Id Limit
    $($subscriptionApproachingLimitPolicyAssignments.subscription -replace "<", "<" -replace ">", ">")$($subscriptionApproachingLimitPolicyAssignments.subscriptionId)$(($subscriptionApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount/$subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentLimit).tostring("P")) ($($subscriptionApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount)/$($subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentLimit)) ($($subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentAtScopeCount) Policy assignments, $($subscriptionApproachingLimitPolicyAssignments.PolicySetAssignmentAtScopeCount) PolicySet assignments)$($mgApproachingLimitPolicyAssignments.MgName -replace '<', '<' -replace '>', '>')$($mgApproachingLimitPolicyAssignments.MgId)$(($mgApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount/$LimitPOLICYPolicyAssignmentsManagementGroup).tostring('P')) ($($mgApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount)/$($LimitPOLICYPolicyAssignmentsManagementGroup)) ($($mgApproachingLimitPolicyAssignments.PolicyAssignmentAtScopeCount) Policy assignments, $($mgApproachingLimitPolicyAssignments.PolicySetAssignmentAtScopeCount) PolicySet assignments)
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    $(($subscriptionsApproachingLimitPolicyAssignments | measure-object).count) Subscriptions approaching Limit ($LimitPOLICYPolicyAssignmentsSubscription) for PolicyAssignment docs

    +

    $(($mgsApproachingLimitPolicyAssignments | measure-object).count) Management Groups approaching Limit ($LimitPOLICYPolicyAssignmentsManagementGroup) for PolicyAssignment docs

    "@) } - #endregion SUMMARYSubsapproachingLimitsPolicyAssignments + #endregion SUMMARYMgsapproachingLimitsPolicyAssignments - #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" + #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(@" - +
    Azure Policy Limits docs
    Download CSV semicolon | comma - - + + "@) - $htmlSUMMARYSubsapproachingLimitsPolicyScope = $null - $htmlSUMMARYSubsapproachingLimitsPolicyScope = foreach ($subscriptionApproachingLimitPolicyScope in $subscriptionsApproachingLimitPolicyScope) { + $htmlSUMMARYMgsapproachingLimitsPolicyScope = $null + $htmlSUMMARYMgsapproachingLimitsPolicyScope = foreach ($mgApproachingLimitPolicyScope in $mgsApproachingLimitPolicyScope) { @" - - - + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicyScope) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicyScope) [void]$htmlTenantSummary.AppendLine(@"
    SubscriptionSubscriptionIdManagement Group NameManagement Group Id Limit
    $($subscriptionApproachingLimitPolicyScope.subscription -replace "<", "<" -replace ">", ">")$($subscriptionApproachingLimitPolicyScope.subscriptionId)$(($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedCount/$subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedLimit).tostring("P")) ($($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedCount)/$($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedLimit))$($mgApproachingLimitPolicyScope.MgName -replace '<', '<' -replace '>', '>')$($mgApproachingLimitPolicyScope.MgId)$(($mgApproachingLimitPolicyScope.PolicyDefinitionsScopedCount/$LimitPOLICYPolicyDefinitionsScopedManagementGroup).tostring('P')) $($mgApproachingLimitPolicyScope.PolicyDefinitionsScopedCount)/$($LimitPOLICYPolicyDefinitionsScopedManagementGroup)
    @@ -17707,43 +17136,43 @@ extensions: [{ name: 'sort' }] } else { [void]$htmlTenantSummary.AppendLine(@" -

    $($subscriptionsApproachingLimitPolicyScope.count) Subscriptions approaching Limit ($LimitPOLICYPolicyDefinitionsScopedSubscription) for Policy Scope docs

    +

    $($mgsApproachingLimitPolicyScope.count) Management Groups approaching Limit ($LimitPOLICYPolicyDefinitionsScopedManagementGroup) for Policy Scope docs

    "@) } - #endregion SUMMARYSubsapproachingLimitsPolicyScope + #endregion SUMMARYMgsapproachingLimitsPolicyScope - #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" + #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(@" - +
    Azure Policy Limits docs
    Download CSV semicolon | comma - - + + "@) - $htmlSUMMARYSubsapproachingLimitsPolicySetScope = $null - $htmlSUMMARYSubsapproachingLimitsPolicySetScope = foreach ($subscriptionApproachingLimitPolicySetScope in $subscriptionsApproachingLimitPolicySetScope) { + $htmlSUMMARYMgsapproachingLimitsPolicySetScope = $null + $htmlSUMMARYMgsapproachingLimitsPolicySetScope = foreach ($mgApproachingLimitPolicySetScope in $mgsApproachingLimitPolicySetScope) { @" - - - + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicySetScope) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicySetScope) [void]$htmlTenantSummary.AppendLine(@"
    SubscriptionSubscriptionIdManagement Group NameManagement Group Id Limit
    $($subscriptionApproachingLimitPolicySetScope.subscription -replace "<", "<" -replace ">", ">")$($subscriptionApproachingLimitPolicySetScope.subscriptionId)$(($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount/$subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedLimit).tostring("P")) ($($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount)/$($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedLimit))$($mgApproachingLimitPolicySetScope.MgName -replace '<', '<' -replace '>', '>')$($mgApproachingLimitPolicySetScope.MgId)$(($mgApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount/$LimitPOLICYPolicySetDefinitionsScopedManagementGroup).tostring('P')) ($($mgApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount)/$($LimitPOLICYPolicySetDefinitionsScopedManagementGroup))
    @@ -17794,46 +17223,44 @@ extensions: [{ name: 'sort' }] } else { [void]$htmlTenantSummary.AppendLine(@" -

    $(($subscriptionsApproachingLimitPolicyScope | measure-object).count) Subscriptions approaching Limit ($LimitPOLICYPolicySetDefinitionsScopedSubscription) for PolicySet Scope docs

    +

    $(($mgsApproachingLimitPolicySetScope | measure-object).count) Management Groups approaching Limit ($LimitPOLICYPolicySetDefinitionsScopedManagementGroup) 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 + #endregion SUMMARYMgsapproachingLimitsPolicySetScope - $availableSubscriptionsRoleAssignmentLimits = ($htSubscriptionsRoleAssignmentLimit.values | Sort-Object -Unique) -join ' | ' + #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 (($subscriptionsApproachingRoleAssignmentLimit).count -gt 0) { - $tfCount = ($subscriptionsApproachingRoleAssignmentLimit).count - $htmlTableId = "TenantSummary_SubsapproachingLimitsRoleAssignment" + if (($mgsApproachingRoleAssignmentLimit).count -gt 0) { + $tfCount = ($mgsApproachingRoleAssignmentLimit).count + $htmlTableId = 'TenantSummary_MgsapproachingLimitsRoleAssignment' [void]$htmlTenantSummary.AppendLine(@" - +
    Azure RBAC Limits docs
    Download CSV semicolon | comma - - + + "@) - $htmlSUMMARYSubsapproachingLimitsRoleAssignment = $null - $htmlSUMMARYSubsapproachingLimitsRoleAssignment = foreach ($subscriptionApproachingRoleAssignmentLimit in $subscriptionsApproachingRoleAssignmentLimit) { + $htmlSUMMARYMgsapproachingLimitsRoleAssignment = $null + $htmlSUMMARYMgsapproachingLimitsRoleAssignment = foreach ($mgApproachingRoleAssignmentLimit in $mgsApproachingRoleAssignmentLimit) { @" - - - + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsRoleAssignment) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsRoleAssignment) [void]$htmlTenantSummary.AppendLine(@"
    SubscriptionSubscriptionIdManagement Group NameManagement Group Id Limit
    $($subscriptionApproachingRoleAssignmentLimit.subscription -replace "<", "<" -replace ">", ">")$($subscriptionApproachingRoleAssignmentLimit.subscriptionId)$(($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsCount/$subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsLimit).tostring("P")) ($($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsCount)/$($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsLimit))$($mgApproachingRoleAssignmentLimit.MgName -replace '<', '<' -replace '>', '>')$($mgApproachingRoleAssignmentLimit.MgId)$(($mgApproachingRoleAssignmentLimit.RoleAssignmentsCount/$LimitRBACRoleAssignmentsManagementGroup).tostring('P')) ($($mgApproachingRoleAssignmentLimit.RoleAssignmentsCount)/$($LimitRBACRoleAssignmentsManagementGroup))
    @@ -17884,64 +17311,60 @@ extensions: [{ name: 'sort' }] } else { [void]$htmlTenantSummary.AppendLine(@" - $(($subscriptionsApproachingRoleAssignmentLimit | measure-object).count) Subscriptions approaching Limit ($availableSubscriptionsRoleAssignmentLimits) for RoleAssignment docs

    +

    $(($mgApproachingRoleAssignmentLimit | measure-object).count) Management Groups approaching Limit ($LimitRBACRoleAssignmentsManagementGroup) for RoleAssignment docs

    "@) } - #endregion SUMMARYSubsapproachingLimitsRoleAssignment - - #endregion tenantSummaryLimitsSubscriptions - - [void]$htmlTenantSummary.AppendLine(@" -
    -"@) - #endregion tenantSummaryLimits - - #region tenantSummaryAAD - [void]$htmlTenantSummary.AppendLine(@" - -
    - Check out AzADServicePrincipalInsights POC GitHub
    - Demystifying Service Principals - Managed Identities devBlogs
    - John Savill - Azure AD App Registrations, Enterprise Apps and Service Principals YouTube
    -"@) + #endregion SUMMARYMgsapproachingLimitsRoleAssignment - #region AADSPNotFound - Write-Host " processing TenantSummary AAD ServicePrincipals - not found" + #endregion tenantSummaryLimitsManagementGroups - if ($servicePrincipalRequestResourceNotFoundCount -gt 0) { - $tfCount = $servicePrincipalRequestResourceNotFoundCount - $htmlTableId = "TenantSummary_AADSPNotFound" + #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(@" - +
    - +Azure Subscription Resource Group Limitdocs
    + Download CSV semicolon | comma +
    - + + + "@) - $htmlSUMMARYAADSPNotFound = $null - $htmlSUMMARYAADSPNotFound = foreach ($serviceprincipal in $arrayServicePrincipalRequestResourceNotFound | Sort-Object) { - + $htmlSUMMARYSubsapproachingLimitsResourceGroups = $null + $htmlSUMMARYSubsapproachingLimitsResourceGroups = foreach ($subscriptionApproachingLimitFromResourceGroupsAll in $subscriptionsApproachingLimitFromResourceGroupsAll) { + $subscriptionData = $htSubDetails.($subscriptionApproachingLimitFromResourceGroupsAll.subscriptionId).details @" - + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPNotFound) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsResourceGroups) [void]$htmlTenantSummary.AppendLine(@" - -
    Service Principal Object IdSubscriptionSubscriptionIdLimit
    $($serviceprincipal)$($subscriptionData.subscription -replace '<', '<' -replace '>', '>')$($subscriptionData.subscriptionId)$(($subscriptionApproachingLimitFromResourceGroupsAll.count_/$LimitResourceGroups).tostring('P')) ($($subscriptionApproachingLimitFromResourceGroupsAll.count_)/$($LimitResourceGroups))
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    No ServicePrincipals where the API returned 'Request_ResourceNotFound'

    + $(($subscriptionsApproachingLimitFromResourceGroupsAll | measure-object).count) Subscriptions approaching Limit ($LimitResourceGroups) for ResourceGroups docs

    "@) } - #endregion AADSPNotFound - - #region AADAppNotFound - Write-Host " processing TenantSummary AAD Applications - not found" - - if ($applicationRequestResourceNotFoundCount -gt 0) { - $tfCount = $applicationRequestResourceNotFoundCount - $htmlTableId = "TenantSummary_AADAppNotFound" + #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(@" - +
    + Azure Subscription Tag Limit docs
    + Download CSV semicolon | comma - + + + "@) - $htmlSUMMARYAADAppNotFound = $null - $htmlSUMMARYAADAppNotFound = foreach ($app in $arrayApplicationRequestResourceNotFound | Sort-Object) { - + $htmlSUMMARYSubsapproachingLimitsSubscriptionTags = $null + $htmlSUMMARYSubsapproachingLimitsSubscriptionTags = foreach ($subscriptionApproachingLimitTags in $subscriptionsApproachingLimitTags) { @" - + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADAppNotFound) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsSubscriptionTags) [void]$htmlTenantSummary.AppendLine(@" - -
    Application (Client) IdSubscriptionSubscriptionIdLimit
    $($app)$($subscriptionApproachingLimitTags.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingLimitTags.subscriptionId)$(($subscriptionApproachingLimitTags.SubscriptionTagsCount/$LimitTagsSubscription).tostring('P')) ($($subscriptionApproachingLimitTags.SubscriptionTagsCount)/$($LimitTagsSubscription))
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } 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 - } +

    $($subscriptionsApproachingLimitTags.count) Subscriptions approaching Limit ($LimitTagsSubscription) for Tags docs

    +"@) + } + #endregion SUMMARYSubsapproachingLimitsSubscriptionTags - $abbr = " " - $abbrOrphanedSPMI = " " + #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(@" - +
    + Azure Policy Limits docs
    Download CSV semicolon | comma - - - - - - - - - - + + "@) - $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 ($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 ($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" - } - } - + $htmlSUMMARYSubsapproachingLimitsPolicyAssignments = $null + $htmlSUMMARYSubsapproachingLimitsPolicyAssignments = foreach ($subscriptionApproachingLimitPolicyAssignments in $subscriptionsApproachingLimitPolicyAssignments) { @" - - - - - - - - - - + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPManagedIdentities) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicyAssignments) [void]$htmlTenantSummary.AppendLine(@" - -
    ApplicationIdDisplayNameSP ObjectIdTypeUsageUsage infoPolicy assignment detailsRole assignmentsAssigned to resources$($abbr)Orphaned$($abbrOrphanedSPMI) +SubscriptionSubscriptionIdLimit
    $($serviceprincipalMIDetailed.appId)$($serviceprincipalMIDetailed.displayName)$($serviceprincipalMI)$miType$miResourceType$($serviceprincipalMIDetailed.alternativeNames -join ", ")$($policyAssignmentMoreInfo)$($miRoleAssignments)$userMiAssignedToResourcesCount$orphanedMI$($subscriptionApproachingLimitPolicyAssignments.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingLimitPolicyAssignments.subscriptionId)$(($subscriptionApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount/$subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentLimit).tostring('P')) ($($subscriptionApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount)/$($subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentLimit)) ($($subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentAtScopeCount) Policy assignments, $($subscriptionApproachingLimitPolicyAssignments.PolicySetAssignmentAtScopeCount) PolicySet assignments)
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    $servicePrincipalsOfTypeManagedIdentityCount AAD ServicePrincipals type=ManagedIdentity

    +

    $(($subscriptionsApproachingLimitPolicyAssignments | measure-object).count) Subscriptions approaching Limit ($LimitPOLICYPolicyAssignmentsSubscription) for PolicyAssignment docs

    "@) } + #endregion SUMMARYSubsapproachingLimitsPolicyAssignments - $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(@" - + #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(@" +
    + Azure Policy Limits docs
    Download CSV semicolon | comma - - - - - - - - - - - - - - - + + + "@) - $htmlSUMMARYAADSPCredExpiry = $null - $htmlSUMMARYAADSPCredExpiry = foreach ($serviceprincipalApp in $servicePrincipalsOfTypeApplication | Sort-Object) { - @" + $htmlSUMMARYSubsapproachingLimitsPolicyScope = $null + $htmlSUMMARYSubsapproachingLimitsPolicyScope = foreach ($subscriptionApproachingLimitPolicyScope in $subscriptionsApproachingLimitPolicyScope) { + @" - - - - - -"@ - if ($htAppDetails.$serviceprincipalApp.appPasswordCredentialsCount) { - @" - - - - - -"@ - } - else { - @" - - - - - -"@ - } - - if ($htAppDetails.$serviceprincipalApp.appKeyCredentialsCount) { - @" - - - - - -"@ - } - else { - @" - - - - - -"@ - } - - @" + + + "@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicyScope) + [void]$htmlTenantSummary.AppendLine(@" + +
    ApplicationIdDisplayNameNotesSP ObjectIdApp ObjectIdSecretsSecrets expiredSecrets expiry
    <$($AADServicePrincipalExpiryWarningDays)d
    Secrets expiry
    >$($AADServicePrincipalExpiryWarningDays)d & <2y
    Secrets expiry
    >2y
    CertsCerts expiredCerts expiry
    <$($AADServicePrincipalExpiryWarningDays)d
    Certs expiry
    >$($AADServicePrincipalExpiryWarningDays)d & <2y
    Certs expiry
    >2y
    SubscriptionSubscriptionIdLimit
    $($htAppDetails.$serviceprincipalApp.spGraphDetails.appId)$($htAppDetails.$serviceprincipalApp.spGraphDetails.displayName)$($htAppDetails.$serviceprincipalApp.spGraphDetails.notes)$($htAppDetails.$serviceprincipalApp.spGraphDetails.Id)$($htAppDetails.$serviceprincipalApp.appGraphDetails.Id)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiredCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsGracePeriodExpiryCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiryOKCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiryOKMoreThan2YearsCount)00000$($htAppDetails.$serviceprincipalApp.appKeyCredentialsCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiredCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsGracePeriodExpiryCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiryOKCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiryOKMoreThan2YearsCount)00000$($subscriptionApproachingLimitPolicyScope.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingLimitPolicyScope.subscriptionId)$(($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedCount/$subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedLimit).tostring('P')) ($($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedCount)/$($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedLimit))
    +
    + -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    $servicePrincipalsOfTypeApplicationCount AAD ServicePrincipals type=Application

    + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) - } - - $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

    +

    $($subscriptionsApproachingLimitPolicyScope.count) Subscriptions approaching Limit ($LimitPOLICYPolicyDefinitionsScopedSubscription) for Policy Scope docs

    "@) } - #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 $checkContext.Tenant.Id } ) - $appsWithOtherOrgIdCount = ($appsWithOtherOrgId).Count - - if ($appsWithOtherOrgIdCount -gt 0) { - $tfCount = $appsWithOtherOrgIdCount - $htmlTableId = "TenantSummary_AADSPExternal" + #endregion SUMMARYSubsapproachingLimitsPolicyScope - if ($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - $abbr = " " - } - else { - $abbr = "" - } + #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(@" - +
    + Azure Policy Limits docs
    Download CSV semicolon | comma - - - - - + + + "@) - $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 ", "))" - } - + $htmlSUMMARYSubsapproachingLimitsPolicySetScope = $null + $htmlSUMMARYSubsapproachingLimitsPolicySetScope = foreach ($subscriptionApproachingLimitPolicySetScope in $subscriptionsApproachingLimitPolicySetScope) { @" - - - - - + + + -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPExternal) - [void]$htmlTenantSummary.AppendLine(@" - -
    ApplicationIdDisplayNameSP ObjectIdOrganizationIdRole assignments$($abbr)SubscriptionSubscriptionIdLimit
    $($htServicePrincipals.($serviceprincipalApp).appId)$($htServicePrincipals.($serviceprincipalApp).displayName)$($htServicePrincipals.($serviceprincipalApp).id)$($htServicePrincipals.($serviceprincipalApp).appOwnerOrganizationId)$roleAssignments4ExternalApp$($subscriptionApproachingLimitPolicySetScope.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingLimitPolicySetScope.subscriptionId)$(($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount/$subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedLimit).tostring('P')) ($($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount)/$($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedLimit))
    -
    - + }; + var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); + tf.init();}} + "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    $appsWithOtherOrgIdCount External (appOwnerOrganizationId) AAD ServicePrincipals type=Application

    +

    $(($subscriptionsApproachingLimitPolicyScope | measure-object).count) Subscriptions approaching Limit ($LimitPOLICYPolicySetDefinitionsScopedSubscription) for PolicySet Scope docs

    "@) } + #endregion SUMMARYSubsapproachingLimitsPolicySetScope - $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 - - #region tenantSummaryConsumption - [void]$htmlTenantSummary.AppendLine(@" - -
    - Customize your Azure environment optimizations (Cost, Reliability & more) with Azure Optimization Engine (AOE) -"@) + #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 - if ($htParameters.DoAzureConsumption -eq $true) { - $startConsumption = Get-Date - Write-Host " processing TenantSummary Consumption" + $availableSubscriptionsRoleAssignmentLimits = ($htSubscriptionsRoleAssignmentLimit.values | Sort-Object -Unique) -join ' | ' - if (($arrayConsumptionData | Measure-Object).Count -gt 0) { - $tfCount = ($arrayConsumptionData | Measure-Object).Count - $htmlTableId = "TenantSummary_Consumption" - [void]$htmlTenantSummary.AppendLine(@" - + if (($subscriptionsApproachingRoleAssignmentLimit).count -gt 0) { + $tfCount = ($subscriptionsApproachingRoleAssignmentLimit).count + $htmlTableId = 'TenantSummary_SubsapproachingLimitsRoleAssignment' + [void]$htmlTenantSummary.AppendLine(@" +
    + Azure RBAC Limits docs
    Download CSV semicolon | comma - +
    - - - - - - - + + + "@) - $htmlSUMMARYConsumption = $null - $htmlSUMMARYConsumption = foreach ($consumptionLine in $arrayConsumptionData) { - @" + $htmlSUMMARYSubsapproachingLimitsRoleAssignment = $null + $htmlSUMMARYSubsapproachingLimitsRoleAssignment = foreach ($subscriptionApproachingRoleAssignmentLimit in $subscriptionsApproachingRoleAssignmentLimit) { + @" - - - - - - - - -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYConsumption) - [void]$htmlTenantSummary.AppendLine(@" - -
    ChargeTypeResourceTypeCategoryResourceCountCost ($($AzureConsumptionPeriod)d)CurrencySubscriptionsSubscriptionSubscriptionIdLimit
    $($consumptionLine.ConsumedServiceChargeType)$($consumptionLine.ConsumedService)$($consumptionLine.ConsumedServiceCategory)$($consumptionLine.ConsumedServiceInstanceCount)$($consumptionLine.ConsumedServiceCost)$($consumptionLine.ConsumedServiceCurrency)$($consumptionLine.ConsumedServiceSubscriptions)
    -
    - -"@) - } - 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 - - #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 $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 $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 $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( { $_.TenOrMgOrSubOrRGOrRes -eq "MG" -or $_.TenOrMgOrSubOrRGOrRes -eq "Ten" }) - $roleAssignmentsCreatedMgCount = $roleAssignmentsCreatedMg.Count - #rbac assignments createdSub - $roleAssignmentsCreatedSub = $roleAssignmentsCreatedUnique.where( { $_.TenOrMgOrSubOrRGOrRes -eq "Sub" }) - $roleAssignmentsCreatedSubCount = $roleAssignmentsCreatedSub.Count - if (-not $htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - $roleAssignmentsCreatedSubRg = $roleAssignmentsCreatedUnique.where( { $_.TenOrMgOrSubOrRGOrRes -eq "RG" }) - $roleAssignmentsCreatedSubRgCount = $roleAssignmentsCreatedSubRg.Count - $roleAssignmentsCreatedSubRgRes = $roleAssignmentsCreatedUnique.where( { $_.TenOrMgOrSubOrRGOrRes -eq "Res" }) - $roleAssignmentsCreatedSubRgResCount = $roleAssignmentsCreatedSubRgRes.Count - } - - if ($customRoleDefinitionsCreatedOrUpdatedCount -gt 0 -or $roleAssignmentsCreatedCount -gt 0) { - $ctContenIndicatorRBAC = "ctContenRBACTrue" - if (-not $htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - $rbacAssignmentSummaryCt = "(Mg: $roleAssignmentsCreatedMgCount; Sub: $roleAssignmentsCreatedSubCount; RG: $roleAssignmentsCreatedSubRgCount; Res: $roleAssignmentsCreatedSubRgResCount)" +$($subscriptionApproachingRoleAssignmentLimit.subscription -replace '<', '<' -replace '>', '>') +$($subscriptionApproachingRoleAssignmentLimit.subscriptionId) +$(($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsCount/$subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsLimit).tostring('P')) ($($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsCount)/$($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsLimit)) + +"@ } - else { - $rbacAssignmentSummaryCt = "(Mg: $roleAssignmentsCreatedMgCount; Sub: $roleAssignmentsCreatedSubCount)" + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsRoleAssignment) + [void]$htmlTenantSummary.AppendLine(@" + + +
    + +"@) } else { - $ctContenIndicatorRBAC = "ctContenRBACFalse" - $rbacAssignmentSummaryCt = "" + [void]$htmlTenantSummary.AppendLine(@" + $(($subscriptionsApproachingRoleAssignmentLimit | measure-object).count) Subscriptions approaching Limit ($availableSubscriptionsRoleAssignmentLimits) for RoleAssignment docs

    +"@) } - #endregion ctrbacassignments - - - if ($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 + #endregion SUMMARYSubsapproachingLimitsRoleAssignment - $resourcesCreated = $resourcesCreatedOrChanged.where( { $_.createdTime -gt $xdaysAgo }) - $resourcesCreatedCount = $resourcesCreated.Count - $resourcesChanged = $resourcesCreatedOrChanged.where( { $_.changedTime -gt $xdaysAgo }) - $resourcesChangedCount = $resourcesChanged.Count + #endregion tenantSummaryLimitsSubscriptions - 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(@' +
    +'@) + #endregion tenantSummaryLimits + showMemoryUsage + #region tenantSummaryAAD + [void]$htmlTenantSummary.AppendLine(@' + +
    + Check out AzADServicePrincipalInsights POC GitHub
    + Demystifying Service Principals - Managed Identities devBlogs
    + John Savill - Azure AD App Registrations, Enterprise Apps and Service Principals YouTube
    +'@) - [void]$htmlTenantSummary.AppendLine(@" - -
    -"@) + #region AADSPNotFound + Write-Host ' processing TenantSummary AAD ServicePrincipals - not found' - #region ctpolicy - [void]$htmlTenantSummary.AppendLine(@" - -
    -"@) + if ($servicePrincipalRequestResourceNotFoundCount -gt 0) { + $tfCount = $servicePrincipalRequestResourceNotFoundCount + $htmlTableId = 'TenantSummary_AADSPNotFound' - #region ChangeTrackingCustomPolicy - if ($customPolicyCreatedOrUpdatedCount -gt 0) { - $tfCount = $customPolicyCreatedOrUpdatedCount - $htmlTableId = "TenantSummary_ChangeTrackingCustomPolicy" [void]$htmlTenantSummary.AppendLine(@" - +
    - Download CSV semicolon | comma - - - - - - - - - - - - - - + "@) - $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) - } + $htmlSUMMARYAADSPNotFound = $null + $htmlSUMMARYAADSPNotFound = foreach ($serviceprincipal in $arrayServicePrincipalRequestResourceNotFound | Sort-Object) { @" - - - - - - - - - - - - - - + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomPolicy) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPNotFound) [void]$htmlTenantSummary.AppendLine(@" - +
    ScopeScope IdPolicy DisplayNamePolicyIdCategoryEffectRole definitionsUnique assignmentsUsed in PolicySetsCreated/UpdatedCreatedOnCreatedByUpdatedOnUpdatedByService Principal Object Id
    $($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)$($serviceprincipal)
    @@ -19600,245 +18342,272 @@ tf.init();}} } else { [void]$htmlTenantSummary.AppendLine(@" -

    $policyAssignmentsCreatedOrUpdatedCount Created/Updated Policy assignments

    +

    $servicePrincipalsOfTypeManagedIdentityCount AAD ServicePrincipals type=ManagedIdentity

    "@) } - #endregion ChangeTrackingPolicyAssignments - [void]$htmlTenantSummary.AppendLine(@" -
    -"@) + $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 - #endregion ctpolicy + #region AADSPCredExpiry + if (-not $skipApplications) { + $startAADSPCredExpiryLoop = Get-Date + Write-Host ' processing TenantSummary AAD SP Apps CredExpiry' - #region ctrbac - [void]$htmlTenantSummary.AppendLine(@" - -
    -"@) + $servicePrincipalsOfTypeApplicationCount = ($servicePrincipalsOfTypeApplication).Count - #region ChangeTrackingCustomRoles - if ($customRoleDefinitionsCreatedOrUpdatedCount -gt 0) { - $tfCount = $customRoleDefinitionsCreatedOrUpdatedCount - $htmlTableId = "TenantSummary_ChangeTrackingCustomRoles" - [void]$htmlTenantSummary.AppendLine(@" - + 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(@" +
    - Download CSV semicolon | comma + Download CSV semicolon | comma - - - - - - - - - - - - -"@) - $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" + + + + + + + + + + + + + + + + + + +"@) + $htmlSUMMARYAADSPCredExpiry = $null + $htmlSUMMARYAADSPCredExpiry = foreach ($serviceprincipalApp in $servicePrincipalsOfTypeApplication | Sort-Object) { + @" + + + + + + +"@ + if ($htAppDetails.$serviceprincipalApp.appPasswordCredentialsCount) { + @" + + + + + +"@ } else { - $createdOnUpdatedOn = "Updated" + @' + + + + + +'@ } - $updatedOnFormated = $updatedOn - $updatedByRemoveNoiseOrNot = $entry.Json.properties.updatedBy - if ($htIdentitiesWithRoleAssignmentsUnique.($updatedByRemoveNoiseOrNot)) { - $updatedByRemoveNoiseOrNot = $htIdentitiesWithRoleAssignmentsUnique.($updatedByRemoveNoiseOrNot).details + + if ($htAppDetails.$serviceprincipalApp.appKeyCredentialsCount) { + @" + + + + + +"@ + } + else { + @' + + + + + +'@ } - } - @" - - - - - - - - - - + @' -"@ - } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomRoles) - [void]$htmlTenantSummary.AppendLine(@" - +'@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPCredExpiry) + [void]$htmlTenantSummary.AppendLine(@" +
    Role NameRoleIdAssignable ScopesDataCreated/UpdatedCreatedOnCreatedByUpdatedOnUpdatedBy
    ApplicationIdDisplayNameNotesSP ObjectIdApp ObjectIdSecretsSecrets expiredSecrets expiry
    <$($AADServicePrincipalExpiryWarningDays)d
    Secrets expiry
    >$($AADServicePrincipalExpiryWarningDays)d & <2y
    Secrets expiry
    >2y
    CertsCerts expiredCerts expiry
    <$($AADServicePrincipalExpiryWarningDays)d
    Certs expiry
    >$($AADServicePrincipalExpiryWarningDays)d & <2y
    Certs expiry
    >2y
    $($htAppDetails.$serviceprincipalApp.spGraphDetails.appId)$($htAppDetails.$serviceprincipalApp.spGraphDetails.displayName)$($htAppDetails.$serviceprincipalApp.spGraphDetails.notes)$($htAppDetails.$serviceprincipalApp.spGraphDetails.Id)$($htAppDetails.$serviceprincipalApp.appGraphDetails.Id)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiredCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsGracePeriodExpiryCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiryOKCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiryOKMoreThan2YearsCount)00000$($htAppDetails.$serviceprincipalApp.appKeyCredentialsCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiredCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsGracePeriodExpiryCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiryOKCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiryOKMoreThan2YearsCount)00000
    $($entry.Name -replace "<", "<" -replace ">", ">")$($entry.Id)$(($entry.AssignableScopes | Measure-Object).count) ($($entry.AssignableScopes -join "$CsvDelimiterOpposite "))$($roleManageData)$createdOnUpdatedOn$createdOnFormated$createdBy$updatedOnFormated$updatedByRemoveNoiseOrNot
    "@) + } + 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(@" -

    $customRoleDefinitionsCreatedOrUpdatedCount Created/Updated custom Role definitions

    -"@) + [void]$htmlTenantSummary.AppendLine(@' +

    No information on AAD ServicePrincipals type=Application as Guest account does not have enough permissions

    +'@) } - #endregion ChangeTrackingCustomRoles + #endregion AADSPCredExpiry - #region ChangeTrackingRoleAssignments - if ($roleAssignmentsCreatedCount -gt 0) { - $tfCount = $roleAssignmentsCreatedCount - $htmlTableId = "TenantSummary_ChangeTrackingRoleAssignments" + #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(@" - +
    - Download CSV semicolon | comma + Download CSV semicolon | comma - - - - - - - - - - - - - - - - + + + + + "@) - $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 + $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 } - [void]$htmlSUMMARYChangeTrackingRoleAssignments.AppendFormat( - @' - - - - - - - - - - - - - - - - - + $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 ', '))" + } + + @" + + + + + + -'@, $entry.TenOrMgOrSubOrRGOrRes, - $roleName, - $entry.RoleId, - $entry.RoleType, - $entry.RoleDataRelated, - $entry.ObjectDisplayName, - $entry.ObjectSignInName, - $entry.ObjectId, - $entry.ObjectType, - $entry.AssignmentType, - $entry.AssignmentInheritFrom, - $entry.GroupMembersCount, - $entry.RoleAssignmentId, - ($entry.RbacRelatedPolicyAssignment -replace "<", "<" -replace ">", ">"), - $entry.CreatedOn, - $entry.CreatedBy - ) +"@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingRoleAssignments) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPExternal) [void]$htmlTenantSummary.AppendLine(@" - +
    ScopeRoleRole IdRole TypeDataIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsRole AssignmentIdRelated Policy Assignment $noteOrNotCreatedOnCreatedByApplicationIdDisplayNameSP ObjectIdOrganizationIdRole assignments$($abbr)
    {0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}
    $($htServicePrincipals.($serviceprincipalApp).appId)$($htServicePrincipals.($serviceprincipalApp).displayName)$($htServicePrincipals.($serviceprincipalApp).id)$($htServicePrincipals.($serviceprincipalApp).appOwnerOrganizationId)$roleAssignments4ExternalApp
    - "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    Policy $($namingPolicyCount) Naming findings

    +

    $customPolicyCreatedOrUpdatedCount Created/Updated custom Policy definitions

    "@) } + #endregion ChangeTrackingCustomPolicy - $namingPolicySetCount = $htNamingValidation.PolicySet.values.count - if ($namingPolicySetCount -gt 0) { - $tfCount = $namingPolicySetCount - $htmlTableId = "TenantSummary_NamingPolicySet" + #region ChangeTrackingCustomPolicySet + if ($customPolicySetCreatedOrUpdatedCount -gt 0) { + $tfCount = $customPolicySetCreatedOrUpdatedCount + $htmlTableId = 'TenantSummary_ChangeTrackingCustomPolicySet' [void]$htmlTenantSummary.AppendLine(@" - +
    - Download CSV semicolon | comma + Download CSV semicolon | comma - - - - - + + + + + + + + + + + + "@) - $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 ">", ">" + $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 { - $name = "" - $nameInvalidChars = "" + $createdOn = '' } - if ($htNamingValidation.PolicySet.($key).displayName) { - $displayName = $htNamingValidation.PolicySet.($key).displayName -replace "<", "<" -replace ">", ">" - $displayNameInvalidChars = $htNamingValidation.PolicySet.($key).displayNameInvalidChars -replace "<", "<" -replace ">", ">" + $updatedOnGt = $false + if ($entry.updatedOn -ne '') { + $updatedOn = ($entry.UpdatedOn) + if ([datetime]($entry.UpdatedOn) -gt $xdaysAgo) { + $updatedOnGt = $true + } + $updatedOnGt = $true } else { - $displayName = "" - $displayNameInvalidChars = "" + $updatedOn = '' } + $createOnUpdatedOn = $null + if ($createdOnGt) { + $createOnUpdatedOn = 'Created' + } + if ($updatedOnGt) { + $createOnUpdatedOn = 'Updated' + } + if ($createdOnGt -and $updatedOnGt) { + $createOnUpdatedOn = 'Created&Updated' + } @" - - - - - + + + + + + + + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicySet) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomPolicySet) [void]$htmlTenantSummary.AppendLine(@" - -
    IdNameName Invalid charsDisplayNameDisplayName Invalid charsScopeScopeIdPolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies used in PolicySetCreated/UpdatedCreatedOnCreatedByUpdatedOnUpdatedBy
    $($id)$($name)$($nameInvalidChars)$($displayName)$($displayNameInvalidChars)$($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)
    + +
    - "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    PolicySet $($namingPolicySetCount) Naming findings

    +

    $customPolicySetCreatedOrUpdatedCount Created/Updated custom PolicySet definitions

    "@) } + #endregion ChangeTrackingCustomPolicySet - $namingPolicyAssignmentCount = $htNamingValidation.PolicyAssignment.values.count - if ($namingPolicyAssignmentCount -gt 0) { - $tfCount = $namingPolicyAssignmentCount - $htmlTableId = "TenantSummary_NamingPolicyAssignment" + #region ChangeTrackingPolicyAssignments + if ($policyAssignmentsCreatedOrUpdatedCount -gt 0) { + $tfCount = $policyAssignmentsCreatedOrUpdatedCount + $htmlTableId = 'TenantSummary_ChangeTrackingPolicyAssignments' [void]$htmlTenantSummary.AppendLine(@" - +
    - Download CSV semicolon | comma + Download CSV semicolon | comma - - - - - + + + + + + + + + + + + + + + + + + +"@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + + + + + +'@) + } + + [void]$htmlTenantSummary.AppendLine(@" + + + + + + + + + + "@) - $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 ">", ">" + $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 { - $name = "" - $nameInvalidChars = "" + $createdOn = '' } - if ($htNamingValidation.PolicyAssignment.($key).displayName) { - $displayName = $htNamingValidation.PolicyAssignment.($key).displayName -replace "<", "<" -replace ">", ">" - $displayNameInvalidChars = $htNamingValidation.PolicyAssignment.($key).displayNameInvalidChars -replace "<", "<" -replace ">", ">" + $updatedOnGt = $false + if ($policyAssignment.updatedOn -ne '') { + $updatedOn = ($policyAssignment.UpdatedOn) + if ([datetime]($policyAssignment.UpdatedOn) -gt $xdaysAgo) { + $updatedOnGt = $true + } + $updatedOnGt = $true } else { - $displayName = "" - $displayNameInvalidChars = "" + $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 + } @" - - - - - + + + + + + + + + + + + + + + + + + +"@ + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + @" + + + + + +"@ + } + + @" + + + + + + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicyAssignment) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingPolicyAssignments) [void]$htmlTenantSummary.AppendLine(@" - -
    IdNameName Invalid charsDisplayNameDisplayName Invalid charsScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameInheritanceScopeExcludedExemption appliesPolicy/Set DisplayNamePolicy/Set DescriptionPolicy/SetIdPolicy/SetTypeCategoryEffectParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotAssignment DisplayNameAssignment DescriptionAssignmentIdCreated/UpdatedAssignedByCreatedOnCreatedByUpdatedOnUpdatedBy
    $($id)$($name)$($nameInvalidChars)$($displayName)$($displayNameInvalidChars)$($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)$($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)
    + +
    - -"@) - } - else { - [void]$htmlTenantSummary.AppendLine(@" -

    Policy assignment $($namingPolicyAssignmentCount) Naming findings

    -"@) - } +linked_filters: true, +col_0: 'select', + col_6: 'select', + col_7: 'select', + col_11: 'select', + col_12: 'select', + col_14: 'select', + col_16: 'select', +'@) + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + col_27: 'multiple', +'@) + } + else { + [void]$htmlTenantSummary.AppendLine(@' + col_22: 'multiple', +'@) + } + [void]$htmlTenantSummary.AppendLine(@' + locale: 'en-US', + col_types: [ + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', +'@) - $namingManagementGroupCount = $htNamingValidation.ManagementGroup.values.count - if ($namingManagementGroupCount -gt 0) { - $tfCount = $namingManagementGroupCount - $htmlTableId = "TenantSummary_NamingManagementGroup" - [void]$htmlTenantSummary.AppendLine(@" - -
    - Download CSV semicolon | comma - - - - - - - - - -"@) - $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 = "" - } + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + 'number', + 'number', + 'number', + 'number', + 'number', +'@) + } - @" - - - - - -"@ + [void]$htmlTenantSummary.AppendLine(@' + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'caseinsensitivestring', + 'date', + 'caseinsensitivestring', + 'date', + 'caseinsensitivestring' + ], +'@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + watermark: ['', '', '', 'try [nonempty]', '', 'thisScope', '', '', '', '', '', '','', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], +'@) } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingManagementGroup) - [void]$htmlTenantSummary.AppendLine(@" - -
    IdNameName Invalid chars
    $($id)$($name)$($nameInvalidChars)
    -
    - - "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    Management Group $($namingManagementGroupCount) Naming findings

    +

    $policyAssignmentsCreatedOrUpdatedCount Created/Updated Policy assignments

    "@) } + #endregion ChangeTrackingPolicyAssignments + [void]$htmlTenantSummary.AppendLine(@' +
    +'@) - $namingSubscriptionCount = $htNamingValidation.Subscription.values.count - if ($namingSubscriptionCount -gt 0) { - $tfCount = $namingSubscriptionCount - $htmlTableId = "TenantSummary_NamingSubscription" + #endregion ctpolicy + + #region ctrbac + [void]$htmlTenantSummary.AppendLine(@" + +
    +"@) + + #region ChangeTrackingCustomRoles + if ($customRoleDefinitionsCreatedOrUpdatedCount -gt 0) { + $tfCount = $customRoleDefinitionsCreatedOrUpdatedCount + $htmlTableId = 'TenantSummary_ChangeTrackingCustomRoles' [void]$htmlTenantSummary.AppendLine(@" - +
    - Download CSV semicolon | comma + Download CSV semicolon | comma - - - + + + + + + + + + "@) - $htmlSUMMARYNamingSubscription = $null - $htmlSUMMARYNamingSubscription = foreach ($key in $htNamingValidation.Subscription.Keys | Sort-Object) { + $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 + } - if ($htNamingValidation.Subscription.($key).displayName) { - $displayName = $htNamingValidation.Subscription.($key).displayName -replace "<", "<" -replace ">", ">" - $displayNameInvalidChars = $htNamingValidation.Subscription.($key).displayNameInvalidChars -replace "<", "<" -replace ">", ">" + $createdOn = $entry.Json.properties.createdOn + $createdOnFormated = $createdOn + $createdOnUpdatedOn = 'Created' + + $updatedOn = $entry.Json.properties.updatedOn + if ($updatedOn -eq $createdOn) { + $updatedOnFormated = '' + $updatedByRemoveNoiseOrNot = '' } else { - $displayName = "" - $displayNameInvalidChars = "" + if ($createdOn -gt $xdaysAgo) { + $createdOnUpdatedOn = 'Created&Updated' + } + else { + $createdOnUpdatedOn = 'Updated' + } + $updatedOnFormated = $updatedOn + $updatedByRemoveNoiseOrNot = $entry.Json.properties.updatedBy + if ($htIdentitiesWithRoleAssignmentsUnique.($updatedByRemoveNoiseOrNot)) { + $updatedByRemoveNoiseOrNot = $htIdentitiesWithRoleAssignmentsUnique.($updatedByRemoveNoiseOrNot).details + } } @" - - - + + + + + + + + + "@ } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingSubscription) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomRoles) [void]$htmlTenantSummary.AppendLine(@" - -
    IdDisplayNameDisplayName Invalid charsRole NameRoleIdAssignable ScopesDataCreated/UpdatedCreatedOnCreatedByUpdatedOnUpdatedBy
    $($key)$($displayName)$($displayNameInvalidChars)$($entry.Name -replace '<', '<' -replace '>', '>')$($entry.Id)$(($entry.AssignableScopes | Measure-Object).count) ($($entry.AssignableScopes -join "$CsvDelimiterOpposite "))$($roleManageData)$createdOnUpdatedOn$createdOnFormated$createdBy$updatedOnFormated$updatedByRemoveNoiseOrNot
    + +
    - "@) } else { [void]$htmlTenantSummary.AppendLine(@" -

    Subscription $($namingSubscriptionCount) Naming findings

    +

    $customRoleDefinitionsCreatedOrUpdatedCount Created/Updated custom Role definitions

    "@) } + #endregion ChangeTrackingCustomRoles - - $namingRoleCount = $htNamingValidation.Role.values.count - if ($namingRoleCount -gt 0) { - $tfCount = $namingRoleCount - $htmlTableId = "TenantSummary_NamingRole" + #region ChangeTrackingRoleAssignments + if ($roleAssignmentsCreatedCount -gt 0) { + $tfCount = $roleAssignmentsCreatedCount + $htmlTableId = 'TenantSummary_ChangeTrackingRoleAssignments' [void]$htmlTenantSummary.AppendLine(@" - +
    - Download CSV semicolon | comma + Download CSV semicolon | comma - - - + + + + + + + + + + + + + + + + "@) - $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 ">", ">" + $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 = "" - $roleNameInvalidChars = "" + $roleName = $entry.Role } - - @" + [void]$htmlSUMMARYChangeTrackingRoleAssignments.AppendFormat( + @' - - - + + + + + + + + + + + + + + + + -"@ +'@, $entry.TenOrMgOrSubOrRGOrRes, + $roleName, + $entry.RoleId, + $entry.RoleType, + $entry.RoleDataRelated, + $entry.ObjectDisplayName, + $entry.ObjectSignInName, + $entry.ObjectId, + $entry.ObjectType, + $entry.AssignmentType, + $entry.AssignmentInheritFrom, + $entry.GroupMembersCount, + $entry.RoleAssignmentId, + ($entry.RbacRelatedPolicyAssignment -replace '<', '<' -replace '>', '>'), + $entry.CreatedOn, + $entry.CreatedBy + ) } - [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingRole) + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingRoleAssignments) [void]$htmlTenantSummary.AppendLine(@" - -
    IdNameName Invalid charsScopeRoleRole IdRole TypeDataIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsRole AssignmentIdRelated Policy Assignment $noteOrNotCreatedOnCreatedBy
    $($key)$($roleName)$($roleNameInvalidChars){0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}
    + +
    - -"@) - } - 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 - -} -#endregion TenantSummary - -#region DefinitionInsights -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 DefinitionInsightsPolicyDefinitions" - $tfCount = $tenantAllPoliciesCount - $htmlTableId = "definitionInsights_Policy" - [void]$htmlDefinitionInsights.AppendLine( @" - -
    - -
    -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - - - - - -
    - - -
    - - - - - -
    - - -
    +}; +var tf = new TableFilter('$htmlTableId', tfConfig4$htmlTableId); +tf.init();}} + +"@) + } + 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(@" + +
    +"@) + #region ChangeTrackingResources + if ($resourcesCreatedOrChangedCount -gt 0) { + $tfCount = $resourcesCreatedOrChangedGroupedCount + $htmlTableId = 'TenantSummary_ChangeTrackingResources' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma - - - - - - - - - - - - - - - + + + + + + + + "@) + $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 - $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" - } - - $hasAssignments = "false" - $assignmentsCount = 0 - $assignmentsDetailed = "n/a" + $created = $entry.group.where( { $_.createdTime -gt $xdaysAgo }) + $createdCount = $created.Count + $createdInSubscriptionsCount = ($created | Group-Object -Property subscriptionId | Measure-Object).Count - if (($htPolicyWithAssignments).policy.($policy.PolicyDefinitionId)) { - $hasAssignments = "true" - $assignments = ($htPolicyWithAssignments).policy.($policy.PolicyDefinitionId).Assignments - $assignmentsCount = $assignments.Count + $changed = $entry.group.where( { $_.changedTime -gt $xdaysAgo }) + $changedCount = $changed.Count + $changedInSubscriptionsCount = ($changed | Group-Object -Property subscriptionId | 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 " + @" + + + + + + + + + + +"@ } - - } - - $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))" + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingResources) + [void]$htmlTenantSummary.AppendLine(@" + +
    JSONPolicyTypeCategoryDeprecatedPreviewScope Mg/SubScope Name/IdeffectDefaultValuehasAssignmentsAssignments CountAssignmentsUsedInPolicySetPolicySetsCountPolicySetsRolesResourceTypeResource CountCreated&ChangedCreated&Changed SubsCreatedCreated SubsChangedChanged Subs
    $($entry.Name)$($entry.Count)$($createdAndChangedCount)$($createdAndChangedInSubscriptionsCount)$($createdCount)$($createdInSubscriptionsCount)$($changedCount)$($changedInSubscriptionsCount)
    +
    + +"@) } - - $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))" + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $resourcesCreatedOrChangedCount Created/Changed Resources

    +"@) } + #endregion ChangeTrackingResources - $usedInPolicySet = "false" - $usedInPolicySetCount = 0 - $usedInPolicySets = "n/a" + [void]$htmlTenantSummary.AppendLine(@' +
    +'@) - if ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId)) { - $usedInPolicySet = "true" - $usedInPolicySetCount = ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId).policySet).Count - $usedInPolicySets = ($htPoliciesUsedInPolicySets.($policy.PolicyDefinitionId).policySet | Sort-Object) -join "$CsvDelimiterOpposite " - } + #endregion ctresources + } - $json = $($policy.Json | convertto-json -depth 99) - $guid = ([System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($policy.PolicyDefinitionId)))) -replace "-" - @" - - + [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 - - -$($policy.Type) -$($policy.Category -replace "<", "<" -replace ">", ">") -$($policy.Deprecated) -$($policy.Preview) -$($policy.ScopeMgSub) -$($scopeDetails -replace "<", "<" -replace ">", ">") -$($policy.effectDefaultValue) -$hasAssignments -$assignmentsCount -$assignmentsDetailed -$usedInPolicySet -$usedInPolicySetCount -$usedInPolicySets -$($roleDefinitionIds -replace "<", "<" -replace ">", ">") + 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]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) - $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $htmlDefinitionInsights = [System.Text.StringBuilder]::new() - [void]$htmlDefinitionInsights.AppendLine( @" - - + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicy) + [void]$htmlTenantSummary.AppendLine(@" + +
    -
    -"@) - $endDefinitionInsightsPolicyDefinitions = Get-Date - Write-Host " DefinitionInsightsPolicyDefinitions duration: $((NEW-TIMESPAN -Start $startDefinitionInsightsPolicyDefinitions -End $endDefinitionInsightsPolicyDefinitions).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startDefinitionInsightsPolicyDefinitions -End $endDefinitionInsightsPolicyDefinitions).TotalSeconds) seconds)" - #endregion definitionInsightsPolicyDefinitions - #region definitionInsightsPolicySetDefinitions - $startDefinitionInsightsPolicySetDefinitions = Get-Date - Write-Host " processing DefinitionInsightsPolicySetDefinitions" - $tfCount = $tenantAllPolicySetsCount - $htmlTableId = "definitionInsights_PolicySet" - [void]$htmlDefinitionInsights.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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $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 = '' + } -
    - - -
    -
    - - -
    + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicySet) + [void]$htmlTenantSummary.AppendLine(@" + +
    IdNameName Invalid charsDisplayNameDisplayName Invalid chars
    $($id)$($name)$($nameInvalidChars)$($displayName)$($displayNameInvalidChars)
    +
    + -
    - - -
    +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $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 = '' + } -
    - - -
    - + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicyAssignment) + [void]$htmlTenantSummary.AppendLine(@" + +
    IdNameName Invalid charsDisplayNameDisplayName Invalid chars
    $($id)$($name)$($nameInvalidChars)$($displayName)$($displayNameInvalidChars)
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma - - - - - - - - - - + + + "@) - $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 + $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 = '' + } - 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 " + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingManagementGroup) + [void]$htmlTenantSummary.AppendLine(@" + +
    JSONPolicySet TypeCategoryDeprecatedPreviewScope Mg/SubScope Name/IdhasAssignmentsAssignments CountAssignmentsIdNameName Invalid chars
    $($id)$($name)$($nameInvalidChars)
    +
    + - $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 "-" - @" - - +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYNamingSubscription = $null + $htmlSUMMARYNamingSubscription = foreach ($key in $htNamingValidation.Subscription.Keys | Sort-Object) { - - - - - - - - - - - + @" + + + + "@ - } - [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) - $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $htmlDefinitionInsights = [System.Text.StringBuilder]::new() - [void]$htmlDefinitionInsights.AppendLine( @" - -
    IdDisplayNameDisplayName Invalid chars
    $($policySet.Type)$($policySet.Category -replace "<", "<" -replace ">", ">")$($policySet.Deprecated)$($policySet.Preview)$($policySet.ScopeMgSub)$($scopeDetails -replace "<", "<" -replace ">", ">")$hasAssignments$assignmentsCount$assignmentsDetailed
    $($key)$($displayName)$($displayNameInvalidChars)
    + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingSubscription) + [void]$htmlTenantSummary.AppendLine(@" + +
    -
    -"@) - $endDefinitionInsightsPolicySetDefinitions = Get-Date - Write-Host " DefinitionInsightsPolicySetDefinitions duration: $((NEW-TIMESPAN -Start $startDefinitionInsightsPolicySetDefinitions -End $endDefinitionInsightsPolicySetDefinitions).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startDefinitionInsightsPolicySetDefinitions -End $endDefinitionInsightsPolicySetDefinitions).TotalSeconds) seconds)" - #endregion definitionInsightsPolicySetDefinitions - [void]$htmlDefinitionInsights.AppendLine( @" -
    "@) - #endregion definitionInsightsAzurePolicy - - #region definitionInsightsAzureRBAC - [void]$htmlDefinitionInsights.AppendLine( @" - -
    + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    Subscription $($namingSubscriptionCount) Naming findings

    "@) - - #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 DefinitionInsightsRoleDefinitions" - $tfCount = $tenantAllRolesCount - $htmlTableId = "definitionInsights_Roles" - [void]$htmlDefinitionInsights.AppendLine( @" - -
    - -
    -
    -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    -
    - - -
    + $namingRoleCount = $htNamingValidation.Role.values.count + if ($namingRoleCount -gt 0) { + $tfCount = $namingRoleCount + $htmlTableId = 'TenantSummary_NamingRole' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma - - - - - - - + + + "@) - $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 + $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 { - $AssignableScopesMG = $false + $roleName = '' + $roleNameInvalidChars = '' } + @" + + + + + +"@ } - 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 " + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingRole) + [void]$htmlTenantSummary.AppendLine(@" + +
    JSONRole TypeDatacanDoRoleAssignmentshasAssignmentsAssignments CountAssignmentsIdNameName Invalid chars
    $($key)$($roleName)$($roleNameInvalidChars)
    +
    + - #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 " - }) +"@) + } + 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($objectIds) { + + $arrayObjectIdsToCheck = @() + $arrayObjectIdsToCheck = foreach ($objectToCheckIfAlreadyResolved in $objectIds) { + if (-not $htPrincipals.($objectToCheckIfAlreadyResolved)) { + $objectToCheckIfAlreadyResolved + } + else { + #Write-Host "$objectToCheckIfAlreadyResolved already resolved" } + } - $json = $role.Json | convertto-json -depth 99 - $guid = $role.Id -replace "-" - @" - - + 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 -join '","') + $currentTask = " Resolving ObjectIds - Batch #$batchCnt/$($ObjectBatchCount) ($(($batch.Group).Count)" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].MicrosoftGraph)/beta/directoryObjects/getByIds" + $method = 'POST' + $body = @" + { + "ids":[$($objectsToProcess)] + } +"@ + $resolveObjectIds = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask - - - -$($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 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation - $arrayRoleDefinitionsForCSVExport = $null +} +function runInfo { + #region RunInfo + Write-Host 'Run Info:' + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $true) { + Write-Host ' Creating HierarchyMap only' -ForegroundColor Green } - #endregion exportCSV + else { + $script:paramsUsed = $Null + $startTimeUTC = ((Get-Date).ToUniversalTime()).ToString('dd-MMM-yyyy HH:mm:ss') + $script:paramsUsed += "Date: $startTimeUTC (UTC); Version: $ProductVersion " - [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) - $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $htmlDefinitionInsights = [System.Text.StringBuilder]::new() - [void]$htmlDefinitionInsights.AppendLine( @" - - -
    - -
    -"@) - $endDefinitionInsightsRoleDefinitions = Get-Date - Write-Host " DefinitionInsightsRoleDefinitions duration: $((NEW-TIMESPAN -Start $startDefinitionInsightsRoleDefinitions -End $endDefinitionInsightsRoleDefinitions).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startDefinitionInsightsRoleDefinitions -End $endDefinitionInsightsRoleDefinitions).TotalSeconds) seconds)" - #endregion definitionInsightsRoleDefinitions - [void]$htmlDefinitionInsights.AppendLine( @" -
    -"@) - #endregion definitionInsightsAzureRBAC + 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 ' + } - $script:html += $htmlDefinitionInsights - $htmlDefinitionInsights = $null - $script:html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $script:html = $null + 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 ' + } - $endDefinitionInsights = Get-Date - Write-Host " DefinitionInsights processing duration: $((NEW-TIMESPAN -Start $startDefinitionInsights -End $endDefinitionInsights).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startDefinitionInsights -End $endDefinitionInsights).TotalSeconds) seconds)" -} -#endregion DefinitionInsights + 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)% " + } -#region markdown4wiki -function ProcessDiagramMermaid() { - if ($ManagementGroupId -ne $checkContext.Tenant.Id) { - $optimizedTableForPathQueryMg = $optimizedTableForPathQueryMg.where({ $_.mgParentId -ne "'upperScopes'" }) - } - $mgLevels = ($optimizedTableForPathQueryMg | Sort-Object -Property Level -Unique).Level + 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 ' + } - 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 (-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 ($mgParentName -eq $mgParentId) { - $mgParentNameId = $mgParentName + 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 { - $mgParentNameId = "$mgParentName
    $mgParentId" + 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 ' + } - if ($mgName -eq $mgInLevel) { - $mgNameId = $mgName + 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 - AzGovViz: check the last console output for details' } - else { - $mgNameId = "$mgName
    $mgInLevel" + elseif ($AzureConsumptionPeriod -eq 0) { + Write-Host 'parameter -AzureConsumptionPeriod must be gt 0' + Throw 'Error - AzGovViz: check the last console output for details' } - $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 { + #$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 { - $mgNameId = "$mgName
    $mgInLevel" + Write-Host " Azure Consumption reporting enabled: $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" -ForegroundColor Green } - $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 -"@ + 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 " } } - } -} -#endregion markdown4wikiF + 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 ' + } -#endregion Function + 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 ' + } -#region runDataCollection + 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) " + } -#run + 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) " -#create bearer token -CreateBearerToken -targetEndPoint "ManagementAPI" -CreateBearerToken -targetEndPoint "MSGraphAPI" + 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) " + } -$arrayAPICallTracking = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) -$arrayAPICallTrackingCustomDataCollection = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + 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) " -#region validationAccess + 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) " + } -#validation / check 'Microsoft Graph API' Access -$permissionCheckResults = @() + 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) " + } + } -$userType = "n/a" -if ($accountType -eq "User") { - $currentTask = "Checking AAD UserType" - Write-Host $currentTask - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/me?`$select=userType" - $method = "GET" - $checkUserType = AzAPICall -uri $uri -method $method -listenOn "Content" -currentTask $currentTask + 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 ($checkUserType -eq "unknown") { - $userType = $checkUserType - } - else { - $userType = $checkUserType.userType - } - Write-Host " AAD UserType: $($userType)" -ForegroundColor Yellow -} -$htParameters.userType = $userType + 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 ($htParameters.onAzureDevOpsOrGitHubActions -eq $true -or $accountType -eq "ServicePrincipal" -or $accountType -eq "ManagedService" -or $accountType -eq "ClientAssertion") { + 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 " + } - Write-Host "Checking $accountType permissions" - $permissionsCheckFailed = $false + 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 " + } - $currentTask = "Test AAD Users Read permission" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/users?`$count=true&`$top=1" - $method = "GET" - $res = AzAPICall -uri $uri -method $method -currentTask $currentTask -consistencyLevel "eventual" -validateAccess $true - if ($res -eq "failed") { - $permissionCheckResults += "AAD Users Read permission check FAILED" - $permissionsCheckFailed = $true + 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) " + } } - else { - $permissionCheckResults += "AAD Users Read permission check PASSED" + #endregion RunInfo +} +function selectMg() { + Write-Host 'Please select a Management Group from the list below:' + $MgtGroupArray | Select-Object '#', Name, 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)" - $currentTask = "Test AAD Groups Read permission" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/groups?`$count=true&`$top=1" - $method = "GET" - $res = AzAPICall -uri $uri -method $method -currentTask $currentTask -consistencyLevel "eventual" -validateAccess $true - - if ($res -eq "failed") { - $permissionCheckResults += "AAD Groups Read permission check FAILED" - $permissionsCheckFailed = $true + 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 { - $permissionCheckResults += "AAD Groups Read permission check PASSED" + $msg = "last input '$SelectedMG' is not numeric, enter a number from the selection!" + selectMg } - - $currentTask = "Test AAD ServicePrincipals Read permission" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/servicePrincipals?`$count=true&`$top=1" - $method = "GET" - $res = AzAPICall -uri $uri -method $method -currentTask $currentTask -consistencyLevel "eventual" -validateAccess $true - - if ($res -eq "failed") { - $permissionCheckResults += "AAD ServicePrincipals Read permission check FAILED" - $permissionsCheckFailed = $true +} +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 { - $permissionCheckResults += "AAD ServicePrincipals Read permission check PASSED" + $script:hierarchyLevel = -1 + $script:mgSubPathTopMg = "$ManagementGroupId" + $script:getMgParentId = "'$ManagementGroupId'" + $script:getMgParentName = 'Tenant Root' + $script:mermaidprnts = "'$getMgParentId',$getMgParentId" } } -#endregion validationAccess - -#ManagementGroup helper -#region managementGroupHelper -#thx @Jim Britt https://github.com/JimGBritt/AzurePolicy/tree/master/AzureMonitor/Scripts Create-AzDiagPolicy.ps1 -if (-not $ManagementGroupId) { - #$catchResult = "letscheck" - $currentTask = "Getting all Management Groups" - #Write-Host $currentTask - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups?api-version=2020-05-01" - $method = "GET" - $getAzManagementGroups = AzAPICall -uri $uri -method $method -currentTask $currentTask -validateAccess $true - - if ($getAzManagementGroups -eq "failed") { - $permissionCheckResults += "MG Reader permission check FAILED" - $permissionsCheckFailed = $true +function setOutput { + #outputPath + 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 { - $permissionCheckResults += "MG Reader permission check PASSED" + Write-Host "Output/Files will be created in path '$outputPath'" } - Write-Host "Permission check results" - foreach ($permissionCheckResult in $permissionCheckResults) { - if ($permissionCheckResult -like "*PASSED*") { - Write-Host $permissionCheckResult -ForegroundColor Green - } - else { - Write-Host $permissionCheckResult -ForegroundColor DarkRed - } + #fileTimestamp + try { + $script:fileTimestamp = (Get-Date -Format $FileTimeStampFormat) } - if ($permissionsCheckFailed -eq $true) { - Write-Host "Please consult the documentation: https://$($GithubRepository)#required-permissions-in-azure" - Throw "Error - AzGovViz: check the last console output for details" + catch { + Write-Host "fileTimestamp format: '$($FileTimeStampFormat)' invalid; continue with default format: 'yyyyMMdd_HHmmss'" -ForegroundColor Red + $FileTimeStampFormat = 'yyyyMMdd_HHmmss' + $script:fileTimestamp = (Get-Date -Format $FileTimeStampFormat) } - if ($getAzManagementGroups.Count -eq 0) { - Write-Host "Management Groups count returned null" - Throw "Error - AzGovViz: check the last console output for details" + $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 { - Write-Host "Detected $($getAzManagementGroups.Count) Management Groups" + 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 { + if ($ShowMemoryUsage) { + function makeDouble { + [CmdletBinding()] + Param + ( + [Parameter(Mandatory = $true)]$MemoryUsed + ) - [array]$MgtGroupArray = AddIndexNumberToArray ($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 - AzGovViz: check the last console output for details" - } - function selectMg() { - Write-Host "Please select a Management Group from the list below:" - $MgtGroupArray | Select-Object "#", Name, 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 + try { + $memoryUsedDouble = [double]($memoryUsed -replace ',', '.') + } + catch { + $memoryUsedDouble = [string]$MemoryUsed + } + return $memoryUsedDouble } - $script:SelectedMG = Read-Host "Please enter a selection from 1 to $(($MgtGroupArray).count)" - - function IsNumeric ($Value) { - return $Value -match "^[\d\.]+$" + if ($IsLinux) { + $memoryUsed = 100 - (free | grep Mem | awk '{print $4/$2 * 100.0}') + $memoryUsed = makeDouble $memoryUsed } - if (IsNumeric $SelectedMG) { - 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 + if ($IsWindows) { + $memoryUsed = (Get-CimInstance win32_operatingsystem | ForEach-Object { '{0:N2}' -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory) * 100) / $_.TotalVisibleMemorySize) }) + $memoryUsed = makeDouble $memoryUsed + } + + if ($memoryUsed -is [double]) { + if ($memoryUsed -gt 90) { + Write-Host "Memory utilization HIGH: $([math]::Round($memoryUsed))%" -ForegroundColor Magenta + } + else { + Write-Host "Memory utilization: $([math]::Round($memoryUsed))%" } } else { - $msg = "last input '$SelectedMG' is not numeric, enter a number from the selection!" - selectMg + Write-Host "Memory utilization: $($memoryUsed)%" } } - selectMg - - if ($($MgtGroupArray[$SelectedMG - 1].Name)) { - $ManagementGroupId = $($MgtGroupArray[$SelectedMG - 1].Name) - $ManagementGroupName = $($MgtGroupArray[$SelectedMG - 1].DisplayName) - } - else { - Write-Host "s.th. unexpected happened" -ForegroundColor Red - return - } - Write-Host "Selected Management Group: $ManagementGroupName (Id: $ManagementGroupId)" -ForegroundColor Green - Write-Host "_______________________________________" } -else { - $currentTask = "Checking permissions for ManagementGroup '$ManagementGroupId'" - Write-Host $currentTask - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($ManagementGroupId)?api-version=2020-05-01" - $method = "GET" - $selectedManagementGroupId = AzAPICall -uri $uri -method $method -currentTask $currentTask -listenOn "Content" -validateAccess $true +function stats { + #region Stats + if (-not $StatsOptOut) { - if ($selectedManagementGroupId -eq "failed") { - $permissionCheckResults += "MG Reader permission check FAILED" - $permissionsCheckFailed = $true - } - else { - $permissionCheckResults += "MG Reader permission check PASSED" - } + 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) + } - Write-Host "Permission check results" - foreach ($permissionCheckResult in $permissionCheckResults) { - if ($permissionCheckResult -like "*PASSED*") { - Write-Host $permissionCheckResult -ForegroundColor Green + $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 { - Write-Host $permissionCheckResult -ForegroundColor DarkRed + $hashTenantIdOrRepositoryIdUse = "$(($hashTenantIdOrRepositoryIdSplit[4]).substring(6))$($hashAccIdSplit[1])" + $hashTenantIdOrRepositoryIdUse = $hasher384.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($hashTenantIdOrRepositoryIdUse)) + $hashTenantIdOrRepositoryIdUse = "$(([System.BitConverter]::ToString($hashTenantIdOrRepositoryIdUse)) -replace '-')" } - } - if ($permissionsCheckFailed -eq $true) { - Write-Host "Please consult the documentation: https://$($GithubRepository)#required-permissions-in-azure" - Throw "Error - AzGovViz: check the last console output for details" - } + 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)" + } -} -#endregion managementGroupHelper + $identifierBase = $hasher512.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($hashUse)) + $identifier = "$(([System.BitConverter]::ToString($identifierBase)) -replace '-')" -Write-Host "Running AzGovViz for ManagementGroupId: '$ManagementGroupId'" -ForegroundColor Yellow + $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 + } -<# -if ($accountType -eq "User") { - Write-Host "Checking least priviledge practice" - $currenttask = "Get permissions of executing identity $($accountId)" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/me" - $method = "GET" - Write-Host " Getting ObjectId for '$($accountId)'" - $identity = AzAPICall -uri $uri -method $method -currentTask $currenttask -listenOn "Content" - Write-Host " Getting Role assignments for '$($identity.id)' on scope '$($ManagementGroupId)'" - $identityRoleAssignments = get-AzRoleAssignment -Scope "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)" -ObjectId $identity.id - if ($identityRoleAssignments.Count -eq 1 -and $identityRoleAssignments.RoleDefinitionName -eq "Reader") { - $leastPriviledgePracticeCheck = "Passed" - Write-Host " Least priviledge practice check passed" - Write-Host " $($identityRoleAssignments.Count) Role assignment found ($($identityRoleAssignments.RoleDefinitionName)) - OK" -ForegroundColor Green + $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": "$($identifier)", + "platform": "$($azAPICallConf['htParameters'].CodeRunPlatform)", + "productVersion": "$($ProductVersion)", + "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": "$($azAPICallConf['htParameters'].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)", + "statsParametersNoResources": "$($azAPICallConf['htParameters'].NoResources)", + "statsParametersPolicyAtScopeOnly": "$($azAPICallConf['htParameters'].PolicyAtScopeOnly)", + "statsParametersRBACAtScopeOnly": "$($azAPICallConf['htParameters'].RBACAtScopeOnly)", + "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) } else { - $leastPriviledgePracticeCheck = "Failed - verify that only 'Reader' Role is assigned" - Write-Host " $($identityRoleAssignments.Count) Role assignments found:" - $cnt = 0 - foreach ($identityRoleAssignment in $identityRoleAssignments) { - $cnt++ - if ($identityRoleAssignment.RoleDefinitionName -eq "Reader") { - Write-Host " $($cnt). '$($identityRoleAssignment.RoleDefinitionName)'" -ForegroundColor Green + #noStats + $identifier = (New-Guid).Guid + $tryCounter = 0 + do { + if ($tryCounter -gt 0) { + start-sleep -seconds ($tryCounter * 3) } - else { - Write-Host " $($cnt). '$($identityRoleAssignment.RoleDefinitionName)'" -ForegroundColor DarkYellow + $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": "$($identifier)", + "statsTry": "$($tryCounter)" } } - Write-Host "" - Write-Host " Follow follow best practices by only applying a Reader Role assignment for '$($accountId)' (ObjId: $($identity.id))!" -ForegroundColor DarkYellow - pause } } -#> +"@ + $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 testPowerShellVersion { -<# -$userType = "n/a" -if ($accountType -eq "User") { - $currentTask = "Checking AAD UserType" - Write-Host $currentTask - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/me?`$select=userType" - $method = "GET" - $checkUserType = AzAPICall -uri $uri -method $method -listenOn "Content" -currentTask $currentTask - if ($checkUserType -eq "unknown") { - $userType = $checkUserType + 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 { - $userType = $checkUserType.userType + 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' } - Write-Host " AAD UserType: $($userType)" -ForegroundColor Yellow } -#> +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') { -$newTable = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + 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 -noPaging + + 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 -noPaging + + 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 -noPaging + + if ($res -eq 'failed') { + $permissionCheckResults += "MSGraph API 'ServicePrincipals Read' permission - check FAILED" + $permissionsCheckFailed = $true + } + else { + $permissionCheckResults += "MSGraph API 'ServicePrincipals' Read permission - check PASSED" + } + } + #endregion validationAccess -#region GettingEntities -Write-Host "Entities" -$startEntities = Get-Date -$currentTask = " Getting Entities" -Write-Host $currentTask -#https://management.azure.com/providers/Microsoft.Management/getEntities?api-version=2020-02-01 -$uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/getEntities?api-version=2020-02-01" -$method = "POST" -$arrayEntitiesFromAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask - -Write-Host " $($arrayEntitiesFromAPI.Count) Entities returned" - -$endEntities = Get-Date -Write-Host " Getting Entities duration: $((NEW-TIMESPAN -Start $startEntities -End $endEntities).TotalSeconds) seconds" - -$startEntitiesdata = Get-Date -Write-Host " Processing Entities data" -$htSubscriptionsMgPath = @{} -$htManagementGroupsMgPath = @{} -$htEntities = @{} -$htEntitiesPlain = @{} - -foreach ($entity in $arrayEntitiesFromAPI) { - $htEntitiesPlain.($entity.Name) = @{} - $htEntitiesPlain.($entity.Name) = $entity -} + #ManagementGroup helper + #region managementGroupHelper + #thx @Jim Britt https://github.com/JimGBritt/AzurePolicy/tree/master/AzureMonitor/Scripts Create-AzDiagPolicy.ps1 + 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 -noPaging -foreach ($entity in $arrayEntitiesFromAPI) { - if ($entity.Type -eq "/subscriptions") { - $htSubscriptionsMgPath.($entity.name) = @{} - $htSubscriptionsMgPath.($entity.name).ParentNameChain = $entity.properties.parentNameChain - $htSubscriptionsMgPath.($entity.name).ParentNameChainDelimited = $entity.properties.parentNameChain -join "/" - $htSubscriptionsMgPath.($entity.name).Parent = $entity.properties.parent.Id -replace ".*/" - $htSubscriptionsMgPath.($entity.name).ParentName = $htEntitiesPlain.($entity.properties.parent.Id -replace ".*/").properties.displayName - $htSubscriptionsMgPath.($entity.name).DisplayName = $entity.properties.displayName - $array = $entity.properties.parentNameChain - $array += $entity.name - $htSubscriptionsMgPath.($entity.name).path = $array - $htSubscriptionsMgPath.($entity.name).pathDelimited = $array -join "/" - $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__" + if ($getAzManagementGroups -eq 'failed') { + $permissionCheckResults += "'Reader' permissions on Management Group - check FAILED" + $permissionsCheckFailed = $true } else { - $parent = $entity.properties.parent.Id -replace ".*/" - } - $htManagementGroupsMgPath.($entity.name) = @{} - $htManagementGroupsMgPath.($entity.name).ParentNameChain = $entity.properties.parentNameChain - $htManagementGroupsMgPath.($entity.name).ParentNameChainDelimited = $entity.properties.parentNameChain -join "/" - $htManagementGroupsMgPath.($entity.name).ParentNameChainCount = ($entity.properties.parentNameChain | Measure-Object).Count - $htManagementGroupsMgPath.($entity.name).Parent = $parent - $htManagementGroupsMgPath.($entity.name).ChildMgsAll = ($arrayEntitiesFromAPI.where( { $_.Type -eq "Microsoft.Management/managementGroups" -and $_.properties.ParentNameChain -contains $entity.name } )).Name - $htManagementGroupsMgPath.($entity.name).ChildMgsDirect = ($arrayEntitiesFromAPI.where( { $_.Type -eq "Microsoft.Management/managementGroups" -and $_.properties.Parent.Id -replace ".*/" -eq $entity.name } )).Name - $htManagementGroupsMgPath.($entity.name).DisplayName = $entity.properties.displayName - $htManagementGroupsMgPath.($entity.name).Id = ($entity.name) - $array = $entity.properties.parentNameChain - $array += $entity.name - $htManagementGroupsMgPath.($entity.name).path = $array - $htManagementGroupsMgPath.($entity.name).pathDelimited = $array -join "/" - } - - $htEntities.($entity.name) = @{} - $htEntities.($entity.name).ParentNameChain = $entity.properties.parentNameChain - $htEntities.($entity.name).Parent = $parent - if ($parent -eq "__TenantRoot__") { - $parentDisplayName = "__TenantRoot__" - } - else { - $parentDisplayName = $htEntitiesPlain.($htEntities.($entity.name).Parent).properties.displayName - } - $htEntities.($entity.name).ParentDisplayName = $parentDisplayName - $htEntities.($entity.name).DisplayName = $entity.properties.displayName - $htEntities.($entity.name).Id = $entity.Name -} - -Write-Host " $(($htManagementGroupsMgPath.Keys).Count) Management Groups returned" -Write-Host " $(($htSubscriptionsMgPath.Keys).Count) Subscriptions returned" + $permissionCheckResults += "'Reader' permissions on Management Group - check PASSED" + } -$endEntitiesdata = Get-Date -Write-Host " Processing Entities data duration: $((NEW-TIMESPAN -Start $startEntitiesdata -End $endEntitiesdata).TotalSeconds) seconds" + 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 - AzGovViz: check the last console output for details' + } -$endEntities = Get-Date -Write-Host "Processing Entities duration: $((NEW-TIMESPAN -Start $startEntities -End $endEntities).TotalSeconds) seconds" -#endregion GettingEntities + if ($getAzManagementGroups.Count -eq 0) { + Write-Host 'Management Groups count returned null' + Throw 'Error - AzGovViz: check the last console output for details' + } + else { + Write-Host "Detected $($getAzManagementGroups.Count) Management Groups" + } -if (($checkContext).Tenant.Id -ne $ManagementGroupId) { - $mgSubPathTopMg = $selectedManagementGroupId.ParentName - $getMgParentId = $selectedManagementGroupId.ParentName - $getMgParentName = $selectedManagementGroupId.ParentDisplayName - $mermaidprnts = "'$(($checkContext).Tenant.Id)',$getMgParentId" -} -else { - $hierarchyLevel = -1 - $mgSubPathTopMg = "$ManagementGroupId" - $getMgParentId = "'$ManagementGroupId'" - $getMgParentName = "Tenant Root" - $mermaidprnts = "'$getMgParentId',$getMgParentId" -} + [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 - AzGovViz: check the last console output for details' + } -if ($htParameters.onAzureDevOpsOrGitHubActions -eq $false) { - $currentTask = "Get Tenant details" - Write-Host $currentTask - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)tenants?api-version=2020-01-01" - $method = "GET" - $tenantDetailsResult = AzAPICall -uri $uri -method $method -currentTask $currentTask + selectMg - if (($tenantDetailsResult).count -gt 0) { - $tenantDetails = $tenantDetailsResult | Where-Object { $_.tenantId -eq ($checkContext).Tenant.Id } - if ($tenantDetails.displayName) { - $tenantDisplayName = $tenantDetails.displayName - Write-Host " Tenant DisplayName: $tenantDisplayName" + if ($($MgtGroupArray[$SelectedMG - 1].Name)) { + $script:ManagementGroupId = $($MgtGroupArray[$SelectedMG - 1].Name) + $script:ManagementGroupName = $($MgtGroupArray[$SelectedMG - 1].DisplayName) } else { - Write-Host " Tenant DisplayName: could not be retrieved" - } - - if ($tenantDetails.defaultDomain) { - $tenantDefaultDomain = $tenantDetails.defaultDomain + Write-Host 's.th. unexpected happened' -ForegroundColor Red + return } + Write-Host "Selected Management Group: $ManagementGroupName (Id: $ManagementGroupId)" -ForegroundColor Green + Write-Host '_______________________________________' } else { - Write-Host " something unexpected" - } -} + $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 -noPaging -Write-Host "Get Default Management Group" -$currentTask = "Get Default Management Group" -#https://docs.microsoft.com/en-us/azure/governance/management-groups/how-to/protect-resource-hierarchy#setting---default-management-group -$uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$(($checkContext).Tenant.Id)/settings?api-version=2020-02-01" -$method = "GET" -$settingsMG = AzAPICall -uri $uri -method $method -currentTask $currentTask - -if (($settingsMG).count -gt 0) { - write-host " default ManagementGroup Id: $($settingsMG.properties.defaultManagementGroup)" - $defaultManagementGroupId = $settingsMG.properties.defaultManagementGroup - write-host " requireAuthorizationForGroupCreation: $($settingsMG.properties.requireAuthorizationForGroupCreation)" - $requireAuthorizationForGroupCreation = $settingsMG.properties.requireAuthorizationForGroupCreation -} -else { - write-host " default ManagementGroup: $(($checkContext).Tenant.Id) (Tenant Root)" - $defaultManagementGroupId = ($checkContext).Tenant.Id - $requireAuthorizationForGroupCreation = $false -} + if ($selectedManagementGroupId -eq 'failed') { + $permissionCheckResults += "'Reader' permissions on Management Group '$($ManagementGroupId)' - check FAILED" + $permissionsCheckFailed = $true + } + else { + $permissionCheckResults += "'Reader' permissions on Management Group '$($ManagementGroupId)' - check PASSED" + $script:ManagementGroupId = $selectedManagementGroupId.Name + $script:ManagementGroupName = $selectedManagementGroupId.properties.displayName + } -if ($htParameters.HierarchyMapOnly -eq $false) { + Write-Host 'Permission check results' + foreach ($permissionCheckResult in $permissionCheckResults) { + if ($permissionCheckResult -like '*PASSED*') { + Write-Host $permissionCheckResult -ForegroundColor Green + } + else { + Write-Host $permissionCheckResult -ForegroundColor DarkRed + } + } - #region RunInfo - $paramsUsed = $Null - $paramsUsed += "Date: $startTimeUTC (UTC); Version: $ProductVersion " + if ($permissionsCheckFailed -eq $true) { + Write-Host "Please consult the documentation: https://$($GithubRepository)#required-permissions-in-azure" + Throw 'Error - AzGovViz: check the last console output for details' + } - if ($accountType -eq "ServicePrincipal") { - $paramsUsed += "ExecutedBy: $($accountId) (App/ClientId) ($($accountType)) " - } - elseif ($accountType -eq "ManagedService") { - $paramsUsed += "ExecutedBy: $($accountId) (Id) ($($accountType)) " - } - elseif ($accountType -eq "ClientAssertion") { - $paramsUsed += "ExecutedBy: $($accountId) (App/ClientId) ($($accountType)) " - } - else { - $paramsUsed += "ExecutedBy: $($accountId) ($($accountType), $($userType)) " } - #$paramsUsed += "ManagementGroupId: $($ManagementGroupId) " - $paramsUsed += "HierarchyMapOnly: false " - Write-Host "Run Info:" - Write-Host " Creating HierarchyMap, TenantSummary, DefinitionInsights and ScopeInsights - use parameter: '-HierarchyMapOnly' to only create the HierarchyMap" -ForegroundColor Yellow + #endregion managementGroupHelper +} +#region functions4DataCollection - if ($htParameters.ManagementGroupsOnly) { - Write-Host " Management Groups only = $($htParameters.ManagementGroupsOnly)" -ForegroundColor Green - } - else { - Write-Host " Management Groups only = $($htParameters.ManagementGroupsOnly) - use parameter -ManagementGroupsOnly to only collect data for Management Groups" -ForegroundColor Yellow - } +function dataCollectionMGSecureScore { + [CmdletBinding()]Param( + [string]$Id + ) - if (($SubscriptionQuotaIdWhitelist).count -eq 1 -and $SubscriptionQuotaIdWhitelist[0] -eq "undefined") { - Write-Host " Subscription Whitelist disabled - use parameter: '-SubscriptionQuotaIdWhitelist' to whitelist QuotaIds" -ForegroundColor Yellow - $paramsUsed += "SubscriptionQuotaIdWhitelist: false " - } - else { - Write-Host " Subscription Whitelist enabled. AzGovViz will only process Subscriptions where QuotaId startswith one of the following strings:" -ForegroundColor Green - foreach ($quotaIdFromSubscriptionQuotaIdWhitelist in $SubscriptionQuotaIdWhitelist) { - Write-Host " - $($quotaIdFromSubscriptionQuotaIdWhitelist)" -ForegroundColor Green + $mgAscSecureScoreResult = '' + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + if ($htMgASCSecureScore.($Id)) { + $mgAscSecureScoreResult = $htMgASCSecureScore.($Id).SecureScore } - 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 - AzGovViz: check the last console output for details" - } + else { + $mgAscSecureScoreResult = 'isNullOrEmpty' } - $paramsUsed += "SubscriptionQuotaIdWhitelist: $($SubscriptionQuotaIdWhitelist -join ", ") " } + return $mgAscSecureScoreResult +} +$funcDataCollectionMGSecureScore = $function:dataCollectionMGSecureScore.ToString() - if ($htParameters.NoMDfCSecureScore -eq $true) { - Write-Host " Microsoft Defender for Cloud Secure Score disabled (-NoMDfCSecureScore = $($htParameters.NoMDfCSecureScore))" -ForegroundColor Green - $paramsUsed += "NoMDfCSecureScore: true " - } - else { - Write-Host " Microsoft Defender for Cloud Secure Score enabled - use parameter: '-NoMDfCSecureScore' to disable" -ForegroundColor Yellow - $paramsUsed += "NoMDfCSecureScore: false " - } +function dataCollectionDefenderPlans { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $ChildMgMgPath + ) - if ($htParameters.DoNotShowRoleAssignmentsUserData -eq $true) { - Write-Host " Scrub Identity information for identityType='User' enabled (-DoNotShowRoleAssignmentsUserData = $($htParameters.DoNotShowRoleAssignmentsUserData))" -ForegroundColor Green - $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 - $paramsUsed += "DoNotShowRoleAssignmentsUserData: false " - } + $currentTask = "Getting Microsoft Defender for Cloud plans for Subscription: '$($scopeDisplayName)' ('$scopeId')" + #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 ($LimitCriticalPercentage -eq 80) { - Write-Host " ARM Limits warning set to 80% (default) - use parameter: '-LimitCriticalPercentage' to set warning level accordingly" -ForegroundColor Yellow - #$paramsUsed += "LimitCriticalPercentage: 80% (default) " + if ($defenderPlansResult -eq 'SubScriptionNotRegistered') { + #Subscription skipped for MDfC + $null = $script:arrayDefenderPlansSubscriptionNotRegistered.Add([PSCustomObject]@{ + subscriptionId = $scopeId + subscriptionName = $scopeDisplayName + subscriptionMgPath = $childMgMgPath + }) } else { - Write-Host " ARM Limits warning set to $($LimitCriticalPercentage)% (custom)" -ForegroundColor Green - #$paramsUsed += "LimitCriticalPercentage: $($LimitCriticalPercentage)% " + 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() - if ($htParameters.NoPolicyComplianceStates -eq $false) { - Write-Host " Policy States enabled - use parameter: '-NoPolicyComplianceStates' to disable Policy States" -ForegroundColor Yellow - $paramsUsed += "NoPolicyComplianceStates: false " - } - else { - Write-Host " Policy States disabled (-NoPolicyComplianceStates = $($htParameters.NoPolicyComplianceStates))" -ForegroundColor Green - $paramsUsed += "NoPolicyComplianceStates: true " - } +function dataCollectionDiagnosticsSub { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $ChildMgMgPath, + $ChildMgId + ) - if (-not $NoResourceDiagnosticsPolicyLifecycle) { - Write-Host " Resource Diagnostics Policy Lifecycle recommendations enabled - use parameter: '-NoResourceDiagnosticsPolicyLifecycle' to disable Resource Diagnostics Policy Lifecycle recommendations" -ForegroundColor Yellow - $paramsUsed += "NoResourceDiagnosticsPolicyLifecycle: false " - } - else { - Write-Host " Resource Diagnostics Policy Lifecycle disabled (-NoResourceDiagnosticsPolicyLifecycle = $($NoResourceDiagnosticsPolicyLifecycle))" -ForegroundColor Green - $paramsUsed += "NoResourceDiagnosticsPolicyLifecycle: true " - } + $currentTask = "getDiagnosticSettingsSub for Subscription: '$($scopeDisplayName)' ('$scopeId')" + $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 (-not $NoAADGroupsResolveMembers) { - Write-Host " AAD Groups resolve members enabled (honors parameter -DoNotShowRoleAssignmentsUserData) - use parameter: '-NoAADGroupsResolveMembers' to disable resolving AAD Group memberships" -ForegroundColor Yellow - $paramsUsed += "NoAADGroupsResolveMembers: false " - if ($AADGroupMembersLimit -eq 500) { - Write-Host " AADGroupMembersLimit = $AADGroupMembersLimit" -ForegroundColor Yellow - $paramsUsed += "AADGroupMembersLimit: $AADGroupMembersLimit " - } - else { - Write-Host " AADGroupMembersLimit = $AADGroupMembersLimit" -ForegroundColor Green - $paramsUsed += "AADGroupMembersLimit: $AADGroupMembersLimit " - } + if ($getDiagnosticSettingsSub.Count -eq 0) { + $null = $script:arrayDiagnosticSettingsMgSub.Add([PSCustomObject]@{ + Scope = 'Sub' + ScopeName = $scopeDisplayName + ScopeId = $scopeId + ScopeMgPath = $childMgMgPath + SubMgParent = $childMgId + DiagnosticsPresent = 'false' + }) } else { - Write-Host " AAD Groups resolve members disabled (-NoAADGroupsResolveMembers = $($NoAADGroupsResolveMembers))" -ForegroundColor Green - $paramsUsed += "NoAADGroupsResolveMembers: true " - } - - Write-Host " AADServicePrincipalExpiryWarningDays: $AADServicePrincipalExpiryWarningDays" -ForegroundColor Yellow - #$paramsUsed += "AADServicePrincipalExpiryWarningDays: $AADServicePrincipalExpiryWarningDays " - - if ($htParameters.DoAzureConsumption -eq $true) { - if (-not $AzureConsumptionPeriod -is [int]) { - Write-Host "parameter -AzureConsumptionPeriod must be an integer" - Throw "Error - AzGovViz: check the last console output for details" - } - elseif ($AzureConsumptionPeriod -eq 0) { - Write-Host "parameter -AzureConsumptionPeriod must be gt 0" - Throw "Error - AzGovViz: 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 + 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 + }) + } } - else { - Write-Host " Azure Consumption reporting enabled: $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" -ForegroundColor Green + + $htLogs = @{} + if ($diagnosticSetting.Properties.logs) { + foreach ($logCategory in $diagnosticSetting.properties.logs) { + if ($logCategory.enabled) { + $htLogs.($logCategory.category) = 'true' + } + else { + $htLogs.($logCategory.category) = 'false' + } + } } - if (-not $NoAzureConsumptionReportExportToCSV) { - Write-Host " Azure Consumption report export to CSV enabled - use parameter: '-NoAzureConsumptionReportExportToCSV' to disable" -ForegroundColor Yellow + 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 + }) } - else { - Write-Host " Azure Consumption report export to CSV disabled (-NoAzureConsumptionReportExportToCSV = $($NoAzureConsumptionReportExportToCSV))" -ForegroundColor Green + 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 + }) } - $paramsUsed += "DoAzureConsumption: true ($AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)) " - $paramsUsed += "NoAzureConsumptionReportExportToCSV: $NoAzureConsumptionReportExportToCSV " } } - else { - Write-Host " Azure Consumption reporting disabled (-DoAzureConsumption = $($htParameters.DoAzureConsumption))" -ForegroundColor Green - $paramsUsed += "DoAzureConsumption: false " - } +} +$funcDataCollectionDiagnosticsSub = $function:dataCollectionDiagnosticsSub.ToString() + +function dataCollectionDiagnosticsMG { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName + ) + + $mgPath = $htManagementGroupsMgPath.($scopeId).pathDelimited + $currentTask = "getARMDiagnosticSettingsMg '$($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 ($NoScopeInsights) { - Write-Host " ScopeInsights will not be created (-NoScopeInsights = $($NoScopeInsights))" -ForegroundColor Green - $paramsUsed += "NoScopeInsights: true " + if ($getDiagnosticSettingsMg -eq 'InvalidResourceType') { + #skipping until supported } 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 - $paramsUsed += "NoScopeInsights: false " + 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() - if ($NoSingleSubscriptionOutput) { - Write-Host " No single Subscription output will not be created (-NoSingleSubscriptionOutput = $($NoSingleSubscriptionOutput))" -ForegroundColor Green - $paramsUsed += "NoSingleSubscriptionOutput: true " - } - else { - Write-Host " Single Subscription output will be created (-NoSingleSubscriptionOutput = $($NoSingleSubscriptionOutput))" -ForegroundColor Yellow - $paramsUsed += "NoSingleSubscriptionOutput: false " - } +function dataCollectionResources { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $ChildMgMgPath + ) + $currentTask = "Getting ResourceTypes for Subscription: '$($scopeDisplayName)' ('$scopeId')" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/resources?`$expand=createdTime,changedTime&api-version=2021-04-01" + $method = 'GET' + $resourcesSubscriptionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' - if ($htParameters.NoResourceProvidersDetailed -eq $true) { - Write-Host " ResourceProvider Detailed for TenantSummary disabled (-NoResourceProvidersDetailed = $($htParameters.NoResourceProvidersDetailed))" -ForegroundColor Green - $paramsUsed += "NoResourceProvidersDetailed: $($htParameters.NoResourceProvidersDetailed) " - } - else { - Write-Host " ResourceProvider Detailed for TenantSummary enabled - use parameter: '-NoResourceProvidersDetailed' to disable" -ForegroundColor Yellow - $paramsUsed += "NoResourceProvidersDetailed: $($htParameters.NoResourceProvidersDetailed) " + 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 + }) } - if ($htParameters.LargeTenant -or $htParameters.PolicyAtScopeOnly -or $htParameters.RBACAtScopeOnly) { - if ($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 = $($htParameters.LargeTenant))" -ForegroundColor Green - $paramsUsed += "LargeTenant: $($htParameters.LargeTenant) " - $paramsUsed += "LargeTenant -> PolicyAtScopeOnly: $($htParameters.PolicyAtScopeOnly) " - $paramsUsed += "LargeTenant -> RBACAtScopeOnly: $($htParameters.RBACAtScopeOnly) " - $paramsUsed += "LargeTenant -> NoScopeInsights: $($NoScopeInsights) " - $paramsUsed += "LargeTenant -> NoResourceProvidersDetailed: $($htParameters.NoResourceProvidersDetailed) " - } - else { - Write-Host " TenantSummary LargeTenant disabled (-LargeTenant = $($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 - $paramsUsed += "LargeTenant: $($htParameters.LargeTenant) " - - if ($htParameters.PolicyAtScopeOnly) { - Write-Host " TenantSummary Policy assignments will not include assignment information on scopes where assignment is inherited (PolicyAtScopeOnly = $($htParameters.PolicyAtScopeOnly))" -ForegroundColor Green - $paramsUsed += "PolicyAtScopeOnly: $($htParameters.PolicyAtScopeOnly) " - } - else { - Write-Host " TenantSummary Policy assignments will include assignment information on scopes where assignment is inherited (PolicyAtScopeOnly = $($htParameters.PolicyAtScopeOnly))" -ForegroundColor Yellow - $paramsUsed += "PolicyAtScopeOnly: $($htParameters.PolicyAtScopeOnly) " - } - - if ($htParameters.RBACAtScopeOnly) { - Write-Host " TenantSummary Role assignments will not include assignment information on scopes where assignment is inherited (RBACAtScopeOnly = $($htParameters.RBACAtScopeOnly))" -ForegroundColor Green - $paramsUsed += "RBACAtScopeOnly: $($htParameters.RBACAtScopeOnly) " - } - else { - Write-Host " TenantSummary Role assignments will include assignment information on scopes where assignment is inherited (RBACAtScopeOnly = $($htParameters.RBACAtScopeOnly))" -ForegroundColor Yellow - $paramsUsed += "RBACAtScopeOnly: $($htParameters.RBACAtScopeOnly) " - } + 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 } } - else { - Write-Host " TenantSummary LargeTenant disabled (-LargeTenant = $($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 - $paramsUsed += "LargeTenant: $($htParameters.LargeTenant) " - if ($htParameters.PolicyAtScopeOnly) { - Write-Host " TenantSummary Policy assignments will not include assignment information on scopes where assignment is inherited (PolicyAtScopeOnly = $($htParameters.PolicyAtScopeOnly))" -ForegroundColor Green - $paramsUsed += "PolicyAtScopeOnly: $($htParameters.PolicyAtScopeOnly) " - } - else { - Write-Host " TenantSummary Policy assignments will include assignment information on scopes where assignment is inherited (PolicyAtScopeOnly = $($htParameters.PolicyAtScopeOnly))" -ForegroundColor Yellow - $paramsUsed += "PolicyAtScopeOnly: $($htParameters.PolicyAtScopeOnly) " - } + $startSubResourceIdsThis = Get-Date + foreach ($resource in ($resourcesSubscriptionResult)) { + $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) + }) - if ($htParameters.RBACAtScopeOnly) { - Write-Host " TenantSummary Role assignments will not include assignment information on scopes where assignment is inherited (RBACAtScopeOnly = $($htParameters.RBACAtScopeOnly))" -ForegroundColor Green - $paramsUsed += "RBACAtScopeOnly: $($htParameters.RBACAtScopeOnly) " - } - else { - Write-Host " TenantSummary Role assignments will include assignment information on scopes where assignment is inherited (RBACAtScopeOnly = $($htParameters.RBACAtScopeOnly))" -ForegroundColor Yellow - $paramsUsed += "RBACAtScopeOnly: $($htParameters.RBACAtScopeOnly) " + 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] + $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 '.*/' + }) + } + } } } + $endSubResourceIdsThis = Get-Date + $null = $script:arraySubResourcesAddArrayDuration.Add([PSCustomObject]@{ + sub = $scopeId + DurationSec = (NEW-TIMESPAN -Start $startSubResourceIdsThis -End $endSubResourceIdsThis).TotalSeconds + }) - if (-not $htParameters.DoNotIncludeResourceGroupsOnPolicy) { - Write-Host " TenantSummary Policy assignments will also include assignments on ResourceGroups (DoNotIncludeResourceGroupsOnPolicy = $($htParameters.DoNotIncludeResourceGroupsOnPolicy))" -ForegroundColor Yellow - $paramsUsed += "DoNotIncludeResourceGroupsOnPolicy: false " - } - else { - Write-Host " TenantSummary Policy assignments will not include assignments on ResourceGroups (DoNotIncludeResourceGroupsOnPolicy = $($htParameters.DoNotIncludeResourceGroupsOnPolicy))" -ForegroundColor Green - $paramsUsed += "DoNotIncludeResourceGroupsOnPolicy: true " - } - - if (-not $htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - Write-Host " TenantSummary RBAC Role assignments will also include assignments on ResourceGroups and Resources (DoNotIncludeResourceGroupsAndResourcesOnRBAC = $($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC))" -ForegroundColor Yellow - $paramsUsed += "DoNotIncludeResourceGroupsAndResourcesOnRBAC: false " - } - else { - Write-Host " TenantSummary RBAC Role assignments will not include assignments on ResourceGroups and Resources (DoNotIncludeResourceGroupsAndResourcesOnRBAC = $($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC))" -ForegroundColor Green - $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 - $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 - $paramsUsed += "NoCsvExport: true " - } - if (-not $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 = $($htParameters.NoJsonExport))" -ForegroundColor Yellow - $paramsUsed += "NoJsonExport: false " - if (-not $htParameters.DoNotIncludeResourceGroupsOnPolicy) { - if (-not $JsonExportExcludeResourceGroups) { - Write-Host " JSON Export will also include Policy assignments on ResourceGroups (JsonExportExcludeResourceGroups = $($JsonExportExcludeResourceGroups))" -ForegroundColor Yellow - $paramsUsed += "JsonExportExcludeResourceGroups Policy: $($JsonExportExcludeResourceGroups) " + #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 { - Write-Host " JSON Export will not include Policy assignments on ResourceGroups (JsonExportExcludeResourceGroups = $($JsonExportExcludeResourceGroups))" -ForegroundColor Green - $paramsUsed += "JsonExportExcludeResourceGroups Policy: $($JsonExportExcludeResourceGroups) " + $script:htSubscriptionTagList.($scopeId).Resource."$tagName" = 1 } - } - if (-not $htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - if (-not $JsonExportExcludeResourceGroups) { - Write-Host " JSON Export will also include Role assignments on ResourceGroups (JsonExportExcludeResourceGroups = $($JsonExportExcludeResourceGroups))" -ForegroundColor Yellow - $paramsUsed += "JsonExportExcludeResourceGroups RBAC: $($JsonExportExcludeResourceGroups) " + #resourceAll + if ($htAllTagList.Resource.ContainsKey($tagName)) { + $script:htAllTagList.Resource."$tagName" += 1 } else { - Write-Host " JSON Export will not include Role assignments on ResourceGroups (JsonExportExcludeResourceGroups = $($JsonExportExcludeResourceGroups))" -ForegroundColor Green - $paramsUsed += "JsonExportExcludeResourceGroups RBAC: $($JsonExportExcludeResourceGroups) " + $script:htAllTagList.Resource."$tagName" = 1 } - if (-not $JsonExportExcludeResources) { - Write-Host " JSON Export will also include Role assignments on Resources (JsonExportExcludeResources = $($JsonExportExcludeResources))" -ForegroundColor Yellow - $paramsUsed += "JsonExportExcludeResources RBAC: $($JsonExportExcludeResources) " + + #all + if ($htAllTagList.AllScopes.ContainsKey($tagName)) { + $script:htAllTagList.AllScopes."$tagName" += 1 } else { - Write-Host " JSON Export will not include Role assignments on Resources (JsonExportExcludeResources = $($JsonExportExcludeResources))" -ForegroundColor Green - $paramsUsed += "JsonExportExcludeResources RBAC: $($JsonExportExcludeResources) " + $script:htAllTagList.AllScopes."$tagName" = 1 } } } - 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 = $($htParameters.NoJsonExport))" -ForegroundColor Green - $paramsUsed += "NoJsonExport: true " - } - - if ($ThrottleLimit -eq 10) { - Write-Host " ThrottleLimit = $ThrottleLimit" -ForegroundColor Yellow - #$paramsUsed += "ThrottleLimit: $ThrottleLimit " - } - else { - Write-Host " ThrottleLimit = $ThrottleLimit" -ForegroundColor Green - #$paramsUsed += "ThrottleLimit: $ThrottleLimit " - } - - - if ($ChangeTrackingDays -eq 14) { - Write-Host " ChangeTrackingDays = $ChangeTrackingDays" -ForegroundColor Yellow - #$paramsUsed += "ChangeTrackingDays: $ChangeTrackingDays " - } - else { - Write-Host " ChangeTrackingDays = $ChangeTrackingDays" -ForegroundColor Green - #$paramsUsed += "ChangeTrackingDays: $ChangeTrackingDays " - } - +} +$funcDataCollectionResources = $function:dataCollectionResources.ToString() - if ($htParameters.NoResources) { - Write-Host " NoResources = $($htParameters.NoResources)" -ForegroundColor Green - $paramsUsed += "NoResources: $($htParameters.NoResources) " - } - else { - Write-Host " NoResources = $($htParameters.NoResources)" -ForegroundColor Yellow - $paramsUsed += "NoResources: $($htParameters.NoResources) " - } - #endregion RunInfo +function dataCollectionResourceGroups { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName + ) - #helper ht / collect results /save some time - #$htCacheDefinitions = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheDefinitionsPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheDefinitionsPolicySet = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheDefinitionsRole = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheDefinitionsBlueprint = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htRoleDefinitionIdsUsedInPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htPoliciesUsedInPolicySets = @{} - $htSubscriptionTags = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheAssignmentsPolicyOnResourceGroupsAndResources = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheAssignmentsRole = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheAssignmentsRBACOnResourceGroupsAndResources = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheAssignmentsBlueprint = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCacheAssignmentsPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCachePolicyComplianceMG = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCachePolicyComplianceSUB = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCachePolicyComplianceResponseTooLargeMG = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htCachePolicyComplianceResponseTooLargeSUB = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $outOfScopeSubscriptions = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $htAllSubscriptionsFromAPI = @{} - if ($htParameters.DoAzureConsumption -eq $true) { - $htAzureConsumptionSubscriptions = @{} - } - $customDataCollectionDuration = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $htResourceLocks = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htAllTagList = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htAllTagList.AllScopes = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htAllTagList.Subscription = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htAllTagList.ResourceGroup = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htAllTagList.Resource = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $arrayTagList = [System.Collections.ArrayList]@() - $htSubscriptionTagList = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htPolicyAssignmentExemptions = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htUserTypesGuest = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $resourcesAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $resourcesIdsAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $resourceGroupsAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $htResourceProvidersAll = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htResourceTypesUniqueResource = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $arrayDataCollectionProgressMg = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $arrayDataCollectionProgressSub = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $arraySubResourcesAddArrayDuration = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $arrayDiagnosticSettingsMgSub = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $htDiagnosticSettingsMgSub = @{} - ($htDiagnosticSettingsMgSub).mg = @{} - ($htDiagnosticSettingsMgSub).sub = @{} - $htMgAtScopePolicyAssignments = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htMgAtScopePoliciesScoped = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htMgAtScopeRoleAssignments = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htMgASCSecureScore = @{} - $htConsumptionExceptionLog = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htConsumptionExceptionLog.Mg = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htConsumptionExceptionLog.Sub = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htRoleAssignmentsFromAPIInheritancePrevention = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htNamingValidation = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htNamingValidation.PolicyAssignment = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htNamingValidation.Policy = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htNamingValidation.PolicySet = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htNamingValidation.Role = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htNamingValidation.Subscription = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htNamingValidation.ManagementGroup = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htPrincipals = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htServicePrincipals = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htDailySummary = @{} - $arrayDefenderPlans = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $arrayDefenderPlansSubscriptionNotRegistered = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $arrayUserAssignedIdentities4Resources = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $htSubscriptionsRoleAssignmentLimit = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + #https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2020-06-01 + $currentTask = "Getting ResourceGroups for Subscription: '$($scopeDisplayName)' ('$scopeId')" + $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' - #subscriptions - $startGetSubscriptions = Get-Date - $currentTask = "Getting all Subscriptions" - Write-Host "$currentTask" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions?api-version=2020-01-01" - $method = "GET" - $requestAllSubscriptionsAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask + $null = $script:resourceGroupsAll.Add([PSCustomObject]@{ + subscriptionId = $scopeId + count_ = ($resourceGroupsSubscriptionResult).count + }) - Write-Host " $($requestAllSubscriptionsAPI.Count) Subscriptions returned" - foreach ($subscription in $requestAllSubscriptionsAPI) { - $htAllSubscriptionsFromAPI.($subscription.subscriptionId) = @{} - $htAllSubscriptionsFromAPI.($subscription.subscriptionId).subDetails = $subscription + #resourceGroupTags + if ($azAPICallConf['htParameters'].NoResources -eq $true) { + $script:htSubscriptionTagList.($scopeId) = @{} } - $endGetSubscriptions = Get-Date - Write-Host "Getting all Subscriptions duration: $((NEW-TIMESPAN -Start $startGetSubscriptions -End $endGetSubscriptions).TotalSeconds) seconds" - + $script:htSubscriptionTagList.($scopeId).ResourceGroup = @{} + foreach ($tags in ($resourceGroupsSubscriptionResult.where( { $_.Tags -and -not [String]::IsNullOrWhiteSpace($_.Tags) } )).Tags) { + foreach ($tagName in $tags.PSObject.Properties.Name) { - #newAADCheck - function CheckContextSubscriptionQuotaId($AADQuotaId) { - $sleepSec = @(0, 0, 2, 2, 4, 4, 10, 10) - do { - Start-Sleep -Seconds $sleepSec[$tryCounter] - $script:tryCounter++ - $checkContext = Get-AzContext -ErrorAction Stop - if ($htAllSubscriptionsFromAPI.($checkContext.Subscription.Id).subDetails.subscriptionPolicies.quotaId -like "$($AADQuotaId)*") { - Write-Host "Current AzContext Subscription not OK: $($checkContext.Subscription.Name); $($checkContext.Subscription.Id); QuotaId: $($htAllSubscriptionsFromAPI.($checkContext.Subscription.Id).subDetails.subscriptionPolicies.quotaId)" - $alternativeSubscriptionIdForContext = (($requestAllSubscriptionsAPI.where( { $_.subscriptionPolicies.quotaId -notlike "$($AADQuotaId)*" -and $_.state -eq "Enabled" }))[0]).subscriptionId - Write-Host "Setting AzContext with alternative Subscription: $($htAllSubscriptionsFromAPI.($alternativeSubscriptionIdForContext).subDetails.displayName); $($alternativeSubscriptionIdForContext); $($htAllSubscriptionsFromAPI.($alternativeSubscriptionIdForContext).subDetails.subscriptionPolicies.quotaId)" - Set-AzContext -SubscriptionId "$($alternativeSubscriptionIdForContext)" -Tenant "$($checkContext.Tenant.Id)" -ErrorAction Stop + #resource + if ($htSubscriptionTagList.($scopeId).ResourceGroup.ContainsKey($tagName)) { + $script:htSubscriptionTagList.($scopeId).ResourceGroup."$tagName" += 1 } else { - Write-Host "Current AzContext OK: $($checkContext.Subscription.Name); $($checkContext.Subscription.Id); QuotaId: $($htAllSubscriptionsFromAPI.($checkContext.Subscription.Id).subDetails.subscriptionPolicies.quotaId)" - $contextSubscriptionQuotaId = "OK" + $script:htSubscriptionTagList.($scopeId).ResourceGroup."$tagName" = 1 } - } - until($contextSubscriptionQuotaId -eq "OK" -or $tryCounter -gt 6) - } - $tryCounter = 0 - $contextSubscriptionQuotaId = $null - $AADQuotaId = "AAD" - CheckContextSubscriptionQuotaId -AADQuotaId $AADQuotaId - $checkContext = Get-AzContext -ErrorAction Stop - - if ($tryCounter -gt 6) { - Write-Host "Problem switching the context to a Subscription that has a non AAD_ QuotaId" - Throw "Error - AzGovViz: check the last console output for details" - } - #API in rare cases returns duplicates, therefor sorting unique (id) - $childrenSubscriptions = $arrayEntitiesFromAPI.where( { $_.properties.parentNameChain -contains $ManagementGroupID -and $_.type -eq "/subscriptions" } ) | Sort-Object -Property id -Unique - $childrenSubscriptionsCount = ($childrenSubscriptions).Count - $script:subsToProcessInCustomDataCollection = [System.Collections.ArrayList]@() - - #region ASCSecureScoreMGs - if ($htParameters.NoMDfCSecureScore -eq $false) { - $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 = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)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)" - ] - } -"@ + #resourceAll + if ($htAllTagList.ResourceGroup.ContainsKey($tagName)) { + $script:htAllTagList.ResourceGroup."$tagName" += 1 + } + else { + $script:htAllTagList.ResourceGroup."$tagName" = 1 + } - $start = Get-Date - $getMgAscSecureScore = AzAPICall -uri $uri -method "POST" -currentTask $currentTask -body $body -listenOn "Content" -getMgAscSecureScore $true - $end = Get-Date - Write-Host " Getting Microsoft Defender for Cloud Secure Score for Management Groups duration: $((NEW-TIMESPAN -Start $start -End $end).TotalSeconds) seconds" - $htMgASCSecureScore = @{} - if ($getMgAscSecureScore) { - if ($getMgAscSecureScore -eq "capitulation") { - Write-Host " Microsoft Defender for Cloud SecureScore for Management Groups will not be available" -ForegroundColor Yellow + #all + if ($htAllTagList.AllScopes.ContainsKey($tagName)) { + $script:htAllTagList.AllScopes."$tagName" += 1 } else { - foreach ($entry in $getMgAscSecureScore.data) { - $script:htMgASCSecureScore.($entry.mgId) = @{} - if ($entry.secureScore -eq 404) { - $script:htMgASCSecureScore.($entry.mgId).SecureScore = "n/a" - } - else { - $script:htMgASCSecureScore.($entry.mgId).SecureScore = $entry.secureScore - } - } + $script:htAllTagList.AllScopes."$tagName" = 1 } } } - #endregion ASCSecureScoreMGs +} +$funcDataCollectionResourceGroups = $function:dataCollectionResourceGroups.ToString() - foreach ($childrenSubscription in $childrenSubscriptions) { +function dataCollectionResourceProviders { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayname + ) - $sub = $htAllSubscriptionsFromAPI.($childrenSubscription.name) - if ($sub.subDetails.subscriptionPolicies.quotaId.startswith("AAD_", "CurrentCultureIgnoreCase") -or $sub.subDetails.state -ne "Enabled") { - if (($sub.subDetails.subscriptionPolicies.quotaId).startswith("AAD_", "CurrentCultureIgnoreCase")) { - $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ - subscriptionId = $childrenSubscription.name - subscriptionName = $childrenSubscription.properties.displayName - outOfScopeReason = "QuotaId: AAD_ (State: $($sub.subDetails.state))" - ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent - ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName - Level = $htSubscriptionsMgPath.($childrenSubscription.name).level - }) - } - if ($sub.subDetails.state -ne "Enabled") { - $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ - subscriptionId = $childrenSubscription.name - subscriptionName = $childrenSubscription.properties.displayName - outOfScopeReason = "State: $($sub.subDetails.state)" - ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent - ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName - Level = $htSubscriptionsMgPath.($childrenSubscription.name).level - }) - } - } - else { - if ($SubscriptionQuotaIdWhitelist[0] -ne "undefined") { - $whitelistMatched = "unknown" - foreach ($subscriptionQuotaIdWhitelistQuotaId in $SubscriptionQuotaIdWhitelist) { - if (($sub.subDetails.subscriptionPolicies.quotaId).startswith($subscriptionQuotaIdWhitelistQuotaId, "CurrentCultureIgnoreCase")) { - $whitelistMatched = "inWhitelist" + ($script:htResourceProvidersAll).($scopeId) = @{} + $currentTask = "Getting ResourceProviders for Subscription: '$($scopeDisplayname)' ('$scopeId')" + $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 dataCollectionResourceLocks { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayname + ) + + $currentTask = "Subscription ResourceLocks '$($scopeDisplayname)' ('$scopeId')" + $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++ } } - - if ($whitelistMatched -eq "inWhitelist") { - #write-host "$($childrenSubscription.properties.displayName) in whitelist" - $null = $script:subsToProcessInCustomDataCollection.Add([PSCustomObject]@{ - subscriptionId = $childrenSubscription.name - subscriptionName = $childrenSubscription.properties.displayName - subscriptionQuotaId = $sub.subDetails.subscriptionPolicies.quotaId + #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 + }) + } } - else { - #Write-Host " preCustomDataCollection: $($childrenSubscription.properties.displayName) ($($childrenSubscription.name)) Subscription Quota Id: $($sub.subDetails.subscriptionPolicies.quotaId) is out of scope for AzGovViz (not in Whitelist)" - $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ - subscriptionId = $childrenSubscription.name - subscriptionName = $childrenSubscription.properties.displayName - outOfScopeReason = "QuotaId: '$($sub.subDetails.subscriptionPolicies.quotaId)' not in Whitelist" - ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent - ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName - Level = $htSubscriptionsMgPath.($childrenSubscription.name).level + #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 + }) + } } } - else { - $null = $script:subsToProcessInCustomDataCollection.Add([PSCustomObject]@{ - subscriptionId = $childrenSubscription.name - subscriptionName = $childrenSubscription.properties.displayName - subscriptionQuotaId = $sub.subDetails.subscriptionPolicies.quotaId - }) - } } - } - $subsToProcessInCustomDataCollectionCount = ($subsToProcessInCustomDataCollection).Count - if ($htParameters.DoAzureConsumption -eq $true) { + $htTemp.SubscriptionLocksCannotDeleteCount = $locksCannotDeleteSubscriptionCount + $htTemp.SubscriptionLocksReadOnlyCount = $locksReadOnlySubscriptionCount - #region dataprocessingConsumption - $startConsumptionData = Get-Date + #resourceGroups + $resourceGroupsLocksCannotDeleteCount = ($arrayResourceGroupsCannotDeleteLock).Count + $htTemp.ResourceGroupsLocksCannotDeleteCount = $resourceGroupsLocksCannotDeleteCount - #cost only for whitelisted quotaId - if ($SubscriptionQuotaIdWhitelist[0] -ne "undefined") { - if ($subsToProcessInCustomDataCollectionCount -gt 0) { - #region mgScopeWhitelisted - #$subscriptionIdsOptimizedForBody = '"{0}"' -f ($subsToProcessInCustomDataCollection.subscriptionId -join '","') - #$currenttask = "Getting Consumption data (scope MG '$($ManagementGroupId)') for $($subsToProcessInCustomDataCollectionCount) Subscriptions (QuotaId Whitelist: '$($SubscriptionQuotaIdWhitelist -join ", ")') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" - #Write-Host "$currentTask" - #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" - $method = "POST" + $resourceGroupsLocksReadOnlyCount = ($arrayResourceGroupsReadOnlyLock).Count + $htTemp.ResourceGroupsLocksReadOnlyCount = $resourceGroupsLocksReadOnlyCount + $htTemp.ResourceGroupsLocksCannotDelete = $arrayResourceGroupsCannotDeleteLock - $counterBatch = [PSCustomObject] @{ Value = 0 } - $batchSize = 100 - $subscriptionsBatch = ($subsToProcessInCustomDataCollection | Sort-Object -Property subscriptionQuotaId) | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) } - $batchCnt = 0 - $allConsumptionData = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + #resources + $resourcesLocksCannotDeleteCount = ($arrayResourcesCannotDeleteLock).Count + $htTemp.ResourcesLocksCannotDeleteCount = $resourcesLocksCannotDeleteCount - foreach ($batch in $subscriptionsBatch) { - $batchCnt++ - $subscriptionIdsOptimizedForBody = '"{0}"' -f (($batch.Group).subscriptionId -join '","') - $currenttask = "Getting Consumption data #batch$($batchCnt)/$(($subscriptionsBatch | Measure-Object).Count) (scope MG '$($ManagementGroupId)') for $(($batch.Group).Count) Subscriptions (QuotaId Whitelist: '$($SubscriptionQuotaIdWhitelist -join ", ")') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" - Write-Host "$currentTask" -ForegroundColor Cyan + $resourcesLocksReadOnlyCount = ($arrayResourcesReadOnlyLock).Count + $htTemp.ResourcesLocksReadOnlyCount = $resourcesLocksReadOnlyCount + $htTemp.ResourcesLocksCannotDelete = $arrayResourcesCannotDeleteLock - $body = @" -{ - "type": "ActualCost", - "dataset": { - "granularity": "none", - "filter": { - "dimensions": { - "name": "SubscriptionId", - "operator": "In", - "values": [ - $($subscriptionIdsOptimizedForBody) - ] - } - }, - "aggregation": { - "totalCost": { - "name": "PreTaxCost", - "function": "Sum" - } - }, - "grouping": [ - { - "type": "Dimension", - "name": "SubscriptionId" - }, - { - "type": "Dimension", - "name": "ResourceId" - }, - { - "type": "Dimension", - "name": "ConsumedService" - }, - { - "type": "Dimension", - "name": "MeterCategory" - }, - { - "type": "Dimension", - "name": "ChargeType" - } - ] - }, - "timeframe": "Custom", - "timeperiod": { - "from": "$($azureConsumptionStartDate)", - "to": "$($azureConsumptionEndDate)" + $script:htResourceLocks.($scopeId) = $htTemp } } -"@ +$funcDataCollectionResourceLocks = $function:dataCollectionResourceLocks.ToString() - $mgConsumptionData = AzAPICall -uri $uri -method $method -body $body -currentTask $currentTask -listenOn "ContentProperties" -getConsumption $true - #endregion mgScopeWhitelisted +function dataCollectionTags { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName + ) - <#test - #$mgConsumptionData = "OfferNotSupported" - if ($batchCnt -eq 1){ - $mgConsumptionData = "OfferNotSupported" - } - #> + $currentTask = "Subscription Tags '$($scopeDisplayName)' ('$scopeId')" + $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' - if ($mgConsumptionData -eq "Unauthorized" -or $mgConsumptionData -eq "OfferNotSupported") { - if (-not $script:htConsumptionExceptionLog.Mg.($ManagementGroupId)) { - $script:htConsumptionExceptionLog.Mg.($ManagementGroupId) = @{} - } - $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt) = @{} - $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt).Exception = $mgConsumptionData - $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt).Subscriptions = ($batch.Group).subscriptionId - Write-Host " Switching to 'foreach Subscription' Subscription scope mode. Getting Consumption data #batch$($batchCnt) using Management Group scope failed." - #region subScopewhitelisted - $body = @" -{ - "type": "ActualCost", - "dataset": { - "granularity": "none", - "aggregation": { - "totalCost": { - "name": "PreTaxCost", - "function": "Sum" + $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 } - }, - "grouping": [ - { - "type": "Dimension", - "name": "SubscriptionId" - }, - { - "type": "Dimension", - "name": "ResourceId" - }, - { - "type": "Dimension", - "name": "ConsumedService" - }, - { - "type": "Dimension", - "name": "MeterCategory" - }, - { - "type": "Dimension", - "name": "ChargeType" + else { + $script:htSubscriptionTagList.($scopeId).Subscription."$tagName" = 1 } - ] - }, - "timeframe": "Custom", - "timeperiod": { - "from": "$($azureConsumptionStartDate)", - "to": "$($azureConsumptionEndDate)" - } -} -"@ - #$allConsumptionData = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $batch.Group | ForEach-Object -Parallel { - $subIdToProcess = $_.subscriptionId - $subNameToProcess = $_.subscriptionName - $subscriptionQuotaIdToProcess = $_.subscriptionQuotaId - #region UsingVARs - $body = $using:body - $azureConsumptionStartDate = $using:azureConsumptionStartDate - $azureConsumptionEndDate = $using:azureConsumptionEndDate - $SubscriptionQuotaIdWhitelist = $using:SubscriptionQuotaIdWhitelist - #fromOtherFunctions - $arrayAzureManagementEndPointUrls = $using:arrayAzureManagementEndPointUrls - $checkContext = $using:checkContext - $htAzureEnvironmentRelatedUrls = $using:htAzureEnvironmentRelatedUrls - $htBearerAccessToken = $using:htBearerAccessToken - #Array&HTs - $htParameters = $using:htParameters - $arrayAPICallTracking = $using:arrayAPICallTracking - $allConsumptionData = $using:allConsumptionData - $htSubscriptionsMgPath = $using:htSubscriptionsMgPath - $htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI - $htConsumptionExceptionLog = $using:htConsumptionExceptionLog - #Functions - $function:AzAPICall = $using:funcAzAPICall - $function:CreateBearerToken = $using:funcCreateBearerToken - $function:GetJWTDetails = $using:funcGetJWTDetails - #endregion UsingVARs - - $currentTask = " Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)) (whitelist))" - #test - write-host $currentTask - #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($subIdToProcess)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" - $method = "POST" - $subConsumptionData = AzAPICall -uri $uri -method $method -body $body -currentTask $currentTask -listenOn "ContentProperties" -getConsumption $true - if ($subConsumptionData -eq "Unauthorized" -or $subConsumptionData -eq "OfferNotSupported" -or $subConsumptionData -eq "InvalidQueryDefinition" -or $subConsumptionData -eq "NonValidWebDirectAIRSOfferType" -or $subConsumptionData -eq "NotFoundNotSupported" -or $subConsumptionData -eq "IndirectCostDisabled") { - Write-Host " Failed ($subConsumptionData) - Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)) (whitelist))" - $hlper = $htAllSubscriptionsFromAPI.($subIdToProcess).subDetails - $hlper2 = $htSubscriptionsMgPath.($subIdToProcess) - $script:htConsumptionExceptionLog.Sub.($subIdToProcess) = @{} - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).Exception = $subConsumptionData - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionId = $subIdToProcess - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionName = $hlper.displayName - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).QuotaId = $hlper.subscriptionPolicies.quotaId - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgPath = $hlper2.ParentNameChainDelimited - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgParent = $hlper2.Parent - Continue - } - else { - Write-Host " $($subConsumptionData.Count) Consumption data entries ((scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess))))" - if ($subConsumptionData.Count -gt 0) { - foreach ($consumptionEntry in $subConsumptionData) { - if ($consumptionEntry.PreTaxCost -ne 0) { - $null = $allConsumptionData.Add($consumptionEntry) - } - } - } - } - } -ThrottleLimit $ThrottleLimit - #endregion subScopewhitelisted - } - else { - Write-Host " $($mgConsumptionData.Count) Consumption data entries" - if ($mgConsumptionData.Count -gt 0) { - foreach ($consumptionEntry in $mgConsumptionData) { - if ($consumptionEntry.PreTaxCost -ne 0) { - $null = $allConsumptionData.Add($consumptionEntry) - } - } - } - } - } + #subscriptionAll + if ($htAllTagList.Subscription.ContainsKey($tagName)) { + $script:htAllTagList.Subscription."$tagName" += 1 } else { - $allConsumptionData = "NoWhitelistSubscriptionsPresent" - Write-Host " No Subscriptions matching whitelist present, skipping Consumption data processing" + $script:htAllTagList.Subscription."$tagName" = 1 + } + + #all + if ($htAllTagList.AllScopes.ContainsKey($tagName)) { + $script:htAllTagList.AllScopes."$tagName" += 1 + } + else { + $script:htAllTagList.AllScopes."$tagName" = 1 } - } - else { - if ($subsToProcessInCustomDataCollectionCount -gt 0) { - #region mgScope - $currenttask = "Getting Consumption data (scope MG '$($ManagementGroupId)') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" - Write-Host "$currentTask" - #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" - $method = "POST" - $body = @" - { - "type": "ActualCost", - "dataset": { - "granularity": "none", - "aggregation": { - "totalCost": { - "name": "PreTaxCost", - "function": "Sum" - } - }, - "grouping": [ - { - "type": "Dimension", - "name": "SubscriptionId" - }, - { - "type": "Dimension", - "name": "ResourceId" - }, - { - "type": "Dimension", - "name": "ConsumedService" - }, - { - "type": "Dimension", - "name": "MeterCategory" - }, - { - "type": "Dimension", - "name": "ChargeType" - } - ] - }, - "timeframe": "Custom", - "timeperiod": { - "from": "$($azureConsumptionStartDate)", - "to": "$($azureConsumptionEndDate)" } + $subscriptionTagsCount = ($subscriptionTags).Count + $subscriptionTags = $subscriptionTags -join "$CsvDelimiterOpposite " } -"@ - $allConsumptionData = AzAPICall -uri $uri -method $method -body $body -currentTask $currentTask -listenOn "ContentProperties" -getConsumption $true - #endregion mgScope + else { + $subscriptionTagsCount = 0 + $subscriptionTags = 'none' + } + $htSubscriptionTagsReturn = @{} + $htSubscriptionTagsReturn.subscriptionTagsCount = $subscriptionTagsCount + $htSubscriptionTagsReturn.subscriptionTags = $subscriptionTags + return $htSubscriptionTagsReturn +} +$funcDataCollectionTags = $function:dataCollectionTags.ToString() - #test - #$allConsumptionData = "OfferNotSupported" +function dataCollectionPolicyComplianceStates { + [CmdletBinding()]Param( + [string]$TargetMgOrSub, + [string]$scopeId, + [string]$scopeDisplayName + ) - if ($allConsumptionData -eq "Unauthorized" -or $allConsumptionData -eq "OfferNotSupported") { - $script:htConsumptionExceptionLog.Mg.($ManagementGroupId) = @{} - $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).Exception = $allConsumptionData - Write-Host " Switching to 'foreach Subscription' mode. Getting Consumption data using Management Group scope failed." - #region subScope - $body = @" - { - "type": "ActualCost", - "dataset": { - "granularity": "none", - "aggregation": { - "totalCost": { - "name": "PreTaxCost", - "function": "Sum" - } - }, - "grouping": [ - { - "type": "Dimension", - "name": "SubscriptionId" - }, - { - "type": "Dimension", - "name": "ResourceId" - }, - { - "type": "Dimension", - "name": "ConsumedService" - }, - { - "type": "Dimension", - "name": "MeterCategory" - }, - { - "type": "Dimension", - "name": "ChargeType" - } - ] - }, - "timeframe": "Custom", - "timeperiod": { - "from": "$($azureConsumptionStartDate)", - "to": "$($azureConsumptionEndDate)" + $currentTask = "Policy Compliance $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.PolicyInsights/policyStates/latest/summarize?api-version=2019-10-01" } + if ($TargetMgOrSub -eq 'MG') { $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) = @{} } } -"@ - #$subIdsToProcess = ($arrayEntitiesFromAPI.where( { $_.properties.parentNameChain -contains $ManagementGroupID -and $_.type -eq "/subscriptions" } ) | Sort-Object -Property id -Unique).name - $allConsumptionData = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $subsToProcessInCustomDataCollection | ForEach-Object -Parallel { - $subIdToProcess = $_.subscriptionId - $subNameToProcess = $_.subscriptionName - $subscriptionQuotaIdToProcess = $_.subscriptionQuotaId - #region UsingVARs - $body = $using:body - $azureConsumptionStartDate = $using:azureConsumptionStartDate - $azureConsumptionEndDate = $using:azureConsumptionEndDate - #fromOtherFunctions - $arrayAzureManagementEndPointUrls = $using:arrayAzureManagementEndPointUrls - $checkContext = $using:checkContext - $htAzureEnvironmentRelatedUrls = $using:htAzureEnvironmentRelatedUrls - $htBearerAccessToken = $using:htBearerAccessToken - #Array&HTs - $htParameters = $using:htParameters - $arrayAPICallTracking = $using:arrayAPICallTracking - $htSubscriptionsMgPath = $using:htSubscriptionsMgPath - $htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI - $allConsumptionData = $using:allConsumptionData - $htConsumptionExceptionLog = $using:htConsumptionExceptionLog - #Functions - $function:AzAPICall = $using:funcAzAPICall - $function:CreateBearerToken = $using:funcCreateBearerToken - $function:GetJWTDetails = $using:funcGetJWTDetails - #endregion UsingVARs - - $currentTask = " Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" - #test - write-host $currentTask - #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($subIdToProcess)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" - $method = "POST" - $subConsumptionData = AzAPICall -uri $uri -method $method -body $body -currentTask $currentTask -listenOn "ContentProperties" -getConsumption $true - if ($subConsumptionData -eq "Unauthorized" -or $subConsumptionData -eq "OfferNotSupported" -or $subConsumptionData -eq "InvalidQueryDefinition" -or $subConsumptionData -eq "NonValidWebDirectAIRSOfferType" -or $subConsumptionData -eq "NotFoundNotSupported" -or $subConsumptionData -eq "IndirectCostDisabled") { - Write-Host " Failed ($subConsumptionData) - Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" - $hlper = $htAllSubscriptionsFromAPI.($subIdToProcess).subDetails - $hlper2 = $htSubscriptionsMgPath.($subIdToProcess) - $script:htConsumptionExceptionLog.Sub.($subIdToProcess) = @{} - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).Exception = $subConsumptionData - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionId = $subIdToProcess - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionName = $hlper.displayName - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).QuotaId = $hlper.subscriptionPolicies.quotaId - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgPath = $hlper2.ParentNameChainDelimited - $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgParent = $hlper2.Parent - Continue - } - else { - Write-Host " $($subConsumptionData.Count) Consumption data entries (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" - if ($subConsumptionData.Count -gt 0) { - foreach ($consumptionEntry in $subConsumptionData) { - if ($consumptionEntry.PreTaxCost -ne 0) { - $null = $allConsumptionData.Add($consumptionEntry) - } - } - } - } - } -ThrottleLimit $ThrottleLimit - #endregion subScope + 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 } } - else { - Write-Host " $($allConsumptionData.Count) Consumption data entries" + 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 } } } - else { - $allConsumptionData = "NoSubscriptionsPresent" - Write-Host " No Subscriptions present, skipping Consumption data processing" + + 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() - if ($allConsumptionData -eq "AccountCostDisabled" -or $allConsumptionData -eq "NoValidSubscriptions" -or $allConsumptionData -eq "NoWhitelistSubscriptionsPresent" -or $allConsumptionData -eq "NoSubscriptionsPresent") { - if ($allConsumptionData -eq "AccountCostDisabled") { - Write-Host " Seems Access to cost data has been disabled for this Account - skipping CostManagement" - } - if ($allConsumptionData -eq "NoValidSubscriptions") { - Write-Host " Seems there are no valid Subscriptions present - skipping CostManagement" - } - if ($allConsumptionData -eq "NoWhitelistSubscriptionsPresent") { - Write-Host " Seems there are no Subscriptions present that match the whitelist ($($SubscriptionQuotaIdWhitelist -join ", ")) - skipping CostManagement" - } - if ($allConsumptionData -eq "NoSubscriptionsPresent") { - Write-Host " Seems there are no Subscriptions present - skipping CostManagement" - } - Write-Host " Action: Setting switch parameter 'DoAzureConsumption' to false" - $htParameters.DoAzureConsumption = $false +function dataCollectionASCSecureScoreSub { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName + ) + + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + $currentTask = "Microsoft Defender for Cloud Secure Score Sub: '$($scopeDisplayName)' ('$scopeId')" + $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).count -gt 0) { + $subscriptionASCSecureScore = "$($subASCSecureScoreResult.properties.score.current) of $($subASCSecureScoreResult.properties.score.max) points" } else { - Write-Host " Checking returned Consumption data" - $allConsumptionDataCount = $allConsumptionData.Count + $subscriptionASCSecureScore = 'n/a' + } + } + else { + $subscriptionASCSecureScore = "excluded (-NoMDfCSecureScore $($azAPICallConf['htParameters'].NoMDfCSecureScore))" + } + return $subscriptionASCSecureScore +} +$funcDataCollectionASCSecureScoreSub = $function:dataCollectionASCSecureScoreSub.ToString() - if ($allConsumptionDataCount -gt 0) { +function dataCollectionBluePrintDefinitionsMG { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $hierarchyLevel, + $mgParentId, + $mgParentName, + $mgAscSecureScoreResult + ) - $allConsumptionData = $allConsumptionData.where( { $_.PreTaxCost -ne 0 } ) - $allConsumptionDataCount = $allConsumptionData.Count + $currentTask = "Blueprint definitions MG '$($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' - if ($allConsumptionDataCount -gt 0) { - Write-Host " $($allConsumptionDataCount) relevant Consumption data entries" + $addRowToTableDone = $false + if (($scopeBlueprintDefinitionResult).count -gt 0) { + foreach ($blueprint in $scopeBlueprintDefinitionResult) { - $arrayTotalCostSummary = @() - $htManagementGroupsCost = @{} - $arrayConsumptionData = [System.Collections.ArrayList]@() - $consumptionData = $allConsumptionData - $consumptionDataGroupedByCurrency = $consumptionData | Group-Object -property Currency + if (-not $($htCacheDefinitionsBlueprint).($blueprint.Id)) { + ($script:htCacheDefinitionsBlueprint).($blueprint.Id) = @{} + } - foreach ($currency in $consumptionDataGroupedByCurrency) { + $blueprintName = $blueprint.name + $blueprintId = $blueprint.Id + $blueprintDisplayName = $blueprint.properties.displayName + $blueprintDescription = $blueprint.properties.description + $blueprintScoped = "/providers/Microsoft.Management/managementGroups/$($scopeId)" - #subscriptions - $groupAllConsumptionDataPerCurrencyBySubscriptionId = $currency.group | Group-Object -Property SubscriptionId - foreach ($subscriptionId in $groupAllConsumptionDataPerCurrencyBySubscriptionId) { - - $subTotalCost = ($subscriptionId.Group.PreTaxCost | Measure-Object -Sum).Sum - $htAzureConsumptionSubscriptions.($subscriptionId.Name) = @{} - $htAzureConsumptionSubscriptions.($subscriptionId.Name).ConsumptionData = $subscriptionId.group - $htAzureConsumptionSubscriptions.($subscriptionId.Name).TotalCost = $subTotalCost - $htAzureConsumptionSubscriptions.($subscriptionId.Name).Currency = $currency.Name - $resourceTypes = $subscriptionId.Group.ConsumedService | Sort-Object -Unique - - foreach ($parentMg in $htSubscriptionsMgPath.($subscriptionId.Name).ParentNameChain) { - - if (-not $htManagementGroupsCost.($parentMg)) { - $htManagementGroupsCost.($parentMg) = @{} - $htManagementGroupsCost.($parentMg).currencies = $currency.Name - $htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" = $subTotalCost #[decimal]$subTotalCost - $htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" = ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count - $htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent = ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count - $htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" = 1 - $htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent = 1 - $htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" = $resourceTypes - $htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent = $resourceTypes - $htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" = $subscriptionId.group - $htManagementGroupsCost.($parentMg).consumptionDataSubscriptions = $subscriptionId.group - } - else { - $newMgTotalCost = $htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" + $subTotalCost #[decimal]$subTotalCost - $htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" = $newMgTotalCost #[decimal]$newMgTotalCost + $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 + } + } - $currencies = [array]$htManagementGroupsCost.($parentMg).currencies - if ($currencies -notcontains $currency.Name) { - $currencies += $currency.Name - $htManagementGroupsCost.($parentMg).currencies = $currencies - } + $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 + ) - #currency based - $resourcesThatGeneratedCost = $htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" + ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count - $htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" = $resourcesThatGeneratedCost + $currentTask = "Blueprint definitions Sub '$($scopeDisplayName)' ('$scopeId')" + $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).count -gt 0) { + foreach ($blueprint in $scopeBlueprintDefinitionResult) { - $subscriptionsThatGeneratedCost = $htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" + 1 - $htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" = $subscriptionsThatGeneratedCost + if (-not $($htCacheDefinitionsBlueprint).($blueprint.Id)) { + ($script:htCacheDefinitionsBlueprint).($blueprint.Id) = @{} + } - $consumptionDataSubscriptions = $htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" += $subscriptionId.group - $htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" = $consumptionDataSubscriptions + $blueprintName = $blueprint.name + $blueprintId = $blueprint.Id + $blueprintDisplayName = $blueprint.properties.displayName + $blueprintDescription = $blueprint.properties.description + $blueprintScoped = "/subscriptions/$($scopeId)" - $resourceTypesThatGeneratedCost = $htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" - foreach ($resourceType in $resourceTypes) { - if ($resourceTypesThatGeneratedCost -notcontains $resourceType) { - $resourceTypesThatGeneratedCost += $resourceType - } - } - $htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" = $resourceTypesThatGeneratedCost + $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 + ) - #currencyIndependent - $resourcesThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent + ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count - $htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent = $resourcesThatGeneratedCostCurrencyIndependent + $currentTask = "Blueprint assignments '$($scopeDisplayName)' ('$scopeId')" + $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' - $subscriptionsThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent + 1 - $htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent = $subscriptionsThatGeneratedCostCurrencyIndependent + $addRowToTableDone = $false + if (($subscriptionBlueprintAssignmentsResult).count -gt 0) { + foreach ($subscriptionBlueprintAssignment in $subscriptionBlueprintAssignmentsResult) { - $consumptionDataSubscriptionsCurrencyIndependent = $htManagementGroupsCost.($parentMg).consumptionDataSubscriptions += $subscriptionId.group - $htManagementGroupsCost.($parentMg).consumptionDataSubscriptions = $consumptionDataSubscriptionsCurrencyIndependent + if (-not ($htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id)) { + ($script:htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id) = @{} + ($script:htCacheAssignmentsBlueprint).($subscriptionBlueprintAssignment.Id) = $subscriptionBlueprintAssignment + } - $resourceTypesThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent - foreach ($resourceType in $resourceTypes) { - if ($resourceTypesThatGeneratedCostCurrencyIndependent -notcontains $resourceType) { - $resourceTypesThatGeneratedCostCurrencyIndependent += $resourceType - } - } - $htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent = $resourceTypesThatGeneratedCostCurrencyIndependent - } - } - } + 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/.*', '' + } - $totalCost = 0 - $tenantSummaryConsumptionDataGrouped = $currency.group | Group-Object -property ConsumedService, ChargeType, MeterCategory - $subsCount = ($tenantSummaryConsumptionDataGrouped.group.subscriptionId | Sort-Object -Unique | Measure-Object).Count - $consumedServiceCount = ($tenantSummaryConsumptionDataGrouped.group.consumedService | Sort-Object -Unique | Measure-Object).Count - $resourceCount = ($tenantSummaryConsumptionDataGrouped.group.ResourceId | Sort-Object -Unique | Measure-Object).Count - foreach ($consumptionline in $tenantSummaryConsumptionDataGrouped) { + $currentTask = " Blueprint definitions related to Blueprint assignments '$($scopeDisplayName)' ('$scopeId')" + $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 + } - $costConsumptionLine = ($consumptionline.group.PreTaxCost | Measure-Object -Sum).Sum + $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 + } + } - if ([math]::Round($costConsumptionLine, 2) -eq 0) { - $cost = $costConsumptionLine.ToString("0.0000") - } - else { - $cost = [math]::Round($costConsumptionLine, 2).ToString("0.00") - } + $returnObject = @{} + if ($addRowToTableDone) { + $returnObject.'addRowToTableDone' = @{} + } + return $returnObject +} +$funcDataCollectionBluePrintAssignmentsSub = $function:dataCollectionBluePrintAssignmentsSub.ToString() - $null = $arrayConsumptionData.Add([PSCustomObject]@{ - ConsumedService = ($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 - }) +function dataCollectionPolicyExemptions { + [CmdletBinding()]Param( + [string]$TargetMgOrSub, + [string]$scopeId, + [string]$scopeDisplayName + ) - $totalCost = $totalCost + $costConsumptionLine + $currentTask = "Policy exemptions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyExemptions?api-version=2020-07-01-preview" + } + if ($TargetMgOrSub -eq 'MG') { + $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' - } - if ([math]::Round($totalCost, 2) -eq 0) { - $totalCost = $totalCost - } - else { - $totalCost = [math]::Round($totalCost, 2).ToString("0.00") - } - $arrayTotalCostSummary += "$($totalCost) $($currency.Name) generated by $($resourceCount) Resources ($($consumedServiceCount) ResourceTypes) in $($subsCount) Subscriptions" - } - } - else { - Write-Host " No relevant consumption data entries (0)" - } + $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 } } - $endConsumptionData = Get-Date - Write-Host "Getting Consumption data duration: $((NEW-TIMESPAN -Start $startConsumptionData -End $endConsumptionData).TotalSeconds) seconds" - #endregion dataprocessingConsumption } +} +$funcDataCollectionPolicyExemptions = $function:dataCollectionPolicyExemptions.ToString() - #region dataprocessingDefinitionCaching - $startDefinitionsCaching = Get-Date - Write-Host "Caching built-in Policy and RBAC Role definitions" +function dataCollectionPolicyDefinitions { + [CmdletBinding()]Param( + [string]$TargetMgOrSub, + [string]$scopeId, + [string]$scopeDisplayName + ) - $arrayBuiltInCaching = @('PolicyDefinitions', 'PolicySetDefinitions', 'RoleDefinitions') + $currentTask = "Policy definitions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'" + } + if ($TargetMgOrSub -eq 'MG') { + $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' - $arrayBuiltInCaching | ForEach-Object -parallel { + $scopePolicyDefinitions = $requestPolicyDefinitionAPI.where( { $_.properties.policyType -eq 'custom' } ) - $builtInCapability = $_ - #fromOtherFunctions - $arrayAzureManagementEndPointUrls = $using:arrayAzureManagementEndPointUrls - $checkContext = $using:checkContext - $htAzureEnvironmentRelatedUrls = $using:htAzureEnvironmentRelatedUrls - $htBearerAccessToken = $using:htBearerAccessToken - #Array&HTs - $htParameters = $using:htParameters - $arrayAPICallTracking = $using:arrayAPICallTracking - $htCacheDefinitionsPolicy = $using:htCacheDefinitionsPolicy - $htCacheDefinitionsPolicySet = $using:htCacheDefinitionsPolicySet - $htCacheDefinitionsRole = $using:htCacheDefinitionsRole - $htRoleDefinitionIdsUsedInPolicy = $using:htRoleDefinitionIdsUsedInPolicy - #Functions - $function:AzAPICall = $using:funcAzAPICall - $function:CreateBearerToken = $using:funcCreateBearerToken - $function:GetJWTDetails = $using:funcGetJWTDetails + 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 + } - if ($builtInCapability -eq "PolicyDefinitions") { - $currentTask = "Caching built-in Policy definitions" - #Write-Host " $currentTask" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'BuiltIn'" - $method = "GET" - $requestPolicyDefinitionAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask + foreach ($scopePolicyDefinition in $scopePolicyDefinitions) { + $hlpPolicyDefinitionId = ($scopePolicyDefinition.id).ToLower() - Write-Host " $($requestPolicyDefinitionAPI.Count) built-in Policy definitions returned" - $builtinPolicyDefinitions = $requestPolicyDefinitionAPI.where( { $_.properties.policyType -eq "BuiltIn" } ) + $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 + } + } - foreach ($builtinPolicyDefinition in $builtinPolicyDefinitions) { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()) = @{} - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Id = ($builtinPolicyDefinition.Id).ToLower() - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeMGLevel = "" - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Scope = "n/a" - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeMgSub = "n/a" - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeId = "n/a" - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).DisplayName = $builtinPolicyDefinition.Properties.displayname - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Description = $builtinPolicyDefinition.Properties.description - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Type = $builtinPolicyDefinition.Properties.policyType - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Category = $builtinPolicyDefinition.Properties.metadata.category - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).PolicyDefinitionId = ($builtinPolicyDefinition.Id).ToLower() - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).LinkToAzAdvertizer = "$($builtinPolicyDefinition.Properties.displayname)" - if ($builtinPolicyDefinition.Properties.metadata.deprecated -eq $true -or $builtinPolicyDefinition.Properties.displayname -like "``[Deprecated``]*") { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Deprecated = $builtinPolicyDefinition.Properties.metadata.deprecated - } - else { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Deprecated = $false - } - if ($builtinPolicyDefinition.Properties.metadata.preview -eq $true -or $builtinPolicyDefinition.Properties.displayname -like "``[*Preview``]*") { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Preview = $builtinPolicyDefinition.Properties.metadata.preview + if ($doIt) { + 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 + } + $htTemp.DisplayName = $($scopePolicyDefinition.Properties.displayname) + $htTemp.Description = $($policyDefinitionDescription) + $htTemp.Type = $($scopePolicyDefinition.Properties.policyType) + $htTemp.Category = $($scopePolicyDefinition.Properties.metadata.category) + $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 + } + #effects + if ($scopePolicyDefinition.properties.parameters.effect.defaultvalue) { + $htTemp.effectDefaultValue = $scopePolicyDefinition.properties.parameters.effect.defaultvalue + if ($scopePolicyDefinition.properties.parameters.effect.allowedValues) { + $htTemp.effectAllowedValue = $scopePolicyDefinition.properties.parameters.effect.allowedValues -join ',' } else { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Preview = $false + $htTemp.effectAllowedValue = 'n/a' } - #effects - if ($builtinPolicyDefinition.properties.parameters.effect.defaultvalue) { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = $builtinPolicyDefinition.properties.parameters.effect.defaultvalue - if ($builtinPolicyDefinition.properties.parameters.effect.allowedValues) { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = $builtinPolicyDefinition.properties.parameters.effect.allowedValues -join "," + $htTemp.effectFixedValue = 'n/a' + } + else { + if ($scopePolicyDefinition.properties.parameters.policyEffect.defaultValue) { + $htTemp.effectDefaultValue = $scopePolicyDefinition.properties.parameters.policyEffect.defaultvalue + if ($scopePolicyDefinition.properties.parameters.policyEffect.allowedValues) { + $htTemp.effectAllowedValue = $scopePolicyDefinition.properties.parameters.policyEffect.allowedValues -join ',' } else { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = "n/a" + $htTemp.effectAllowedValue = 'n/a' } - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = "n/a" + $htTemp.effectFixedValue = 'n/a' } else { - if ($builtinPolicyDefinition.properties.parameters.policyEffect.defaultValue) { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = $builtinPolicyDefinition.properties.parameters.policyEffect.defaultvalue - if ($builtinPolicyDefinition.properties.parameters.policyEffect.allowedValues) { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = $builtinPolicyDefinition.properties.parameters.policyEffect.allowedValues -join "," - } - else { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = "n/a" - } - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = "n/a" - } - else { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = $builtinPolicyDefinition.Properties.policyRule.then.effect - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = "n/a" - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = "n/a" - } + $htTemp.effectFixedValue = $scopePolicyDefinition.Properties.policyRule.then.effect + $htTemp.effectDefaultValue = 'n/a' + $htTemp.effectAllowedValue = 'n/a' } - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Json = $builtinPolicyDefinition + } + $htTemp.Json = $scopePolicyDefinition + ($script:htCacheDefinitionsPolicy).($hlpPolicyDefinitionId) = $htTemp - if (-not [string]::IsNullOrEmpty($builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds)) { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).RoleDefinitionIds = $builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds - foreach ($roledefinitionId in $builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds) { + + 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]$builtinPolicyDefinition.Id + $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = [array]$hlpPolicyDefinitionId } else { $usedInPolicies = $htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies - $usedInPolicies += $builtinPolicyDefinition.Id + $usedInPolicies += $hlpPolicyDefinitionId $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = $usedInPolicies } } - } - else { - ($htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).RoleDefinitionIds = "n/a" + else { + Write-Host "$currentTask $($hlpPolicyDefinitionId) Finding: empty roleDefinitionId in roledefinitionIds" + } } } - } - - if ($builtInCapability -eq "PolicySetDefinitions") { - - $currentTask = "Caching built-in PolicySet definitions" - #Write-Host " $currentTask" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'BuiltIn'" - $method = "GET" - $requestPolicySetDefinitionAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask - - $builtinPolicySetDefinitions = $requestPolicySetDefinitionAPI.where( { $_.properties.policyType -eq "BuiltIn" } ) - Write-Host " $($requestPolicySetDefinitionAPI.Count) built-in PolicySet definitions returned" - foreach ($builtinPolicySetDefinition in $builtinPolicySetDefinitions) { - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()) = @{} - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Id = ($builtinPolicySetDefinition.Id).ToLower() - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeMGLevel = "" - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Scope = "n/a" - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeMgSub = "n/a" - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeId = "n/a" - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).DisplayName = $builtinPolicySetDefinition.Properties.displayname - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Description = $builtinPolicySetDefinition.Properties.description - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Type = $builtinPolicySetDefinition.Properties.policyType - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Category = $builtinPolicySetDefinition.Properties.metadata.category - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).PolicyDefinitionId = ($builtinPolicySetDefinition.Id).ToLower() - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).LinkToAzAdvertizer = "$($builtinPolicySetDefinition.Properties.displayname)" - $arrayPolicySetPolicyIdsToLower = @() - $arrayPolicySetPolicyIdsToLower = foreach ($policySetPolicy in $builtinPolicySetDefinition.properties.policydefinitions.policyDefinitionId) { - ($policySetPolicy).ToLower() - } - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).PolicySetPolicyIds = $arrayPolicySetPolicyIdsToLower - if ($builtinPolicySetDefinition.Properties.metadata.deprecated -eq $true -or $builtinPolicySetDefinition.Properties.displayname -like "``[Deprecated``]*") { - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Deprecated = $builtinPolicySetDefinition.Properties.metadata.deprecated - } - else { - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Deprecated = $false - } - if ($builtinPolicySetDefinition.Properties.metadata.preview -eq $true -or $builtinPolicySetDefinition.Properties.displayname -like "``[*Preview``]*") { - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Preview = $builtinPolicySetDefinition.Properties.metadata.preview - } - else { - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Preview = $false - } - ($htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Json = $builtinPolicySetDefinition + else { + ($script:htCacheDefinitionsPolicy).($hlpPolicyDefinitionId).RoleDefinitionIds = 'n/a' } - } - - if ($builtInCapability -eq "RoleDefinitions") { - $currentTask = "Caching built-in Role definitions" - #Write-Host " $currentTask" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)subscriptions/$($checkContext.Subscription.Id)/providers/Microsoft.Authorization/roleDefinitions?api-version=2018-07-01&`$filter=type eq 'BuiltInRole'" - $method = "GET" - $requestRoleDefinitionAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask - - Write-Host " $($requestRoleDefinitionAPI.Count) built-in Role definitions returned" - foreach ($roleDefinition in $requestRoleDefinitionAPI) { - if ( - ( - $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/roleassignments/write' -or - $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/roleassignments/*' -or - $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/*/write' -or - $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/*' -or - $roleDefinition.properties.permissions.actions -contains '*/write' -or - $roleDefinition.properties.permissions.actions -contains '*' - ) -and ( - $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/roleassignments/write' -and - $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/roleassignments/*' -and - $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/*/write' -and - $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/*' -and - $roleDefinition.properties.permissions.notActions -notcontains '*/write' -and - $roleDefinition.properties.permissions.notActions -notcontains '*' - ) - ) { - $roleCapable4RoleAssignmentsWrite = $true + + #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 } - else { - $roleCapable4RoleAssignmentsWrite = $false + } + 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 } - - ($htCacheDefinitionsRole).($roleDefinition.name) = @{} - ($htCacheDefinitionsRole).($roleDefinition.name).Id = ($roleDefinition.name) - ($htCacheDefinitionsRole).($roleDefinition.name).Name = ($roleDefinition.properties.roleName) - ($htCacheDefinitionsRole).($roleDefinition.name).IsCustom = $false - ($htCacheDefinitionsRole).($roleDefinition.name).AssignableScopes = ($roleDefinition.properties.assignableScopes) - ($htCacheDefinitionsRole).($roleDefinition.name).Actions = ($roleDefinition.properties.permissions.actions) - ($htCacheDefinitionsRole).($roleDefinition.name).NotActions = ($roleDefinition.properties.permissions.notActions) - ($htCacheDefinitionsRole).($roleDefinition.name).DataActions = ($roleDefinition.properties.permissions.dataActions) - ($htCacheDefinitionsRole).($roleDefinition.name).NotDataActions = ($roleDefinition.properties.permissions.notDataActions) - ($htCacheDefinitionsRole).($roleDefinition.name).Json = ($roleDefinition.properties) - ($htCacheDefinitionsRole).($roleDefinition.name).LinkToAzAdvertizer = "$($roleDefinition.properties.roleName)" - ($htCacheDefinitionsRole).($roleDefinition.name).RoleCanDoRoleAssignments = $roleCapable4RoleAssignmentsWrite } + #endregion namingValidation } } - $builtInPolicyDefinitionsCount = $htCacheDefinitionsPolicy.Keys.Count - - $endDefinitionsCaching = Get-Date - Write-Host "Caching built-in definitions duration: $((NEW-TIMESPAN -Start $startDefinitionsCaching -End $endDefinitionsCaching).TotalSeconds) seconds" - #endregion dataprocessingDefinitionCaching -} -else { - Write-Host "Run Info:" - Write-Host " Creating HierarchyMap only" -ForegroundColor Green + $returnObject = @{} + $returnObject.'PolicyDefinitionsScopedCount' = $PolicyDefinitionsScopedCount + return $returnObject } +$funcDataCollectionPolicyDefinitions = $function:dataCollectionPolicyDefinitions.ToString() + +function dataCollectionPolicySetDefinitions { + [CmdletBinding()]Param( + [string]$TargetMgOrSub, + [string]$scopeId, + [string]$scopeDisplayName + ) -$arrayEntitiesFromAPISubscriptionsCount = ($arrayEntitiesFromAPI.where( { $_.type -eq "/subscriptions" -and $_.properties.parentNameChain -contains $ManagementGroupId } ) | Sort-Object -Property id -Unique).count -$arrayEntitiesFromAPIManagementGroupsCount = ($arrayEntitiesFromAPI.where( { $_.type -eq "Microsoft.Management/managementGroups" -and $_.properties.parentNameChain -contains $ManagementGroupId } ) | Sort-Object -Property id -Unique).count + 1 + $currentTask = "PolicySet definitions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'" + } + if ($TargetMgOrSub -eq 'MG') { + $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' -if ($htParameters.HierarchyMapOnly -eq $false) { - Write-Host "Collecting custom data" - $startDataCollection = Get-Date + $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 + } - DataCollection -mgId $ManagementGroupId + foreach ($scopePolicySetDefinition in $scopePolicySetDefinitions) { + $hlpPolicySetDefinitionId = ($scopePolicySetDefinition.id).ToLower() - if ($htParameters.LargeTenant -eq $false -or $htParameters.PolicyAtScopeOnly -eq $false -or $htParameters.RBACAtScopeOnly -eq $false) { - if (($checkContext).Tenant.Id -ne $ManagementGroupId) { - AddRowToTable ` - -level (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain | Measure-Object).Count - 1) ` - -mgName $getMgParentName ` - -mgId $getMgParentId ` - -mgParentId "'upperScopes'" ` - -mgParentName "upperScopes" + $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 ($htParameters.LargeTenant -eq $true -or $htParameters.PolicyAtScopeOnly -eq $true) { - if (($checkContext).Tenant.Id -ne $ManagementGroupId) { - $currentTask = "Policy assignments ('$($ManagementGroupId)')" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementgroups/$($ManagementGroupId)/providers/Microsoft.Authorization/policyAssignments?`$filter=atScope()&api-version=2021-06-01" - $method = "GET" - $upperScopesPolicyAssignments = AzAPICall -uri $uri -method $method -currentTask $currentTask -caller "CustomDataCollection" + if ($doIt) { + if (($scopePolicySetDefinition.Properties.description).length -eq 0) { + $policySetDefinitionDescription = 'no description given' + } + else { + $policySetDefinitionDescription = $scopePolicySetDefinition.Properties.description + } - $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) { + $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 + } + $htTemp.DisplayName = $($scopePolicySetDefinition.Properties.displayname) + $htTemp.Description = $($policySetDefinitionDescription) + $htTemp.Type = $($scopePolicySetDefinition.Properties.policyType) + $htTemp.Category = $($scopePolicySetDefinition.Properties.metadata.category) + $htTemp.PolicyDefinitionId = $hlpPolicySetDefinitionId + $arrayPolicySetPolicyIdsToLower = @() + $arrayPolicySetPolicyIdsToLower = foreach ($policySetPolicy in $scopePolicySetDefinition.properties.policydefinitions.policyDefinitionId) { + ($policySetPolicy).ToLower() + } + $htTemp.PolicySetPolicyIds = $arrayPolicySetPolicyIdsToLower + $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 - 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 - } + #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 + } + } + } + } - if ($L0mgmtGroupPolicyAssignment.identity) { - $PolicyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId - } - else { - $PolicyAssignmentIdentity = "n/a" - } + $returnObject = @{} + $returnObject.'PolicySetDefinitionsScopedCount' = $PolicySetDefinitionsScopedCount + return $returnObject +} +$funcDataCollectionPolicySetDefinitions = $function:dataCollectionPolicySetDefinitions.ToString() - if ($Def.Type -eq "Custom") { - $policyDefintionScope = $Def.Scope - $policyDefintionScopeMgSub = $Def.ScopeMgSub - $policyDefintionScopeId = $Def.ScopeId - } - else { - $policyDefintionScope = "n/a" - $policyDefintionScopeMgSub = "n/a" - $policyDefintionScopeId = "n/a" - } +function dataCollectionPolicyAssignmentsMG { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $hierarchyLevel, + $mgParentId, + $mgParentName, + $mgAscSecureScoreResult, + $PolicyDefinitionsScopedCount, + $PolicySetDefinitionsScopedCount + ) - $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 - } - } + $addRowToTableDone = $false + $currentTask = "Policy assignments '$($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' - if (($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message) { - $nonComplianceMessage = ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message - } - else { - $nonComplianceMessage = "" - } + $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) - $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) " - } + if (-not $htMgAtScopePolicyAssignments.($scopeId)) { + $script:htMgAtScopePolicyAssignments.($scopeId) = @{} + $script:htMgAtScopePolicyAssignments.($scopeId).AssignmentsCount = $L0mgmtGroupPolicyAssignmentsPolicyAndPolicySetAtScopeCount + } - #mgSecureScore - $mgAscSecureScoreResult = "" + foreach ($L0mgmtGroupPolicyAssignment in $L0mgmtGroupPolicyAssignments) { - 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 - } + $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/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.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -OR $L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') { - if ($L0mgmtGroupPolicyAssignment.identity) { - $PolicyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId - } - else { - $PolicyAssignmentIdentity = "n/a" - } + #policy + if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/') { + $policyVariant = 'Policy' + $policyDefinitionId = ($L0mgmtGroupPolicyAssignment.properties.policydefinitionid).ToLower() - if ($Def.Type -eq "Custom") { - $policyDefintionScope = $Def.Scope - $policyDefintionScopeMgSub = $Def.ScopeMgSub - $policyDefintionScopeId = $Def.ScopeId - } - else { - $policyDefintionScope = "n/a" - $policyDefintionScopeMgSub = "n/a" - $policyDefintionScopeId = "n/a" - } + $policyDefinitionSplitted = $policyDefinitionId.split('/') + $hlpPolicyDefinitionScope = $policyDefinitionSplitted[4] - $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 ( ($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 ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy) { - $updatedBy = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedBy + + if ($policyDefinition.Type -eq 'Custom') { + $policyDefintionScope = $policyDefinition.Scope + $policyDefintionScopeMgSub = $policyDefinition.ScopeMgSub + $policyDefintionScopeId = $policyDefinition.ScopeId } - if ($L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn) { - $updatedOn = $L0mgmtGroupPolicyAssignment.properties.metadata.updatedOn + else { + $policyDefintionScope = 'n/a' + $policyDefintionScopeMgSub = 'n/a' + $policyDefintionScopeId = 'n/a' } - } - if (($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message) { - $nonComplianceMessage = ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId })).Message + $policyAvailability = '' + $policyDisplayName = ($policyDefinition).DisplayName + $policyDescription = ($policyDefinition).Description + $policyDefinitionType = ($policyDefinition).Type + $policyCategory = ($policyDefinition).Category + $policyDefinitionEffectDefault = ($policyDefinition).effectDefaultValue + $policyDefinitionEffectFixed = ($policyDefinition).effectFixedValue } else { - $nonComplianceMessage = "" + #test + Write-Host " attention! $scopeDisplayName ($scopeId); policyAssignment '$($L0mgmtGroupPolicyAssignment.Id)' policyDefinition (Policy) could not be found: '$($policyDefinitionId)' -retry" + start-sleep -seconds 1 } - - $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) " + } + 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 } - - 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 + Throw 'Error - AzGovViz: check the last console output for details' } } - } - } - } - - if ($htParameters.LargeTenant -eq $true -or $htParameters.RBACAtScopeOnly -eq $true) { + #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' - #RoleAssignment API (system metadata e.g. createdOn) - $currentTask = "Role assignments API '$($ManagementGroupId)'" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01" - $method = "GET" - $roleAssignmentsFromAPI = AzAPICall -uri $uri -method $method -currentTask $currentTask + $policyDefintionScope = "/$($policyDefinitionSplitted[1])/$($policyDefinitionSplitted[2])/$($policyDefinitionSplitted[3])/$($hlpPolicyDefinitionScope)" + $policyDefintionScopeMgSub = 'Mg' + $policyDefintionScopeId = $hlpPolicyDefinitionScope - if ($roleAssignmentsFromAPI.Count -gt 0) { - $principalsToResolve = @() - $principalsToResolve = foreach ($ra in $roleAssignmentsFromAPI.properties | Sort-Object -Property principalId -Unique) { - if (-not $htPrincipals.($ra.principalId)) { - $ra.principalId + $policyDisplayName = 'unknown' + $policyDescription = 'unknown' + $policyDefinitionType = 'likely Custom' + $policyCategory = 'unknown' + $policyDefinitionEffectDefault = 'unknown' + $policyDefinitionEffectFixed = 'unknown' } - } - if ($principalsToResolve.Count -gt 0) { - ResolveObjectIds -objectIds $principalsToResolve - } - } + $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 + } - #$upperScopesRoleAssignments = GetRoleAssignments -Scope "/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)" -scopeDetails "getRoleAssignments upperScopes (Mg)" - $upperScopesRoleAssignments = $roleAssignmentsFromAPI + if ($L0mgmtGroupPolicyAssignment.identity) { + $policyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId + } + else { + $policyAssignmentIdentity = 'n/a' + } - $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 - } + $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 + } + } - foreach ($upperScopesRoleAssignment in $upperScopesRoleAssignments) { + if ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.Message) { + $nonComplianceMessage = $L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.Message + } + else { + $nonComplianceMessage = '' + } - $roleAssignmentId = ($upperScopesRoleAssignment.id).ToLower() + $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) " + } - if ($upperScopesRoleAssignment.properties.scope -ne "/providers/Microsoft.Management/managementGroups/$ManagementGroupId") { - $roleDefinitionId = $upperScopesRoleAssignment.properties.roleDefinitionId - $roleDefinitionIdGuid = $roleDefinitionId -replace ".*/" + $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 ` + -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 + } - if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) { - $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'" - } - else { - $roleDefinitionName = ($htCacheDefinitionsRole).($roleDefinitionIdGuid).Name - } + #policySet + if ($L0mgmtGroupPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') { + $policyVariant = 'PolicySet' + $policySetDefinitionId = ($L0mgmtGroupPolicyAssignment.properties.policydefinitionid).ToLower() + $policySetDefinitionSplitted = $policySetDefinitionId.split('/') + $hlpPolicySetDefinitionScope = $policySetDefinitionSplitted[4] - if (($htPrincipals.($upperScopesRoleAssignment.properties.principalId).displayName).length -eq 0) { - $roleAssignmentIdentityDisplayname = "n/a" - } - else { - if ($htPrincipals.($upperScopesRoleAssignment.properties.principalId).type -eq "User") { - if ($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 ($htParameters.DoNotShowRoleAssignmentsUserData -eq $false) { - $roleAssignmentIdentitySignInName = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).signInName + $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 { - $roleAssignmentIdentitySignInName = "scrubbed" + $policySetDefintionScope = 'n/a' + $policySetDefintionScopeMgSub = 'n/a' + $policySetDefintionScopeId = 'n/a' } + $policySetDisplayName = $policySetDefinition.DisplayName + $policySetDescription = $policySetDefinition.Description + $policySetDefinitionType = $policySetDefinition.Type + $policySetCategory = $policySetDefinition.Category } else { - $roleAssignmentIdentitySignInName = $htPrincipals.($upperScopesRoleAssignment.properties.principalId).signInName + #test + #Write-Host "pa '($L0mgmtGroupPolicyAssignment.Id)' scope: '$($scopeId)' - policySetDefinition not available: $policySetDefinitionId" + start-sleep -seconds 1 } } - $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 + 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' + $policySetCategory = 'unknown' } - $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 + $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' } - if ($upperScopesRoleAssignment.properties.updatedOn) { - $updatedOn = $upperScopesRoleAssignment.properties.updatedOn + else { + $policyAssignmentDescription = $L0mgmtGroupPolicyAssignment.Properties.Description } - $createdOnUnformatted = $upperScopesRoleAssignment.properties.createdOn - if (($checkContext).Tenant.Id -ne $ManagementGroupId) { - $levelToUse = (($htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain).Count - 1) - $toUseAsmgName = $getMgParentName - $toUseAsmgId = $getMgParentId - $toUseAsmgParentId = "'upperScopes'" - $toUseAsmgParentName = "upperScopes" + if ($L0mgmtGroupPolicyAssignment.identity) { + $policyAssignmentIdentity = $L0mgmtGroupPolicyAssignment.identity.principalId } 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" - } - } - } - - $endDataCollection = Get-Date - Write-Host "Collecting custom data duration: $((NEW-TIMESPAN -Start $startDataCollection -End $endDataCollection).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startDataCollection -End $endDataCollection).TotalSeconds) seconds)" -} -else { - foreach ($entity in $arrayEntitiesFromAPI) { - if ($entity.properties.parentNameChain -contains $ManagementGroupID -or $entity.Name -eq $ManagementGroupId) { - if ($entity.type -eq "/subscriptions") { - AddRowToTable ` - -level (($htEntities.($entity.name).ParentNameChain).Count - 1) ` - -mgName $htEntities.(($entity.properties.parent.Id) -replace '.*/').displayName ` - -mgId (($entity.properties.parent.Id) -replace '.*/') ` - -mgParentId $htEntities.(($entity.properties.parent.Id) -replace '.*/').Parent ` - -mgParentName $htEntities.(($entity.properties.parent.Id) -replace '.*/').ParentDisplayName ` - -Subscription $htEntities.($entity.name).DisplayName ` - -SubscriptionId $htEntities.($entity.name).Id - } - if ($entity.type -eq "Microsoft.Management/managementGroups") { - AddRowToTable ` - -level ($htEntities.($entity.name).ParentNameChain).Count ` - -mgName $entity.properties.displayname ` - -mgId $entity.Name ` - -mgParentId $htEntities.($entity.name).Parent ` - -mgParentName $htEntities.($entity.name).ParentDisplayName - } - } - } -} - -$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))" - -if ($htParameters.NoResources -eq $false) { - $totaldurationSubResourcesAddArray = ($arraySubResourcesAddArrayDuration.DurationSec | Measure-Object -sum).Sum - Write-Host "Collecting custom data total duration writing the subResourcesArray: $totaldurationSubResourcesAddArray seconds" -} - - -#APITracking -$APICallTrackingCount = ($arrayAPICallTrackingCustomDataCollection).Count -$APICallTrackingRetriesCount = ($arrayAPICallTrackingCustomDataCollection.where( { $_.TryCounter -gt 0 } )).Count -$APICallTrackingRestartDueToDuplicateNextlinkCounterCount = ($arrayAPICallTrackingCustomDataCollection.where( { $_.RestartDueToDuplicateNextlinkCounter -gt 0 } )).Count -Write-Host "Collecting custom data APICalls (Management) total count: $APICallTrackingCount ($APICallTrackingRetriesCount retries; $APICallTrackingRestartDueToDuplicateNextlinkCounterCount nextLinkReset)" - -Write-Host "Preparing Arrays" -$startPreparingArrays = Get-Date -$optimizedTableForPathQuery = ($newTable | Select-Object -Property level, mg*, subscription*) | Sort-Object -Property level, mgid, subscriptionId -Unique -$hlperOptimizedTableForPathQuery = $optimizedTableForPathQuery.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) } ) -$optimizedTableForPathQueryMgAndSub = ($hlperOptimizedTableForPathQuery | Select-Object -Property level, mg*, subscription*) | Sort-Object -Property level, mgid, mgname, mgparentId, mgparentName, subscriptionId, subscription -Unique -$optimizedTableForPathQueryMg = ($optimizedTableForPathQuery.where( { [String]::IsNullOrEmpty($_.SubscriptionId) } ) | Select-Object -Property level, mgid, mgName, mgparentid, mgparentName) | Sort-Object -Property level, mgid, mgname, mgparentId, mgparentName -Unique -$optimizedTableForPathQuerySub = ($hlperOptimizedTableForPathQuery | Select-Object -Property subscription*) | Sort-Object -Property subscriptionId -Unique - -$htMgDetails = @{} -foreach ($entry in $optimizedTableForPathQuery) { - $htMgDetails.($entry.mgId) = @{} - $mgSubs = $optimizedTableForPathQueryMgAndSub.where( { $_.mgId -eq $entry.mgId } ) - $htMgDetails.($entry.mgId).subscriptionsCount = $mgSubs.Count - $htMgDetails.($entry.mgId).subscriptions = $mgSubs - $htMgDetails.($entry.mgId).details = $entry - $mgChildren = ($optimizedTableForPathQueryMg.where( { $_.mgParentId -eq $entry.mgId } )).MgId - $htMgDetails.($entry.mgId).mgChildren = $mgChildren - $htMgDetails.($entry.mgId).mgChildrenCount = $mgChildren.Count -} - -$htSubDetails = @{} -foreach ($entry in $optimizedTableForPathQueryMgAndSub) { - $htSubDetails.($entry.SubscriptionId) = @{} - $subMg = $optimizedTableForPathQueryMgAndSub.where( { $_.SubscriptionId -eq $entry.SubscriptionId } ) - $htSubDetails.($entry.SubscriptionId).details = $subMg -} - -$endPreparingArrays = Get-Date -Write-Host "Preparing Arrays duration: $((NEW-TIMESPAN -Start $startPreparingArrays -End $endPreparingArrays).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startPreparingArrays -End $endPreparingArrays).TotalSeconds) seconds)" - -if ($htParameters.HierarchyMapOnly -eq $false) { - - $rbacBaseQuery = $newTable.where( { -not [String]::IsNullOrEmpty($_.RoleDefinitionName) } ) | Sort-Object -Property RoleIsCustom, RoleDefinitionName | Select-Object -Property Level, Role*, mg*, Subscription* - $roleAssignmentsUniqueById = $rbacBaseQuery | Sort-Object -Property RoleAssignmentId -Unique - - - - #region dataprocessingAADGroups - if (-not $NoAADGroupsResolveMembers) { - - $htAADGroupsDetails = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $htAADGroupsExeedingMemberLimit = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $arrayGroupRoleAssignmentsOnServicePrincipals = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $arrayGroupRequestResourceNotFound = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $arrayProgressedAADGroups = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - Write-Host "Resolving AAD Groups (for which a RBAC Role assignment exists)" - Write-Host " Users known as Guest count: $($htUserTypesGuest.Keys.Count) (before Resolving AAD Groups)" - $startAADGroupsResolveMembers = Get-Date - function GetGroupmembers($aadGroupId, $aadGroupDisplayName) { - if (-not $htAADGroupsDetails.($aadGroupId)) { - $script:htAADGroupsDetails.$aadGroupId = @{} - $script:htAADGroupsDetails.($aadGroupId).Id = $aadGroupId - $script:htAADGroupsDetails.($aadGroupId).displayname = $aadGroupDisplayName - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/beta/groups/$($aadGroupId)/transitiveMembers" - $method = "GET" - $aadGroupMembers = AzAPICall -uri $uri -method $method -currentTask "getGroupMembers $($aadGroupId)" -getGroup $true - - if ($aadGroupMembers -eq "Request_ResourceNotFound") { - $null = $arrayGroupRequestResourceNotFound.Add([PSCustomObject]@{ - groupId = $aadGroupId - }) + $policyAssignmentIdentity = 'n/a' } - $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 $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 - } + $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 } } - #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" - } - } - } + if (($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId } )).Message) { + $nonComplianceMessage = ($L0mgmtGroupPolicyAssignment.Properties.nonComplianceMessages.where( { -not $_.policyDefinitionReferenceId } )).Message + } + else { + $nonComplianceMessage = '' } - $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 + $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 ` + -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 } } - $funcGetGroupmembers = $function:GetGroupmembers.ToString() + } - $optimizedTableForAADGroupsQuery = ($roleAssignmentsUniqueById.where( { $_.RoleAssignmentIdentityObjectType -eq "Group" } ) | Select-Object -Property RoleAssignmentIdentityObjectId, RoleAssignmentIdentityDisplayname) | Sort-Object -Property RoleAssignmentIdentityObjectId -Unique - $aadGroupsCount = ($optimizedTableForAADGroupsQuery).Count + $returnObject = @{} + if ($addRowToTableDone) { + $returnObject.'addRowToTableDone' = @{} + } + return $returnObject +} +$funcDataCollectionPolicyAssignmentsMG = $function:dataCollectionPolicyAssignmentsMG.ToString() - if ($aadGroupsCount -gt 0) { +function dataCollectionPolicyAssignmentsSub { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $hierarchyLevel, + $childMgDisplayName, + $childMgId, + $childMgParentId, + $childMgParentName, + $mgAscSecureScoreResult, + $subscriptionQuotaId, + $subscriptionState, + $subscriptionASCSecureScore, + $subscriptionTags, + $subscriptionTagsCount, + $PolicyDefinitionsScopedCount, + $PolicySetDefinitionsScopedCount + ) - 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 } - } + $currentTask = "Policy assignments '$($scopeDisplayName)' ('$scopeId')" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyAssignments?api-version=2021-06-01" + $method = 'GET' - Write-Host " processing $($aadGroupsCount) AAD Groups with Role assignments (indicating progress in steps of $indicator)" + $addRowToTableDone = $false + if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy -eq $false) { + $L1mgmtGroupSubPolicyAssignments = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' - $optimizedTableForAADGroupsQuery | ForEach-Object -Parallel { - $aadGroupIdWithRoleAssignment = $_ - #region UsingVARs - #fromOtherFunctions - $AADGroupMembersLimit = $using:AADGroupMembersLimit - $arrayAzureManagementEndPointUrls = $using:arrayAzureManagementEndPointUrls - $checkContext = $using:checkContext - $htAzureEnvironmentRelatedUrls = $using:htAzureEnvironmentRelatedUrls - $htBearerAccessToken = $using:htBearerAccessToken - #Array&HTs - $htParameters = $using:htParameters - $htAADGroupsDetails = $using:htAADGroupsDetails - $arrayGroupRoleAssignmentsOnServicePrincipals = $using:arrayGroupRoleAssignmentsOnServicePrincipals - $arrayGroupRequestResourceNotFound = $using:arrayGroupRequestResourceNotFound - $arrayProgressedAADGroups = $using:arrayProgressedAADGroups - $arrayAPICallTracking = $using:arrayAPICallTracking - $htAADGroupsExeedingMemberLimit = $using:htAADGroupsExeedingMemberLimit - $indicator = $using:indicator - $htUserTypesGuest = $using:htUserTypesGuest - $htServicePrincipals = $using:htServicePrincipals - #Functions - $function:AzAPICall = $using:funcAzAPICall - $function:CreateBearerToken = $using:funcCreateBearerToken - $function:GetJWTDetails = $using:funcGetJWTDetails - $function:GetGroupmembers = $using:funcGetGroupmembers - #endregion UsingVARs + $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' - $rndom = Get-Random -Minimum 10 -Maximum 750 - start-sleep -Millisecond $rndom + $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" } ) + } - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/beta/groups/$($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId)/transitiveMembers/`$count" - $method = "GET" - $aadGroupMembersCount = AzAPICall -uri $uri -method $method -currentTask "getGroupMembersCount $($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId)" -listenOn "Content" -consistencyLevel "eventual" -getGroupMembersCount $True + $L1mgmtGroupSubPolicyAssignmentsPolicyAndPolicySetAtScopeCount = ($L1mgmtGroupSubPolicyAssignmentsPolicyAtScopeCount + $L1mgmtGroupSubPolicyAssignmentsPolicySetAtScopeCount) - if ($aadGroupMembersCount -eq "Request_ResourceNotFound") { - $null = $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 - } - } + 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 + } - $null = $script:arrayProgressedAADGroups.Add($aadGroupIdWithRoleAssignment.RoleAssignmentIdentityObjectId) - $processedAADGroupsCount = $null - $processedAADGroupsCount = ($arrayProgressedAADGroups).Count - if ($processedAADGroupsCount) { - if ($processedAADGroupsCount % $indicator -eq 0) { - Write-Host " $processedAADGroupsCount AAD Groups processed" - } + #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) = @{} } - } -ThrottleLimit ($ThrottleLimit * 2) - #[System.GC]::Collect() - } - else { - Write-Host " processing $($aadGroupsCount) AAD Groups with Role assignments" + $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id).displayNameInvalidChars = ($namingValidationResult -join '') + $script:htNamingValidation.PolicyAssignment.($L1mgmtGroupSubPolicyAssignment.Id).displayName = $L1mgmtGroupSubPolicyAssignment.Properties.DisplayName + } } - - $arrayGroupRequestResourceNotFoundCount = ($arrayGroupRequestResourceNotFound).Count - if ($arrayGroupRequestResourceNotFoundCount -gt 0) { - Write-Host "$arrayGroupRequestResourceNotFoundCount Groups could not be checked for Memberships" + 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 - Write-Host " Collected $($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)" - } - #endregion dataprocessingAADGroups + if ($L1mgmtGroupSubPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/' -OR $L1mgmtGroupSubPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policySetDefinitions/') { - #region Application - Write-Host "Processing Service Principals - Applications" - $servicePrincipalsOfTypeApplication = $htServicePrincipals.Keys.where( { $htServicePrincipals.($_).servicePrincipalType -eq "Application" -and $htServicePrincipals.($_).appOwnerOrganizationId -eq $checkContext.Subscription.TenantId } ) - if ($UserType -eq "Guest") { - #checking if Guest has enough permissions - $app4Test = $htServicePrincipals.($servicePrincipalsOfTypeApplication[0]) - $currentTask = "getApp Test $($app4Test.appId)" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/applications?`$filter=appId eq '$($app4Test.appId)'" - $method = "GET" - $testGetApplication = AzAPICall -uri $uri -method $method -currentTask $currentTask -getApp $true - 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 - $htAppDetails = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} - $currentDateUTC = (Get-Date).ToUniversalTime() - $arrayApplicationRequestResourceNotFound = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $servicePrincipalsOfTypeApplication | ForEach-Object -Parallel { + #policy + if ($L1mgmtGroupSubPolicyAssignment.properties.policyDefinitionId -match '/providers/Microsoft.Authorization/policyDefinitions/') { + $policyVariant = 'Policy' + $policyDefinitionId = ($L1mgmtGroupSubPolicyAssignment.properties.policydefinitionid).ToLower() - #region UsingVARs - $currentDateUTC = $using:currentDateUTC - #fromOtherFunctions - $arrayAzureManagementEndPointUrls = $using:arrayAzureManagementEndPointUrls - $checkContext = $using:checkContext - $htAzureEnvironmentRelatedUrls = $using:htAzureEnvironmentRelatedUrls - $htBearerAccessToken = $using:htBearerAccessToken - #Array&HTs - $htParameters = $using:htParameters - $arrayApplicationRequestResourceNotFound = $using:arrayApplicationRequestResourceNotFound - $htAppDetails = $using:htAppDetails - $htServicePrincipals = $using:htServicePrincipals - #$arrayProgressedServicePrincipals = $using:arrayProgressedServicePrincipals - $arrayAPICallTracking = $using:arrayAPICallTracking - #$indicator = $using:indicator - #Functions - $function:AzAPICall = $using:funcAzAPICall - $function:CreateBearerToken = $using:funcCreateBearerToken - $function:GetJWTDetails = $using:funcGetJWTDetails - #endregion UsingVARs + if (($htCacheDefinitionsPolicy).($policyDefinitionId)) { + $policyAvailability = '' - $sp = $htServicePrincipals.($_) + #handling some strange scenario where the synchronized hashTable responds fragments?! + $tryCounter = 0 + do { + $tryCounter++ + $policyAssignmentsPolicyDefinition = ($htCacheDefinitionsPolicy).($policyDefinitionId) - $currentTask = "getApp $($sp.appId)" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).MSGraphUrl)/v1.0/applications?`$filter=appId eq '$($sp.appId)'" - $method = "GET" - $getApplication = AzAPICall -uri $uri -method $method -currentTask $currentTask -getApp $true + if (($policyAssignmentsPolicyDefinition).Type -eq 'Custom' -or ($policyAssignmentsPolicyDefinition).Type -eq 'Builtin') { + $policyReturnedFromHt = $true - if ($getApplication -eq "Request_ResourceNotFound") { - $null = $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 + $policyDisplayName = ($policyAssignmentsPolicyDefinition).DisplayName + $policyDescription = ($policyAssignmentsPolicyDefinition).Description + $policyDefinitionType = ($policyAssignmentsPolicyDefinition).Type + $policyCategory = ($policyAssignmentsPolicyDefinition).Category + $policyDefinitionEffectDefault = ($policyAssignmentsPolicyDefinition).effectDefaultValue + $policyDefinitionEffectFixed = ($policyAssignmentsPolicyDefinition).effectFixedValue - $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++ + 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 } - elseif ($passwordExpiryTotalDays -lt $AADServicePrincipalExpiryWarningDays) { - $appPasswordCredentialsGracePeriodExpiryCount++ + + if ($policyDefinitionType -eq 'Custom') { + $policyDefintionScope = ($policyAssignmentsPolicyDefinition).Scope + $policyDefintionScopeMgSub = ($policyAssignmentsPolicyDefinition).ScopeMgSub + $policyDefintionScopeId = ($policyAssignmentsPolicyDefinition).ScopeId } - else { - if ($passwordExpiryTotalDays -gt 730) { - $appPasswordCredentialsExpiryOKMoreThan2YearsCount++ - } - else { - $appPasswordCredentialsExpiryOKCount++ - } + + if ($policyDefinitionType -eq 'Builtin') { + $policyDefintionScope = 'n/a' + $policyDefintionScopeMgSub = 'n/a' + $policyDefintionScopeId = 'n/a' } } - $script:htAppDetails.($sp.id).appPasswordCredentialsExpiredCount = $appPasswordCredentialsExpiredCount - $script:htAppDetails.($sp.id).appPasswordCredentialsGracePeriodExpiryCount = $appPasswordCredentialsGracePeriodExpiryCount - $script:htAppDetails.($sp.id).appPasswordCredentialsExpiryOKCount = $appPasswordCredentialsExpiryOKCount - $script:htAppDetails.($sp.id).appPasswordCredentialsExpiryOKMoreThan2YearsCount = $appPasswordCredentialsExpiryOKMoreThan2YearsCount + else { + Write-Host " **INCONSISTENCY! processing policyId:'$policyDefinitionId'; policyAss:'$($L1mgmtGroupSubPolicyAssignment.Id)'; policyAssignmentsPolicyDefinition.Type: '$($policyAssignmentsPolicyDefinition.Type)'" + start-sleep -seconds 1 + } } - - $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++ + 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 { - if ($keyCredentialExpiryTotalDays -gt 730) { - $appKeyCredentialsExpiryOKMoreThan2YearsCount++ - } - else { - $appKeyCredentialsExpiryOKCount++ - } + 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" } } - $script:htAppDetails.($sp.id).appKeyCredentialsExpiredCount = $appKeyCredentialsExpiredCount - $script:htAppDetails.($sp.id).appKeyCredentialsGracePeriodExpiryCount = $appKeyCredentialsGracePeriodExpiryCount - $script:htAppDetails.($sp.id).appKeyCredentialsExpiryOKCount = $appKeyCredentialsExpiryOKCount - $script:htAppDetails.($sp.id).appKeyCredentialsExpiryOKMoreThan2YearsCount = $appKeyCredentialsExpiryOKMoreThan2YearsCount + $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' + $policyCategory = 'unknown' + $policyDefinitionEffectDefault = 'unknown' + $policyDefinitionEffectFixed = 'unknown' } - } - } -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)" - } - #endregion Application - - #region ManagedIdentity - Write-Host "Processing Service Principals - Managed Identities" - $startSPMI = Get-Date - $htManagedIdentityForPolicyAssignment = @{} - $htPolicyAssignmentManagedIdentity = @{} - $htManagedIdentityDisplayName = @{} - $servicePrincipalsOfTypeManagedIdentity = $htServicePrincipals.Keys.where( { $htServicePrincipals.($_).servicePrincipalType -eq "ManagedIdentity" } ) - $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/*") { - $htManagedIdentityForPolicyAssignment.($hlpSp.Id) = @{} - $htManagedIdentityForPolicyAssignment.($hlpSp.Id).policyAssignmentId = $usageentry.ToLower() - $htPolicyAssignmentManagedIdentity.($usageentry.ToLower()) = @{} - $htPolicyAssignmentManagedIdentity.($usageentry.ToLower()).miObjectId = $hlpSp.id - if (-not $htManagedIdentityDisplayName.($hlpSp.displayName)) { - $htManagedIdentityDisplayName.("$($hlpSp.displayName)_$($usageentry.ToLower())") = $hlpSp + $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' } } } - } - } - } - $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)" - #endregion ManagedIdentity - #resourcesAll - $resourcesAllGroupedBySubcriptionId = $resourcesAll | Group-Object -property subscriptionId - - #region dataprocessingCreateTagListArray - $startTagListArray = Get-Date - Write-Host "Creating TagList array" - - $tagsSubRgResCount = ($htAllTagList."AllScopes".Keys).Count - $tagsSubsriptionCount = ($htAllTagList."Subscription".Keys).Count - $tagsResourceGroupCount = ($htAllTagList."ResourceGroup".Keys).Count - $tagsResourceCount = ($htAllTagList."Resource".Keys).Count - Write-Host " Total Number of ALL unique Tag Names: $tagsSubRgResCount" - Write-Host " Total Number of Subscription unique Tag Names: $tagsSubsriptionCount" - Write-Host " Total Number of ResourceGroup unique Tag Names: $tagsResourceGroupCount" - Write-Host " Total Number of Resource unique Tag Names: $tagsResourceCount" + $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 + } - foreach ($tagScope in $htAllTagList.keys) { - foreach ($tagScopeTagName in $htAllTagList.($tagScope).keys) { - $null = $arrayTagList.Add([PSCustomObject]@{ - Scope = $tagScope - TagName = ($tagScopeTagName) - TagCount = $htAllTagList.($tagScope).($tagScopeTagName) - }) - } - } - $endTagListArray = Get-Date - Write-Host "Creating TagList array duration: $((NEW-TIMESPAN -Start $startTagListArray -End $endTagListArray).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startTagListArray -End $endTagListArray).TotalSeconds) seconds)" - #endregion dataprocessingCreateTagListArray + if ($L1mgmtGroupSubPolicyAssignment.identity) { + $PolicyAssignmentIdentity = $L1mgmtGroupSubPolicyAssignment.identity.principalId + } + else { + $PolicyAssignmentIdentity = 'n/a' + } - if ($htParameters.NoResources -eq $false) { - #region dataprocessingDiagnosticsCapable - Write-Host "Checking Resource Types Diagnostics capability (1st party only)" - $startResourceDiagnosticsCheck = Get-Date - if (($resourcesAll).count -gt 0) { + $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 + } + } - $startGroupResourceIdsByType = Get-Date - $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 to process" - $resourceTypesSummarizedArray = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - - $resourceTypesDiagnosticsArray = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - $resourceTypesUnique.where( { $_.Name -like "microsoft.*" }) | ForEach-Object -Parallel { - $resourceTypesUniqueGroup = $_ - $resourcetype = $resourceTypesUniqueGroup.Name - #region UsingVARs - #fromOtherFunctions - $arrayAzureManagementEndPointUrls = $using:arrayAzureManagementEndPointUrls - $checkContext = $using:checkContext - $htAzureEnvironmentRelatedUrls = $using:htAzureEnvironmentRelatedUrls - $htBearerAccessToken = $using:htBearerAccessToken - #Array&HTs - $htParameters = $using:htParameters - $ExludedResourceTypesDiagnosticsCapable = $using:ExludedResourceTypesDiagnosticsCapable - $resourceTypesDiagnosticsArray = $using:resourceTypesDiagnosticsArray - $htResourceTypesUniqueResource = $using:htResourceTypesUniqueResource - $resourceTypesSummarizedArray = $using:resourceTypesSummarizedArray - $arrayAPICallTracking = $using:arrayAPICallTracking - #Functions - $function:AzAPICallDiag = $using:funcAzAPICallDiag - $function:CreateBearerToken = $using:funcCreateBearerToken - $function:GetJWTDetails = $using:funcGetJWTDetails - #endregion UsingVARs + if ($L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.Message) { + $nonComplianceMessage = $L1mgmtGroupSubPolicyAssignment.Properties.nonComplianceMessages.Message + } + else { + $nonComplianceMessage = '' + } - $skipThisResourceType = $false - if (($ExludedResourceTypesDiagnosticsCapable).Count -gt 0) { - foreach ($excludedResourceType in $ExludedResourceTypesDiagnosticsCapable) { - if ($excludedResourceType -eq $resourcetype) { - $skipThisResourceType = $true - } + $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) " } - if ($skipThisResourceType -eq $false) { - $resourceCount = $resourceTypesUniqueGroup.Count + $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 ` + -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('/') - #thx @Jim Britt (Microsoft) https://github.com/JimGBritt/AzurePolicy/tree/master/AzureMonitor/Scripts Create-AzDiagPolicy.ps1 - $responseJSON = '' - $logCategories = @() - $metrics = $false - $logs = $false + if (($htCacheDefinitionsPolicySet).($policySetDefinitionId)) { + $policyAvailability = '' - $resourceAvailability = ($resourceCount - 1) - $counterTryForResourceType = 0 + #handling some strange behavior where the synchronized hashTable responds fragments?! + $tryCounter = 0 do { - $counterTryForResourceType++ - if ($resourceCount -gt 1) { - $resourceId = $resourceTypesUniqueGroup.Group.Id[$resourceAvailability] + $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 + $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 { - $resourceId = $resourceTypesUniqueGroup.Group.Id + #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' + $policySetCategory = 'unknown' - $resourceAvailability = $resourceAvailability - 1 - $currentTask = "Checking if ResourceType '$resourceType' is capable for Resource Diagnostics using $counterTryForResourceType ResourceId: '$($resourceId)'" - $uri = "$(($htAzureEnvironmentRelatedUrls).($checkContext.Environment.Name).ResourceManagerUrl)$($resourceId)/providers/microsoft.insights/diagnosticSettingsCategories?api-version=2021-05-01-preview" - $method = "GET" - AzAPICallDiag -uri $uri -method $method -currentTask $currentTask -resourceType $resourcetype -resourceId $resourceId - } - until ($resourceAvailability -lt 0 -or $responseJSON -ne "meanwhile_deleted") - - if ($resourceAvailability -lt 0 -and $responseJSON -eq "meanwhile_deleted") { - Write-Host "tried for all available resourceIds ($($resourceCount)) for resourceType $resourceType, but seems all resources meanwhile have been deleted" - $null = $script:resourceTypesDiagnosticsArray.Add([PSCustomObject]@{ - ResourceType = $resourcetype - Metrics = "n/a - resourcesMeanwhileDeleted" - Logs = "n/a - resourcesMeanwhileDeleted" - LogCategories = "n/a" - ResourceCount = $resourceCount - }) + if ($policySetDefinitionId -like '/providers/microsoft.management/managementgroups/*') { + $hlpPolicySetDefinitionScope = $policySetDefinitionSplitted[4] + $policySetDefintionScope = "/$($policySetDefinitionSplitted[1])/$($policySetDefinitionSplitted[2])/$($policySetDefinitionSplitted[3])/$($hlpPolicySetDefinitionScope)" + $policySetDefintionScopeMgSub = 'Mg' + $policySetDefintionScopeId = $hlpPolicySetDefinitionScope } else { - if ($responseJSON) { - foreach ($response in $responseJSON.value) { - if ($response.properties.categoryType -eq "Metrics") { - $metrics = $true - } - if ($response.properties.categoryType -eq "Logs") { - $logs = $true - $logCategories += $response.name - } - } - } + $hlpPolicySetDefinitionScope = $policySetDefinitionSplitted[2] + $policySetDefintionScope = "/$($policySetDefinitionSplitted[1])/$($hlpPolicySetDefinitionScope)" + $policySetDefintionScopeMgSub = 'Sub' + $policySetDefintionScopeId = $hlpPolicySetDefinitionScope - $null = $script:resourceTypesDiagnosticsArray.Add([PSCustomObject]@{ - ResourceType = $resourcetype - Metrics = $metrics - Logs = $logs - LogCategories = $logCategories - ResourceCount = $resourceCount - }) } + 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 { - Write-Host "Skipping ResourceType $($resourcetype) as per parameter '-ExludedResourceTypesDiagnosticsCapable'" + $PolicyAssignmentDescription = $L1mgmtGroupSubPolicyAssignment.Properties.Description } - } -ThrottleLimit $ThrottleLimit - #[System.GC]::Collect() - } - 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)" - #endregion dataprocessingDiagnosticsCapable - } - - Write-Host "Create helper hash table" - $startHelperHt = Get-Date - $tenantAllPolicySets = ($htCacheDefinitionsPolicySet).Values - $tenantAllPolicySetsCount = ($tenantAllPolicySets).count - if ($tenantAllPolicySetsCount -gt 0) { - foreach ($policySet in $tenantAllPolicySets) { - $PolicySetPolicyIds = $policySet.PolicySetPolicyIds - foreach ($PolicySetPolicyId in $PolicySetPolicyIds) { - if ($policySet.LinkToAzAdvertizer) { - $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer = "$($policySet.LinkToAzAdvertizer) ($($policySet.PolicyDefinitionId))" + if ($L1mgmtGroupSubPolicyAssignment.identity) { + $PolicyAssignmentIdentity = $L1mgmtGroupSubPolicyAssignment.identity.principalId } else { - $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer = "$($policySet.DisplayName) ($($policySet.PolicyDefinitionId))" + $PolicyAssignmentIdentity = 'n/a' } - $hlper4CSVOutput = "$($policySet.DisplayName) ($($policySet.PolicyDefinitionId))" - if (-not $htPoliciesUsedInPolicySets.($PolicySetPolicyId)) { - $htPoliciesUsedInPolicySets.($PolicySetPolicyId) = @{} - $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet = [array]$hlperDisplayNameWithOrWithoutLinkToAzAdvertizer - $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV = [array]$hlper4CSVOutput + + $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 { - $array = $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet - $array += $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer - $arrayCSV = $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV - $arrayCSV += $hlper4CSVOutput - $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet = $array - $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV = $arrayCSV + $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 ` + -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 } } } - $endHelperHt = Get-Date - Write-Host "Create helper hash table duration: $((NEW-TIMESPAN -Start $startHelperHt -End $endHelperHt).TotalSeconds) seconds" - + $returnObject = @{} + if ($addRowToTableDone) { + $returnObject.'addRowToTableDone' = @{} + } + return $returnObject } -#endregion runDataCollection +$funcDataCollectionPolicyAssignmentsSub = $function:dataCollectionPolicyAssignmentsSub.ToString() -#region createoutputs +function dataCollectionRoleDefinitions { + [CmdletBinding()]Param( + [string]$TargetMgOrSub, + [string]$scopeId, + [string]$scopeDisplayName + ) -#region BuildHTML -#testhelper -#$fileTimestamp = (Get-Date -Format $FileTimeStampFormat) + $currentTask = "Custom Role definitions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-07-01&`$filter=type eq 'CustomRole'" + } + if ($TargetMgOrSub -eq 'MG') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-07-01&`$filter=type eq 'CustomRole'" + } + $method = 'GET' + $scopeCustomRoleDefinitions = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' -$startBuildHTML = Get-Date -Write-Host "Building HTML" -$html = $null + foreach ($scopeCustomRoleDefinition in $scopeCustomRoleDefinitions) { + if (-not $($htCacheDefinitionsRole).($scopeCustomRoleDefinition.name)) { -#preQueries -Write-Host "processing Helper Queries" -$startHelperQueries = Get-Date + 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 + } -$parentMgBaseQuery = ($optimizedTableForPathQueryMg.where( { $_.MgParentId -eq $getMgParentId } )) -$parentMgNamex = $parentMgBaseQuery.mgParentName | Get-Unique -$parentMgIdx = $parentMgBaseQuery.mgParentId | Get-Unique -$ManagementGroupIdCaseSensitived = (($optimizedTableForPathQueryMg.where( { $_.MgId -eq $ManagementGroupId } )).mgId) | Get-Unique + $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 -#region filename -if ($htParameters.onAzureDevOpsOrGitHubActions -eq $true) { - if ($htParameters.HierarchyMapOnly -eq $true) { - $fileName = "AzGovViz_HierarchyMapOnly_$($ManagementGroupIdCaseSensitived)" - } - elseif ($htParameters.ManagementGroupsOnly -eq $true) { - $fileName = "AzGovViz_ManagementGroupsOnly_$($ManagementGroupIdCaseSensitived)" - } - else { - $fileName = "AzGovViz_$($ManagementGroupIdCaseSensitived)" + #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 + } + } + } } } -else { - if ($htParameters.HierarchyMapOnly -eq $true) { - $fileName = "AzGovViz_HierarchyMapOnly_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupIdCaseSensitived)" - } - elseif ($htParameters.ManagementGroupsOnly -eq $true) { - $fileName = "AzGovViz_ManagementGroupsOnly_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupIdCaseSensitived)" +$funcDataCollectionRoleDefinitions = $function:dataCollectionRoleDefinitions.ToString() + +function dataCollectionRoleAssignmentsMG { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $hierarchyLevel, + $mgParentId, + $mgParentName, + $mgAscSecureScoreResult + ) + + $addRowToTableDone = $false + #PIM MGRoleAssignmentSchedules + $currentTask = "getARMRoleAssignmentSchedules '$($scopeDisplayName)' ('$($scopeId)')" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentSchedules?api-version=2020-10-01-preview" + $method = 'GET' + $roleAssignmentSchedulesFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' + + if ($roleAssignmentSchedulesFromAPI -eq 'ResourceNotOnboarded' -or $roleAssignmentSchedulesFromAPI -eq 'TenantNotOnboarded' -or $roleAssignmentSchedulesFromAPI -eq 'InvalidResourceType') { + #Write-Host "Scope '$($scopeDisplayName)' ('$scopeId') not onboarded in PIM" } else { - $fileName = "AzGovViz_$($ProductVersion)_$($fileTimestamp)_$($ManagementGroupIdCaseSensitived)" + $roleAssignmentSchedules = ($roleAssignmentSchedulesFromAPI.where( { -not [string]::IsNullOrEmpty($_.properties.roleAssignmentScheduleRequestId) })) + $roleAssignmentSchedulesCount = $roleAssignmentSchedules.Count + if ($roleAssignmentSchedulesCount -gt 0) { + $htRoleAssignmentsPIM = @{} + foreach ($roleAssignmentSchedule in $roleAssignmentSchedules) { + $keyName = "$(($roleAssignmentSchedule.properties.scope).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.principal.id).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.roleDefinition.id).ToLower())" + $htRoleAssignmentsPIM.($keyName) = $roleAssignmentSchedule.properties + } + } } -} -#endregion filename -#region BuildCSV -Write-Host "Exporting CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName).csv'" -$startBuildCSV = Get-Date + #RoleAssignment API MG + $currentTask = "Role assignments API '$($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' -$outprops = $newtable[0].PSObject.Properties.Name -$outprops.Set($outprops.IndexOf('PolicyAssignmentNotScopes'), @{L = 'PolicyAssignmentNotScopes'; E = { ($_.PolicyAssignmentNotScopes -join "$CsvDelimiterOpposite ") } }) -if ($CsvExportUseQuotesAsNeeded) { - $newTable | Sort-Object -Property level, mgId, SubscriptionId, PolicyAssignmentId, RoleAssignmentId, BlueprintId, BlueprintAssignmentId | Select-Object -Property $outprops -ExcludeProperty PolicyAssignmentParameters | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded -} -else { - $newTable | Sort-Object -Property level, mgId, SubscriptionId, PolicyAssignmentId, RoleAssignmentId, BlueprintId, BlueprintAssignmentId | Select-Object -Property $outprops -ExcludeProperty PolicyAssignmentParameters | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).csv" -Delimiter "$csvDelimiter" -NoTypeInformation -} + 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 (-not $htParameters.HierarchyMapOnly -and -not $htParameters.ManagementGroupsOnly -and $htParameters.NoResources -eq $false) { - if (-not $NoCsvExport) { - #DataCollection Export of All Resources - 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 + if ($principalsToResolve.Count -gt 0) { + ResolveObjectIds -objectIds $principalsToResolve + } } -} - -$endBuildCSV = Get-Date -Write-Host "Exporting CSV total duration: $((NEW-TIMESPAN -Start $startBuildCSV -End $endBuildCSV).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildCSV -End $endBuildCSV).TotalSeconds) seconds)" -#endregion BuildCSV + $L0mgmtGroupRoleAssignments = $roleAssignmentsFromAPI -if ($htParameters.HierarchyMapOnly -eq $false) { - #region preQueries - Write-Host " Building preQueries" + $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 (-not $htParameters.DoNotIncludeResourceGroupsOnPolicy) { - $policyBaseQuery = $newTable.where( { -not [String]::IsNullOrEmpty($_.PolicyVariant) } ) | Sort-Object -Property PolicyType, Policy | Select-Object -Property Level, Policy*, mg*, Subscription* + if ($azAPICallConf['htParameters'].LargeTenant -eq $true -or $azAPICallConf['htParameters'].RBACAtScopeOnly -eq $true) { + $L0mgmtGroupRoleAssignments = $L0mgmtGroupRoleAssignments.where( { $_.properties.scope -eq "/providers/Microsoft.Management/managementGroups/$($scopeId)" } ) } else { - $policyBaseQuery = $newTable.where( { -not [String]::IsNullOrEmpty($_.PolicyVariant) -and ($_.PolicyAssignmentScopeMgSubRg -eq "Mg" -or $_.PolicyAssignmentScopeMgSubRg -eq "Sub") } ) | Sort-Object -Property PolicyType, Policy | Select-Object -Property Level, Policy*, mg*, Subscription* + #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() - $policyBaseQuerySubscriptions = $policyBaseQuery.where( { -not [String]::IsNullOrEmpty($_.SubscriptionId) } ) - $policyBaseQueryManagementGroups = $policyBaseQuery.where( { [String]::IsNullOrEmpty($_.SubscriptionId) } ) - $policyPolicyBaseQueryScopeInsights = ($policyBaseQuery | Select-Object Mg*, Subscription*, PolicyAssignmentAtScopeCount, PolicySetAssignmentAtScopeCount, PolicyAndPolicySetAssignmentAtScopeCount, PolicyAssignmentLimit -Unique) - $policyBaseQueryUniqueAssignments = $policyBaseQuery | Select-Object -Property Policy* | Sort-Object -Property PolicyAssignmentId -Unique - $policyAssignmentsOrphaned = $policyBaseQuery.where( { $_.PolicyAvailability -eq "na" } ) | Sort-Object -Property PolicyAssignmentId -Unique - $policyAssignmentsOrphanedCount = $policyAssignmentsOrphaned.Count - Write-Host " $policyAssignmentsOrphanedCount orphaned Policy assignments found" - - $htPolicyWithAssignmentsBase = @{} - foreach ($policyAssignment in $policyBaseQueryUniqueAssignments) { - if ($policyAssignment.PolicyVariant -eq "Policy") { - if (-not $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId)) { - $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId) = @{} - $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments = [array]$policyAssignment.PolicyAssignmentId + $keyName = "$(($L0mgmtGroupRoleAssignment.properties.scope).ToLower())-$(($L0mgmtGroupRoleAssignment.properties.principalId).ToLower())-$(($L0mgmtGroupRoleAssignment.properties.roleDefinitionId).ToLower())" + if ($htRoleAssignmentsPIM.($keyName)) { + $hlperPim = $htRoleAssignmentsPIM.($keyName) + $pim = 'true' + $pimAssignmentType = $hlperPim.assignmentType + $pimSlotStart = $($hlperPim.startDateTime) + if ($hlperPim.endDateTime) { + $pimSlotEnd = $($hlperPim.endDateTime) } else { - $usedInAssignments = $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments - $usedInAssignments += $policyAssignment.PolicyAssignmentId - $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments = $usedInAssignments + $pimSlotEnd = 'eternity' } } - } - - $policyPolicySetBaseQueryUniqueAssignments = $policyBaseQueryUniqueAssignments.where( { $_.PolicyVariant -eq "PolicySet" } ) - $policyBaseQueryUniqueCustomDefinitions = ($policyBaseQuery.where( { $_.PolicyType -eq "Custom" } )) | select-object PolicyVariant, PolicyDefinitionId -Unique - $policyPolicyBaseQueryUniqueCustomDefinitions = ($policyBaseQueryUniqueCustomDefinitions.where( { $_.PolicyVariant -eq "Policy" } )).PolicyDefinitionId - $policyPolicySetBaseQueryUniqueCustomDefinitions = ($policyBaseQueryUniqueCustomDefinitions.where( { $_.PolicyVariant -eq "PolicySet" } )).PolicyDefinitionId - - $rbacBaseQueryArrayListNotGroupOwner = $rbacBaseQuery.where( { $_.RoleAssignmentIdentityObjectType -ne "Group" -and $_.RoleDefinitionName -eq "Owner" }) | Select-Object -Property mgid, SubscriptionId, RoleAssignmentId, RoleDefinitionName, RoleDefinitionId, RoleAssignmentIdentityObjectType, RoleAssignmentIdentityDisplayname, RoleAssignmentIdentitySignInName, RoleAssignmentIdentityObjectId - $rbacBaseQueryArrayListNotGroupUserAccessAdministrator = $rbacBaseQuery.where( { $_.RoleAssignmentIdentityObjectType -ne "Group" -and $_.RoleDefinitionName -eq "User Access Administrator" }) | Select-Object -Property mgid, SubscriptionId, RoleAssignmentId, RoleDefinitionName, RoleDefinitionId, RoleAssignmentIdentityObjectType, RoleAssignmentIdentityDisplayname, RoleAssignmentIdentitySignInName, RoleAssignmentIdentityObjectId - $roleAssignmentsForServicePrincipals = (($roleAssignmentsUniqueById.where( { $_.RoleAssignmentIdentityObjectType -eq "ServicePrincipal" }))) - $htRoleAssignmentsForServicePrincipals = @{} - foreach ($spWithRoleAssignment in $roleAssignmentsForServicePrincipals | Group-Object -Property RoleAssignmentIdentityObjectId) { - if (-not $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name)) { - $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name) = @{} - $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name).RoleAssignments = $spWithRoleAssignment.group + else { + $pim = 'false' + $pimAssignmentType = '' + $pimSlotStart = '' + $pimSlotEnd = '' } - } - $blueprintBaseQuery = ($newTable | Select-Object mgid, SubscriptionId, Blueprint*).where( { -not [String]::IsNullOrEmpty($_.BlueprintName) } ) - $mgsAndSubs = (($optimizedTableForPathQuery.where( { $_.mgId -ne "" -and $_.Level -ne "0" } )) | select-object MgId, SubscriptionId -unique) - - #region create array Policy definitions - $tenantAllPoliciesCount = (($htCacheDefinitionsPolicy).Values).count - $tenantCustomPolicies = (($htCacheDefinitionsPolicy).Values).where( { $_.Type -eq "Custom" } ) - $tenantCustomPoliciesCount = ($tenantCustomPolicies).count - #endregion create array Policy definitions + if (-not $htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace '.*/')) { + $script:htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace '.*/') = @{} + $script:htRoleAssignmentsFromAPIInheritancePrevention.($roleAssignmentId -replace '.*/').assignment = $L0mgmtGroupRoleAssignment + } - #region create array PolicySet definitions - $tenantCustomPolicySets = $tenantAllPolicySets.where( { $_.Type -eq "Custom" } ) - $tenantCustompolicySetsCount = ($tenantCustomPolicySets).count - #endregion create array PolicySet definitions + $roleDefinitionId = $L0mgmtGroupRoleAssignment.properties.roleDefinitionId + $roleDefinitionIdGuid = $roleDefinitionId -replace '.*/' - #region assignmentRgRes - $htPoliciesWithAssignmentOnRgRes = @{} - foreach ($policyAssignmentRgRes in ($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values | Sort-Object -Property id -Unique) { - $hlperPolDefId = (($policyAssignmentRgRes.properties.policyDefinitionId).ToLower()) - if (-not $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId)) { - $pscustomObj = [System.Collections.ArrayList]@() - $null = $pscustomObj.Add([PSCustomObject]@{ - PolicyAssignmentId = ($policyAssignmentRgRes.Id).ToLower() - PolicyAssignmentDisplayName = $policyAssignmentRgRes.properties.displayName - }) - $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId) = @{} - $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments = [array](($pscustomObj)) + if (-not ($htCacheDefinitionsRole).($roleDefinitionIdGuid)) { + $roleAssignmentsRoleDefinition = '' + $roleDefinitionName = "'This roleDefinition likely was deleted although a roleAssignment existed'" } else { - $pscustomObj = [System.Collections.ArrayList]@() - $null = $pscustomObj.Add([PSCustomObject]@{ - PolicyAssignmentId = ($policyAssignmentRgRes.Id).ToLower() - PolicyAssignmentDisplayName = $policyAssignmentRgRes.properties.displayName - }) - $array = @() - $array += $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments - $array += (($pscustomObj)) - $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments = $array + $roleAssignmentsRoleDefinition = ($htCacheDefinitionsRole).($roleDefinitionIdGuid) + $roleDefinitionName = $roleAssignmentsRoleDefinition.Name } - } - #endregion assignmentRgRes - - $tenantAllRoles = ($htCacheDefinitionsRole).Values - $tenantAllRolesCount = ($tenantAllRoles).Count - $tenantCustomRoles = $tenantAllRoles.where( { $_.IsCustom -eq $True } ) - $tenantCustomRolesCount = ($tenantCustomRoles).Count - $tenantAllRolesCanDoRoleAssignments = $tenantAllRoles.where( { $_.RoleCanDoRoleAssignments -eq $True } ) - $tenantAllRolesCanDoRoleAssignmentsCount = $tenantAllRolesCanDoRoleAssignments.Count - $mgSubRoleAssignmentsArrayFromHTValues = ($htCacheAssignmentsRole).Values.Assignment - if ($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - $rgResRoleAssignmentsArrayFromHTValues = ($htCacheAssignmentsRBACOnResourceGroupsAndResources).Values - } + $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 + } - #region diagnostics Mg/Sub - $diagnosticSettingsMg = $arrayDiagnosticSettingsMgSub.where( { $_.Scope -eq "Mg" -and $_.DiagnosticsPresent -eq "true" }) - $diagnosticSettingsMgCount = $diagnosticSettingsMg.Count - $diagnosticSettingsMgCategories = ($diagnosticSettingsMg.DiagnosticCategories | Group-Object -Property Category).Name - $diagnosticSettingsMgGrouped = $diagnosticSettingsMg | Group-Object -Property ScopeId - $diagnosticSettingsMgManagementGroupsCount = ($diagnosticSettingsMgGrouped | Measure-Object).Count + 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 + }) - foreach ($entry in $diagnosticSettingsMgGrouped) { - $dsgrouped = $entry.group | Group-Object -property DiagnosticSettingName + $htTemp = @{} + $htTemp.Assignment = $arrayRoleAssignment - foreach ($ds in $dsgrouped) { - $targetTypegrouped = $ds.group | Group-Object -property DiagnosticTargetType - foreach ($tt in $targetTypegrouped) { - if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name)) { - ($htDiagnosticSettingsMgSub).mg.($entry.Name) = @{} - } - if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name)) { - ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name) = @{} - } - if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name).($tt.Name)) { - ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name).($tt.Name) = $tt.group - } + 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 } - } - - foreach ($mg in $htManagementGroupsMgPath.Values) { - foreach ($mgWithDiag in ($htDiagnosticSettingsMgSub).mg.keys) { - if ($mg.ParentNameChain -contains $mgWithDiag) { - foreach ($diagSet in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).keys) { - foreach ($tt in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).($diagset).keys) { - foreach ($tid in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).($diagset).($tt)) { - $null = $script:diagnosticSettingsMg.Add([PSCustomObject]@{ - Scope = "Mg" - ScopeName = $mg.displayName - ScopeId = $mg.Id - ScopeMgPath = $htManagementGroupsMgPath.($mg.Id).pathDelimited - DiagnosticsInheritedOrnot = $true - DiagnosticsInheritedFrom = $mgWithDiag - DiagnosticsPresent = "true" - DiagnosticSettingName = $diagSet - DiagnosticTargetType = $tt - DiagnosticTargetId = $tid.DiagnosticTargetId - DiagnosticCategories = $tid.DiagnosticCategories - DiagnosticCategoriesHt = $tid.DiagnosticCategoriesHt - }) - } - } + 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 + } } - } - $mgsDiagnosticsApplicableCount = $diagnosticSettingsMg.Count - - $arrayMgsWithoutDiagnostics = [System.Collections.ArrayList]@() - foreach ($mg in $htManagementGroupsMgPath.Values) { - if ($diagnosticSettingsMg.ScopeId -notcontains $mg.Id) { - $null = $arrayMgsWithoutDiagnostics.Add([PSCustomObject]@{ - ScopeName = $mg.DisplayName - ScopeId = $mg.Id - ScopeMgPath = $mg.pathDelimited - }) + if (-not $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName) { + $roleAssignmentIdentitySignInName = 'n/a' } - } - $arrayMgsWithoutDiagnosticsCount = $arrayMgsWithoutDiagnostics.Count - - - $diagnosticSettingsSub = $arrayDiagnosticSettingsMgSub.where( { $_.Scope -eq "Sub" -and $_.DiagnosticsPresent -eq "true" }) - $diagnosticSettingsSubCount = $diagnosticSettingsSub.Count - $diagnosticSettingsSubNoDiag = $arrayDiagnosticSettingsMgSub.where( { $_.Scope -eq "Sub" -and $_.DiagnosticsPresent -eq "false" }) - $diagnosticSettingsSubNoDiagCount = $diagnosticSettingsSubNoDiag.Count - $diagnosticSettingsSubCategories = ($diagnosticSettingsSub.DiagnosticCategories | Group-Object -Property Category).Name - $diagnosticSettingsSubGrouped = $diagnosticSettingsSub | Group-Object -Property ScopeId - $diagnosticSettingsSubSubscriptionsCount = ($diagnosticSettingsSubGrouped | Measure-Object).Count - - foreach ($entry in $diagnosticSettingsSubGrouped) { - $dsgrouped = $entry.group | Group-Object -property DiagnosticSettingName - - foreach ($ds in $dsgrouped) { - $targetTypegrouped = $ds.group | Group-Object -property DiagnosticTargetType - foreach ($tt in $targetTypegrouped) { - if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name)) { - ($htDiagnosticSettingsMgSub).sub.($entry.Name) = @{} - } - if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name)) { - ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name) = @{} + else { + if ($htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).type -eq 'User') { + if ($azAPICallConf['htParameters'].DoNotShowRoleAssignmentsUserData -eq $false) { + $roleAssignmentIdentitySignInName = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName } - if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name).($tt.Name)) { - ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name).($tt.Name) = $tt.group + else { + $roleAssignmentIdentitySignInName = 'scrubbed' } } + else { + $roleAssignmentIdentitySignInName = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).signInName + } } - } - #endregion diagnostics Mg/Sub + $roleAssignmentIdentityObjectId = $L0mgmtGroupRoleAssignment.properties.principalId + $roleAssignmentIdentityObjectType = $htPrincipals.($L0mgmtGroupRoleAssignment.properties.principalId).type + $roleAssignmentScope = $L0mgmtGroupRoleAssignment.properties.scope + $roleAssignmentScopeName = $roleAssignmentScope -replace '.*/' + $roleAssignmentScopeType = 'MG' - #region DefenderPlans - $defenderPlansGroupedBySub = $arrayDefenderPlans | Sort-Object -Property subscriptionName | Group-Object -Property subscriptionName, subscriptionId, subscriptionMgPath - $subsDefenderPlansCount = ($defenderPlansGroupedBySub | Measure-Object).Count - $defenderCapabilities = ($arrayDefenderPlans.defenderPlan | Sort-Object -Unique) - $defenderCapabilitiesCount = $defenderCapabilities.Count - $defenderPlansGroupedByPlan = $arrayDefenderPlans | Group-Object -Property defenderPlan, defenderPlanTier - $defenderPlansGroupedByPlanCount = ($defenderPlansGroupedByPlan | Measure-Object).Count - if ($defenderPlansGroupedByPlan.Name -contains "ContainerRegistry, Standard" -or $defenderPlansGroupedByPlan.Name -contains "KubernetesService, Standard") { - if ($defenderPlansGroupedByPlan.Name -contains "ContainerRegistry, Standard") { - $defenderPlanDeprecatedContainerRegistry = $true + $roleSecurityCustomRoleOwner = 0 + if ($roleAssignmentsRoleDefinition.Actions -eq '*' -and (($roleAssignmentsRoleDefinition.NotActions)).length -eq 0 -and $roleAssignmentsRoleDefinition.IsCustom -eq $True) { + $roleSecurityCustomRoleOwner = 1 } - if ($defenderPlansGroupedByPlan.Name -contains "KubernetesService, Standard") { - $defenderPlanDeprecatedKubernetesService = $true + $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 } - } - #endregion DefenderPlans - $endHelperQueries = Get-Date - Write-Host " Pre Queries duration: $((NEW-TIMESPAN -Start $startHelperQueries -End $endHelperQueries).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startHelperQueries -End $endHelperQueries).TotalSeconds) seconds)" + $createdBy = '' + $createdOn = '' + $createdOnUnformatted = $null + $updatedBy = '' + $updatedOn = '' - #endregion preQueries + 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 - #region summarizeDataCollectionResults - $startSummarizeDataCollectionResults = Get-Date - Write-Host "Summary data collection" - $mgsDetails = ($optimizedTableForPathQueryMg | Select-Object Level, MgId -Unique) - $mgDepth = ($mgsDetails.Level | measure-object -maximum).Maximum - $totalMgCount = ($mgsDetails).count - $totalSubCount = ($optimizedTableForPathQuerySub).count - $totalSubOutOfScopeCount = ($outOfScopeSubscriptions).count - $totalSubIncludedAndExcludedCount = $totalSubCount + $totalSubOutOfScopeCount - $totalResourceCount = $($resourcesIdsAll.Count) + $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 + } - $totalPolicyAssignmentsCount = (($htCacheAssignmentsPolicy).keys).count + $returnObject = @{} + if ($addRowToTableDone) { + $returnObject.'addRowToTableDone' = @{} + } + return $returnObject +} +$funcDataCollectionRoleAssignmentsMG = $function:dataCollectionRoleAssignmentsMG.ToString() - $policyAssignmentsMg = (($htCacheAssignmentsPolicy).Values.where( { $_.AssignmentScopeMgSubRg -eq "Mg" } )) - $totalPolicyAssignmentsCountMg = $policyAssignmentsMg.Count +function dataCollectionRoleAssignmentsSub { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $hierarchyLevel, + $childMgDisplayName, + $childMgId, + $childMgParentId, + $childMgParentName, + $mgAscSecureScoreResult, + $subscriptionQuotaId, + $subscriptionState, + $subscriptionASCSecureScore, + $subscriptionTags, + $subscriptionTagsCount + ) + + $addRowToTableDone = $false + #Usage + $currentTask = "Role assignments usage metrics '$($scopeDisplayName)' ('$scopeId')" + $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' - $totalPolicyAssignmentsCountSub = (($htCacheAssignmentsPolicy).Values.where( { $_.AssignmentScopeMgSubRg -eq "Sub" } )).count + $script:htSubscriptionsRoleAssignmentLimit.($scopeId) = $roleAssignmentsUsage.roleAssignmentsLimit - if (-not $htParameters.DoNotIncludeResourceGroupsOnPolicy) { - $totalPolicyAssignmentsCountRg = (($htCacheAssignmentsPolicy).Values.where( { $_.AssignmentScopeMgSubRg -eq "Rg" -or $_.AssignmentScopeMgSubRg -eq "Res" } )).count - } - else { - $totalPolicyAssignmentsCountRg = (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values).count - $totalPolicyAssignmentsCount = $totalPolicyAssignmentsCount + $totalPolicyAssignmentsCountRg - } + #PIM SubscriptionRoleAssignmentSchedules + $currentTask = "Role assignment schedules API '$($scopeDisplayName)' ('$scopeId')" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentSchedules?api-version=2020-10-01-preview" + $method = 'GET' + $roleAssignmentSchedulesFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' - $totalRoleAssignmentsCount = (($htCacheAssignmentsRole).keys).count - $totalRoleAssignmentsCountTen = (($htCacheAssignmentsRole).keys.where( { ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq "Tenant" } )).count - $totalRoleAssignmentsCountMG = (($htCacheAssignmentsRole).keys.where( { ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq "MG" } )).count - $totalRoleAssignmentsCountSub = (($htCacheAssignmentsRole).keys.where( { ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq "Sub" } )).count - if (-not $htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - $totalRoleAssignmentsCountRG = (($htCacheAssignmentsRole).keys.where( { ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq "RG" } )).count - $totalRoleAssignmentsCountRes = (($htCacheAssignmentsRole).keys.where( { ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq "Res" } )).count - $totalRoleAssignmentsResourceGroupsAndResourcesCount = $totalRoleAssignmentsCountRG + $totalRoleAssignmentsCountRes + if ($roleAssignmentSchedulesFromAPI -eq 'ResourceNotOnboarded' -or $roleAssignmentSchedulesFromAPI -eq 'TenantNotOnboarded' -or $roleAssignmentSchedulesFromAPI -eq 'InvalidResourceType') { + #Write-Host "Scope '$($scopeDisplayName)' ('$scopeId') not onboarded in PIM" } else { - $totalRoleAssignmentsResourceGroupsAndResourcesCount = (($htCacheAssignmentsRBACOnResourceGroupsAndResources).values).count - $totalRoleAssignmentsCount = $totalRoleAssignmentsCount + $totalRoleAssignmentsResourceGroupsAndResourcesCount + $roleAssignmentSchedules = ($roleAssignmentSchedulesFromAPI.where( { -not [string]::IsNullOrEmpty($_.properties.roleAssignmentScheduleRequestId) })) + $roleAssignmentSchedulesCount = $roleAssignmentSchedules.Count + if ($roleAssignmentSchedulesCount -gt 0) { + $htRoleAssignmentsPIM = @{} + foreach ($roleAssignmentSchedule in $roleAssignmentSchedules) { + $keyName = "$(($roleAssignmentSchedule.properties.scope).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.principal.id).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.roleDefinition.id).ToLower())" + $htRoleAssignmentsPIM.($keyName) = $roleAssignmentSchedule.properties + } + } } - $totalRoleDefinitionsCustomCount = ((($htCacheDefinitionsRole).keys.where( { ($htCacheDefinitionsRole).($_).IsCustom -eq $True } ))).count - $totalBlueprintDefinitionsCount = ((($htCacheDefinitionsBlueprint).keys)).count - $totalBlueprintAssignmentsCount = (($htCacheAssignmentsBlueprint).keys).count - $totalResourceTypesCount = ($resourceTypesDiagnosticsArray).Count - - Write-Host " Total Management Groups: $totalMgCount (depth $mgDepth)" - $htDailySummary."ManagementGroups" = $totalMgCount - Write-Host " Total Subscriptions: $totalSubIncludedAndExcludedCount ($totalSubCount included; $totalSubOutOfScopeCount out-of-scope)" - $htDailySummary."Subscriptions" = $totalSubCount - $htDailySummary."SubscriptionsOutOfScope" = $totalSubOutOfScopeCount - Write-Host " Total Custom Policy definitions: $tenantCustomPoliciesCount" - $htDailySummary."PolicyDefinitionsCustom" = $tenantCustomPoliciesCount - Write-Host " Total Custom PolicySet definitions: $tenantCustompolicySetsCount" - $htDailySummary."PolicySetDefinitionsCustom" = $tenantCustompolicySetsCount - Write-Host " Total Policy assignments: $($totalPolicyAssignmentsCount)" - $htDailySummary."PolicyAssignments" = $totalPolicyAssignmentsCount - Write-Host " Total Policy assignments ManagementGroups $($totalPolicyAssignmentsCountMg)" - $htDailySummary."PolicyAssignments_ManagementGroups" = $totalPolicyAssignmentsCountMg - Write-Host " Total Policy assignments Subscriptions $($totalPolicyAssignmentsCountSub)" - $htDailySummary."PolicyAssignments_Subscriptions" = $totalPolicyAssignmentsCountSub - Write-Host " Total Policy assignments ResourceGroups: $($totalPolicyAssignmentsCountRg)" - $htDailySummary."PolicyAssignments_ResourceGroups" = $totalPolicyAssignmentsCountRg - Write-Host " Total Custom Role definitions: $totalRoleDefinitionsCustomCount" - $htDailySummary."RoleDefinitionsCustom" = $totalRoleDefinitionsCustomCount - Write-Host " Total Role assignments: $totalRoleAssignmentsCount" - $htDailySummary."TotalRoleAssignments" = $totalRoleAssignmentsCount - Write-Host " Total Role assignments (Tenant): $totalRoleAssignmentsCountTen" - $htDailySummary."TotalRoleAssignments_Tenant" = $totalRoleAssignmentsCountTen - Write-Host " Total Role assignments (ManagementGroups): $totalRoleAssignmentsCountMG" - $htDailySummary."TotalRoleAssignments_ManagementGroups" = $totalRoleAssignmentsCountMG - Write-Host " Total Role assignments (Subscriptions): $totalRoleAssignmentsCountSub" - $htDailySummary."TotalRoleAssignments_Subscriptions" = $totalRoleAssignmentsCountSub - Write-Host " Total Role assignments (ResourceGroups and Resources): $totalRoleAssignmentsResourceGroupsAndResourcesCount" - $htDailySummary."TotalRoleAssignments_RgRes" = $totalRoleAssignmentsResourceGroupsAndResourcesCount - Write-Host " Total Blueprint definitions: $totalBlueprintDefinitionsCount" - $htDailySummary."Blueprints" = $totalBlueprintDefinitionsCount - Write-Host " Total Blueprint assignments: $totalBlueprintAssignmentsCount" - $htDailySummary."BlueprintAssignments" = $totalBlueprintAssignmentsCount - Write-Host " Total Resources: $totalResourceCount" - $htDailySummary."Resources" = $totalResourceCount - Write-Host " Total Resource Types: $totalResourceTypesCount" - $htDailySummary."ResourceTypes" = $totalResourceTypesCount + #RoleAssignment API Sub + $currentTask = "Role assignments API '$($scopeDisplayName)' ('$scopeId')" + $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' - $rbacUnique = $rbacAll | Sort-Object -Property RoleAssignmentId -Unique - $rbacUniqueObjectIds = $rbacUnique | Sort-Object -Property ObjectId -Unique - $rbacUniqueObjectIdsNonPIM = $rbacUnique.where( { $_.RoleAssignmentPIMRelated -eq $false } ) | Sort-Object -Property ObjectId -Unique - $rbacUniqueObjectIdsPIM = $rbacUnique.where( { $_.RoleAssignmentPIMRelated -eq $true } ) | Sort-Object -Property ObjectId -Unique + $baseRoleAssignments = [System.Collections.ArrayList]@() + if ($roleAssignmentsFromAPI.Count -gt 0) { + foreach ($roleAssignmentFromAPI in $roleAssignmentsFromAPI) { - if ($rbacUniqueObjectIds.Count -gt 0) { - $rbacUniqueObjectIdsGrouped = $rbacUniqueObjectIds | Group-Object -Property ObjectType - foreach ($principalType in $rbacUniqueObjectIdsGrouped) { - #Write-Host "$($principalType.Name): $($principalType.Count)" - $htDailySummary."TotalUniquePrincipalWithPermission_$($principalType.Name)" = $principalType.Count + 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) + } } - $htDailySummary."TotalUniquePrincipalWithPermission_SP" = $rbacUniqueObjectIds.where( { $_.ObjectType -like "SP*" } ).count - $htDailySummary."TotalUniquePrincipalWithPermission_User" = $rbacUniqueObjectIds.where( { $_.ObjectType -like "User*" } ).count } - if ($rbacUniqueObjectIdsNonPIM.Count -gt 0) { - $rbacUniqueObjectIdsNonPIMGrouped = $rbacUniqueObjectIdsNonPIM | Group-Object -Property ObjectType - foreach ($principalType in $rbacUniqueObjectIdsNonPIMGrouped) { - #Write-Host "$($principalType.Name): $($principalType.Count)" - $htDailySummary."TotalUniquePrincipalWithPermissionStatic_$($principalType.Name)" = $principalType.Count - } - $htDailySummary."TotalUniquePrincipalWithPermissionStatic_SP" = $rbacUniqueObjectIdsNonPIM.where( { $_.ObjectType -like "SP*" } ).count - $htDailySummary."TotalUniquePrincipalWithPermissionStatic_User" = $rbacUniqueObjectIdsNonPIM.where( { $_.ObjectType -like "User*" } ).count + 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 ($rbacUniqueObjectIdsPIM.Count -gt 0) { - $rbacUniqueObjectIdsPIMGrouped = $rbacUniqueObjectIdsPIM | Group-Object -Property ObjectType - foreach ($principalType in $rbacUniqueObjectIdsPIMGrouped) { - #Write-Host "$($principalType.Name): $($principalType.Count)" - $htDailySummary."TotalUniquePrincipalWithPermissionPIM_$($principalType.Name)" = $principalType.Count + if ($principalsToResolve.Count -gt 0) { + ResolveObjectIds -objectIds $principalsToResolve } - $htDailySummary."TotalUniquePrincipalWithPermissionPIM_SP" = $rbacUniqueObjectIdsPIM.where( { $_.ObjectType -like "SP*" } ).count - $htDailySummary."TotalUniquePrincipalWithPermissionPIM_User" = $rbacUniqueObjectIdsPIM.where( { $_.ObjectType -like "User*" } ).count } - $endSummarizeDataCollectionResults = Get-Date - Write-Host " Summary data collection duration: $((NEW-TIMESPAN -Start $startSummarizeDataCollectionResults -End $endSummarizeDataCollectionResults).TotalSeconds) seconds" - #endregion summarizeDataCollectionResults -} -$html = @" - - - - - - - - - AzGovViz - - - - - - - - - - - - + $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 ($htParameters.HierarchyMapOnly -eq $false) { - if (-not $NoSingleSubscriptionOutput) { + 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/" } ) + } + } - if ($htParameters.onAzureDevOpsOrGitHubActions) { - $HTMLPath = "HTML-Subscriptions_$($ManagementGroupId)" - if (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($HTMLPath)") { - Write-Host " Cleaning old state (Pipeline only)" - Remove-Item -Recurse -Force "$($outputPath)$($DirectorySeparatorChar)$($HTMLPath)" + 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] + } + + $keyName = "$(($L1mgmtGroupSubRoleAssignment.properties.scope).ToLower())-$(($L1mgmtGroupSubRoleAssignment.properties.principalId).ToLower())-$(($L1mgmtGroupSubRoleAssignment.properties.roleDefinitionId).ToLower())" + if ($htRoleAssignmentsPIM.($keyName)) { + $hlperPim = $htRoleAssignmentsPIM.($keyName) + $pim = 'true' + $pimAssignmentType = $hlperPim.assignmentType + $pimSlotStart = $($hlperPim.startDateTime) + if ($hlperPim.endDateTime) { + $pimSlotEnd = $($hlperPim.endDateTime) + } + else { + $pimSlotEnd = 'eternity' } } else { - $HTMLPath = "HTML-Subscriptions_$($ManagementGroupId)_$($fileTimestamp)" - Write-Host " Creating new state ($($HTMLPath)) (local only))" + $pim = 'false' + $pimAssignmentType = '' + $pimSlotStart = '' + $pimSlotEnd = '' } - $null = new-item -Name $HTMLPath -ItemType directory -path $outputPath + if ($roleAssignmentId -like "/subscriptions/$($scopeId)/*") { - $htmlSubscriptionOnlyStart = $html - $htmlSubscriptionOnlyStart += @" - -
    -
    + #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 -
    -
    -

    ScopeInsights

    - -"@ + $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 + } - $htmlSubscriptionOnlyEnd = @" -
    -
    -
    - - - - - - - - - -"@ - } -} + 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 = '' -$html += @" - -
    -
    -
    -
    -

    HierarchyMap

    -

    - - - -

    -
    -"@ + 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 -$html += @" -
    -"@ +#region verifyAzAPICall +if ($AzAPICallVersion) { + Write-Host " Verify 'AzAPICall' ($AzAPICallVersion)" } else { - $html += @" - - - - - -
    -
    -"@ + Write-Host " Verify 'AzAPICall' (latest)" } -if ($htParameters.HierarchyMapOnly -eq $false) { +$maxRetry = 3 +$tryCount = 0 +do { + $tryCount++ + if ($tryCount -gt $maxRetry) { + Write-Host " Managing 'AzAPICall' failed (tried $($tryCount - 1)x)" + throw " Managing 'AzAPICall' failed" + } - $html += @" -
    -

    TenantSummary

    -"@ + $importAzAPICallModuleSuccess = $false + try { - $html | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $html = $null + if (-not $AzAPICallVersion) { + Write-Host ' Check latest module version' + try { + $AzAPICallVersion = (Find-Module -name AzAPICall).Version + Write-Host " Latest module version: $AzAPICallVersion" + } + catch { + Write-Host ' Check latest module version failed' + throw + } + } - $startSummary = Get-Date + try { + $azAPICallModuleDeviation = $false + $azAPICallModuleVersionLoaded = ((Get-Module -name AzAPICall).Version) + foreach ($moduleLoaded in $azAPICallModuleVersionLoaded) { + if ($moduleLoaded.toString() -ne $AzAPICallVersion) { + Write-Host " Deviating loaded version found ('$($moduleLoaded.toString())' != '$($AzAPICallVersion)')" + $azAPICallModuleDeviation = $true + } + else { + if ($azAPICallModuleVersionLoaded.count -eq 1) { + Write-Host " AzAPICall module ($($moduleLoaded.toString())) is already loaded" -ForegroundColor Green + $importAzAPICallModuleSuccess = $true + } + } + } - ProcessTenantSummary - #[System.GC]::Collect() + if ($azAPICallModuleDeviation) { + $importAzAPICallModuleSuccess = $false + try { + Write-Host " Remove-Module AzAPICall ($(($azAPICallModuleVersionLoaded -join ', ').ToString()))" + Remove-Module -Name AzAPICall -Force + } + catch { + Write-Host ' Remove-Module AzAPICall failed' + throw + } + } + } + catch { + #Write-Host ' AzAPICall module is not loaded' + } - $endSummary = Get-Date - Write-Host " Building TenantSummary duration: $((NEW-TIMESPAN -Start $startSummary -End $endSummary).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startSummary -End $endSummary).TotalSeconds) seconds)" + if (-not $importAzAPICallModuleSuccess) { + Write-Host " Try (#$tryCount) importing AzAPICall module ($AzAPICallVersion)" + if (($env:SYSTEM_TEAMPROJECTID -and $env:BUILD_REPOSITORY_ID) -or $env:GITHUB_ACTIONS) { + Import-Module ".\$($ScriptPath)\AzAPICallModule\AzAPICall\$($AzAPICallVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + Write-Host " Import PS module 'AzAPICall' ($($AzAPICallVersion)) succeeded" -ForegroundColor Green + } + else { + Import-Module -Name AzAPICall -RequiredVersion $AzAPICallVersion -Force + Write-Host " Import PS module 'AzAPICall' ($($AzAPICallVersion)) succeeded" -ForegroundColor Green + } + $importAzAPICallModuleSuccess = $true + } + } + catch { + Write-Host ' Importing AzAPICall module failed' + if (($env:SYSTEM_TEAMPROJECTID -and $env:BUILD_REPOSITORY_ID) -or $env:GITHUB_ACTIONS) { + Write-Host " Saving AzAPICall module ($($AzAPICallVersion))" + try { + $params = @{ + Name = 'AzAPICall' + Path = ".\$($ScriptPath)\AzAPICallModule" + Force = $true + RequiredVersion = $AzAPICallVersion + } + Save-Module @params + } + catch { + Write-Host " Saving AzAPICall module ($($AzAPICallVersion)) failed" + throw + } + } + else { + do { + $installAzAPICallModuleUserChoice = Read-Host " Do you want to install AzAPICall module ($($AzAPICallVersion)) from the PowerShell Gallery? (y/n)" + if ($installAzAPICallModuleUserChoice -eq 'y') { + try { + Install-Module -Name AzAPICall -RequiredVersion $AzAPICallVersion + } + catch { + Write-Host " Install-Module AzAPICall ($($AzAPICallVersion)) Failed" + throw + } + } + elseif ($installAzAPICallModuleUserChoice -eq 'n') { + Write-Host ' AzAPICall module is required, please visit https://aka.ms/AZAPICall or https://www.powershellgallery.com/packages/AzAPICall' + throw ' AzAPICall module is required' + } + else { + Write-Host " Accepted input 'y' or 'n'; start over.." + } + } + until ($installAzAPICallModuleUserChoice -eq 'y') + } + } +} +until ($importAzAPICallModuleSuccess) +#endregion verifyAzAPICall + +#Region initAZAPICall +Write-Host "Initialize 'AzAPICall'" +$parameters4AzAPICallModule = @{ + DebugAzAPICall = $DebugAzAPICall + SubscriptionId4AzContext = $SubscriptionId4AzContext + GithubRepository = $GithubRepository +} +$azAPICallConf = initAzAPICall @parameters4AzAPICallModule +Write-Host " Initialize 'AzAPICall' succeeded" -ForegroundColor Green +#EndRegion initAZAPICall - $html += @" -
    -
    +#obsolete +#$AzAPICallFunctions = getAzAPICallFunctions -
    -

    DefinitionInsights

    -"@ - $html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $html = $null +handleCloudEnvironment +addHtParameters - ProcessDefinitionInsights - #[System.GC]::Collect() +#region delimiterOpposite +if ($CsvDelimiter -eq ';') { + $CsvDelimiterOpposite = ',' +} +if ($CsvDelimiter -eq ',') { + $CsvDelimiterOpposite = ';' +} +#endregion delimiterOpposite - $html += @" -
    -
    -"@ +#region runDataCollection - if ((-not $NoScopeInsights) -or (-not $NoSingleSubscriptionOutput)) { +#run +$arrayAPICallTrackingCustomDataCollection = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) - if ((-not $NoScopeInsights)) { - $html += @" -
    -

    ScopeInsights

    -"@ - $html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $html = $null - Write-Host " Building ScopeInsights" - } +validateAccess +getFileNaming - $startHierarchyTable = Get-Date - $script:scopescnter = 0 - ProcessScopeInsights -mgChild $ManagementGroupIdCaseSensitived -mgChildOf $getMgParentId - #[System.GC]::Collect() +Write-Host "Running AzGovViz for ManagementGroupId: '$ManagementGroupId'" -ForegroundColor Yellow - $endHierarchyTable = Get-Date - Write-Host " Building ScopeInsights duration: $((NEW-TIMESPAN -Start $startHierarchyTable -End $endHierarchyTable).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startHierarchyTable -End $endHierarchyTable).TotalSeconds) seconds)" +$newTable = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) +$htMgDetails = @{} +$htSubDetails = @{} - if ((-not $NoScopeInsights)) { - $html += @" -
    -
    -"@ - } +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + #helper ht / collect results /save some time + $htCacheDefinitionsPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheDefinitionsPolicySet = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheDefinitionsRole = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheDefinitionsBlueprint = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htRoleDefinitionIdsUsedInPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htPoliciesUsedInPolicySets = @{} + $htSubscriptionTags = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsPolicyOnResourceGroupsAndResources = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsRole = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsRBACOnResourceGroupsAndResources = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsBlueprint = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCachePolicyComplianceMG = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCachePolicyComplianceSUB = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCachePolicyComplianceResponseTooLargeMG = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCachePolicyComplianceResponseTooLargeSUB = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $outOfScopeSubscriptions = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htAllSubscriptionsFromAPI = @{} + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + $htManagementGroupsCost = @{} + $htAzureConsumptionSubscriptions = @{} + $arrayConsumptionData = [System.Collections.ArrayList]@() + $arrayTotalCostSummary = @() + $azureConsumptionStartDate = ((Get-Date).AddDays( - ($($AzureConsumptionPeriod)))).ToString('yyyy-MM-dd') + $azureConsumptionEndDate = ((Get-Date).AddDays(-1)).ToString('yyyy-MM-dd') + } + $customDataCollectionDuration = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htResourceLocks = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList.AllScopes = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList.Subscription = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList.ResourceGroup = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList.Resource = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $arrayTagList = [System.Collections.ArrayList]@() + $htSubscriptionTagList = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htPolicyAssignmentExemptions = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htUserTypesGuest = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $resourcesAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $resourcesIdsAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $resourceGroupsAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htResourceProvidersAll = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htResourceTypesUniqueResource = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $arrayDataCollectionProgressMg = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayDataCollectionProgressSub = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arraySubResourcesAddArrayDuration = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayDiagnosticSettingsMgSub = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htDiagnosticSettingsMgSub = @{} + ($htDiagnosticSettingsMgSub).mg = @{} + ($htDiagnosticSettingsMgSub).sub = @{} + $htMgAtScopePolicyAssignments = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htMgAtScopePoliciesScoped = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htMgAtScopeRoleAssignments = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htMgASCSecureScore = @{} + $htConsumptionExceptionLog = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htConsumptionExceptionLog.Mg = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htConsumptionExceptionLog.Sub = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htRoleAssignmentsFromAPIInheritancePrevention = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.PolicyAssignment = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.Policy = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.PolicySet = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.Role = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.Subscription = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.ManagementGroup = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htPrincipals = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htServicePrincipals = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htDailySummary = @{} + $arrayDefenderPlans = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayDefenderPlansSubscriptionNotRegistered = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayUserAssignedIdentities4Resources = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htSubscriptionsRoleAssignmentLimit = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + $htMgASCSecureScore = @{} } -} + $htManagedIdentityForPolicyAssignment = @{} + $htPolicyAssignmentManagedIdentity = @{} + $htManagedIdentityDisplayName = @{} + $htAppDetails = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} -$html += @" - - - - - - - - - -"@ - -$html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force +getEntities +showMemoryUsage +setBaseVariablesMG -$endBuildHTML = Get-Date -Write-Host "Building HTML total duration: $((NEW-TIMESPAN -Start $startBuildHTML -End $endBuildHTML).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildHTML -End $endBuildHTML).TotalSeconds) seconds)" -#endregion BuildHTML +if ($azAPICallConf['htParameters'].accountType -eq 'User') { + getTenantDetails +} -#region BuildMD -Write-Host "Building Markdown" -$startBuildMD = Get-Date -$arrayMgs = [System.Collections.ArrayList]@() -$arraySubs = [System.Collections.ArrayList]@() -$arraySubsOos = [System.Collections.ArrayList]@() -$markdown = $null -$markdownhierarchyMgs = $null -$markdownhierarchySubs = $null -$markdownTable = $null - -if ($htParameters.onAzureDevOpsOrGitHubActions -eq $true) { - $markdown += @" -# AzGovViz - Management Group Hierarchy +getDefaultManagementGroup -## Hierarchy Diagram (Mermaid) +runInfo -::: mermaid - graph $($AzureDevOpsWikiHierarchyDirection.ToUpper());`n -"@ -} -else { - $markdown += @" -# AzGovViz - Management Group Hierarchy +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { -$executionDateTimeInternationalReadable ($currentTimeZone) + #checkContextSubscriptionQuotaId -AADQuotaId $AADQuotaId + #testAzContext + getSubscriptions + detailSubscriptions + showMemoryUsage -## Hierarchy Diagram (Mermaid) + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + getMDfCSecureScoreMG + } -::: mermaid - graph TD;`n -"@ -} + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + getConsumption + } -ProcessDiagramMermaid + cacheBuiltIn + showMemoryUsage -$markdown += @" -$markdownhierarchyMgs -$markdownhierarchySubs - classDef mgr fill:#D9F0FF,stroke:#56595E,color:#000000,stroke-width:1px; - classDef subs fill:#EEEEEE,stroke:#56595E,color:#000000,stroke-width:1px; -"@ + Write-Host 'Collecting custom data' + $startDataCollection = Get-Date -if (($arraySubsOos).count -gt 0) { - $markdown += @" - classDef subsoos fill:#FFCBC7,stroke:#56595E,color:#000000,stroke-width:1px; -"@ -} + processDataCollection -mgId $ManagementGroupId + showMemoryUsage -$markdown += @" - classDef mgrprnts fill:#FFFFFF,stroke:#56595E,color:#000000,stroke-width:1px; - class $(($arrayMgs | Sort-Object -unique) -join ",") mgr; - class $(($arraySubs | Sort-Object -unique) -join ",") subs; -"@ + exportBaseCSV -if (($arraySubsOos).count -gt 0) { - $markdown += @" - class $(($arraySubsOos | Sort-Object -unique) -join ",") subsoos; -"@ + $endDataCollection = Get-Date + Write-Host "Collecting custom data duration: $((NEW-TIMESPAN -Start $startDataCollection -End $endDataCollection).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startDataCollection -End $endDataCollection).TotalSeconds) seconds)" +} +else { + processHierarchyMapOnly + exportBaseCSV } -$markdown += @" - class $mermaidprnts mgrprnts; -::: +prepareData +showMemoryUsage -## Summary -`n -"@ -if ($htParameters.HierarchyMapOnly -eq $false) { - $markdown += @" -Total Management Groups: $totalMgCount (depth $mgDepth)\`n -"@ +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { - if (($arraySubsOos).count -gt 0) { - $markdown += @" -Total Subscriptions: $totalSubIncludedAndExcludedCount ($totalSubOutOfScopeCount out-of-scope)\`n -"@ - } - else { - $markdown += @" -Total Subscriptions: $totalSubIncludedAndExcludedCount\`n -"@ + $rbacBaseQuery = $newTable.where({ -not [String]::IsNullOrEmpty($_.RoleDefinitionName) } ) | Sort-Object -Property RoleIsCustom, RoleDefinitionName | Select-Object -Property Level, Role*, mg*, Subscription* + $roleAssignmentsUniqueById = $rbacBaseQuery | Sort-Object -Property RoleAssignmentId -Unique + + if (-not $NoAADGroupsResolveMembers) { + processAADGroups + showMemoryUsage } - $markdown += @" -Total Custom Policy definitions: $tenantCustomPoliciesCount\ -Total Custom PolicySet definitions: $tenantCustompolicySetsCount\ -Total Policy assignments: $($totalPolicyAssignmentsCount)\ -Total Policy assignments ManagementGroups $($totalPolicyAssignmentsCountMg)\ -Total Policy assignments Subscriptions $($totalPolicyAssignmentsCountSub)\ -Total Policy assignments ResourceGroups: $($totalPolicyAssignmentsCountRg)\ -Total Custom Role definitions: $totalRoleDefinitionsCustomCount\ -Total Role assignments: $totalRoleAssignmentsCount\ -Total Role assignments (Tenant): $totalRoleAssignmentsCountTen\ -Total Role assignments (ManagementGroups): $totalRoleAssignmentsCountMG\ -Total Role assignments (Subscriptions): $totalRoleAssignmentsCountSub\ -Total Role assignments (ResourceGroups and Resources): $totalRoleAssignmentsResourceGroupsAndResourcesCount\ -Total Blueprint definitions: $totalBlueprintDefinitionsCount\ -Total Blueprint assignments: $totalBlueprintAssignmentsCount\ -Total Resources: $totalResourceCount\ -Total Resource Types: $totalResourceTypesCount -"@ + processApplications + showMemoryUsage -} -if ($htParameters.HierarchyMapOnly -eq $true) { - $mgsDetails = ($optimizedTableForPathQueryMg | Select-Object Level, MgId -Unique) - $mgDepth = ($mgsDetails.Level | Measure-Object -maximum).Maximum - $totalMgCount = ($mgsDetails).count - $totalSubCount = ($optimizedTableForPathQuerySub).count + processManagedIdentities + showMemoryUsage - $markdown += @" -Total Management Groups: $totalMgCount (depth $mgDepth)\ -Total Subscriptions: $totalSubCount -"@ + createTagList + showMemoryUsage + if ($azAPICallConf['htParameters'].NoResources -eq $false) { + getResourceDiagnosticsCapability + showMemoryUsage + } } +#endregion runDataCollection -$markdown += @" -`n -## Hierarchy Table +#region createoutputs -| **MgLevel** | **MgName** | **MgId** | **MgParentName** | **MgParentId** | **SubName** | **SubId** | -|-------------|-------------|-------------|-------------|-------------|-------------|-------------| -$markdownTable -"@ +#region BuildHTML +#testhelper +#$fileTimestamp = (Get-Date -Format $FileTimeStampFormat) -$markdown | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).md" -Encoding utf8 -Force -$endBuildMD = Get-Date -Write-Host "Building Markdown total duration: $((NEW-TIMESPAN -Start $startBuildMD -End $endBuildMD).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildMD -End $endBuildMD).TotalSeconds) seconds)" -#endregion BuildMD +$startBuildHTML = Get-Date +Write-Host 'Building HTML' +$html = $null -#region BuildDailySummaryCSV -if (-not $htParameters.HierarchyMapOnly) { - $dailySummary4ExportToCSV = [System.Collections.ArrayList]@() - foreach ($entry in $htDailySummary.keys | sort-Object) { - $null = $dailySummary4ExportToCSV.Add([PSCustomObject]@{ - capability = $entry - count = $htDailySummary.($entry) - }) +#getFileNaming + +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + #region preQueries + Write-Host ' Building preQueries' + $startPreQueries = Get-Date + + Write-Host 'Create Policy/Set helper hash table' + $startHelperHt = Get-Date + $tenantAllPolicySets = ($htCacheDefinitionsPolicySet).Values + $tenantAllPolicySetsCount = ($tenantAllPolicySets).count + if ($tenantAllPolicySetsCount -gt 0) { + foreach ($policySet in $tenantAllPolicySets) { + $PolicySetPolicyIds = $policySet.PolicySetPolicyIds + foreach ($PolicySetPolicyId in $PolicySetPolicyIds) { + + if ($policySet.LinkToAzAdvertizer) { + $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer = "$($policySet.LinkToAzAdvertizer) ($($policySet.PolicyDefinitionId))" + } + else { + $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer = "$($policySet.DisplayName) ($($policySet.PolicyDefinitionId))" + } + $hlper4CSVOutput = "$($policySet.DisplayName) ($($policySet.PolicyDefinitionId))" + if (-not $htPoliciesUsedInPolicySets.($PolicySetPolicyId)) { + $htPoliciesUsedInPolicySets.($PolicySetPolicyId) = @{} + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet = [array]$hlperDisplayNameWithOrWithoutLinkToAzAdvertizer + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV = [array]$hlper4CSVOutput + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySetIdOnly = [array]($policySet.PolicyDefinitionId) + } + else { + $array = $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet + $array += $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer + $arrayCSV = $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV + $arrayCSV += $hlper4CSVOutput + $arrayIdOnly = $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySetIdOnly + $arrayIdOnly += $policySet.PolicyDefinitionId + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet = $array + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV = $arrayCSV + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySetIdOnly = $arrayIdOnly + } + } + } } - Write-Host "Exporting DailySummary CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_DailySummary.csv'" - $dailySummary4ExportToCSV | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_DailySummary.csv" -Delimiter "$csvDelimiter" -NoTypeInformation -} + $endHelperHt = Get-Date + Write-Host "Create Policy/Set helper hash table duration: $((NEW-TIMESPAN -Start $startHelperHt -End $endHelperHt).TotalSeconds) seconds" -#region BuildDailySummaryCSV + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + $policyBaseQuery = $newTable.where({ -not [String]::IsNullOrEmpty($_.PolicyVariant) } ) | Sort-Object -Property PolicyType, Policy | Select-Object -Property Level, Policy*, mg*, Subscription* + } + else { + $policyBaseQuery = $newTable.where({ -not [String]::IsNullOrEmpty($_.PolicyVariant) -and ($_.PolicyAssignmentScopeMgSubRg -eq 'Mg' -or $_.PolicyAssignmentScopeMgSubRg -eq 'Sub') } ) | Sort-Object -Property PolicyType, Policy | Select-Object -Property Level, Policy*, mg*, Subscription* + } -#region BuildConsumptionCSV -if ($htParameters.HierarchyMapOnly -eq $false) { - if ($htParameters.DoAzureConsumption -eq $true) { - if (-not $NoAzureConsumptionReportExportToCSV) { - Write-Host "Exporting Consumption CSV $($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" - $startBuildConsumptionCSV = Get-Date - if ($CsvExportUseQuotesAsNeeded) { - $allConsumptionData | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded + $policyBaseQuerySubscriptions = $policyBaseQuery.where({ -not [String]::IsNullOrEmpty($_.SubscriptionId) } ) + $policyBaseQueryManagementGroups = $policyBaseQuery.where({ [String]::IsNullOrEmpty($_.SubscriptionId) } ) + $policyPolicyBaseQueryScopeInsights = ($policyBaseQuery | Select-Object Mg*, Subscription*, PolicyAssignmentAtScopeCount, PolicySetAssignmentAtScopeCount, PolicyAndPolicySetAssignmentAtScopeCount, PolicyAssignmentLimit -Unique) + $policyBaseQueryUniqueAssignments = $policyBaseQuery | Select-Object -Property Policy* | Sort-Object -Property PolicyAssignmentId -Unique + $policyAssignmentsOrphaned = $policyBaseQuery.where({ $_.PolicyAvailability -eq 'na' } ) | Sort-Object -Property PolicyAssignmentId -Unique + $policyAssignmentsOrphanedCount = $policyAssignmentsOrphaned.Count + Write-Host " $policyAssignmentsOrphanedCount orphaned Policy assignments found" + + $htPolicyWithAssignmentsBase = @{} + foreach ($policyAssignment in $policyBaseQueryUniqueAssignments) { + if ($policyAssignment.PolicyVariant -eq 'Policy') { + if (-not $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId)) { + $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId) = @{} + $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments = [array]$policyAssignment.PolicyAssignmentId } else { - $allConsumptionData | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" -Delimiter "$csvDelimiter" -NoTypeInformation + $usedInAssignments = $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments + $usedInAssignments += $policyAssignment.PolicyAssignmentId + $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments = $usedInAssignments } - $endBuildConsumptionCSV = Get-Date - Write-Host "Exporting Consumption CSV total duration: $((NEW-TIMESPAN -Start $startBuildConsumptionCSV -End $endBuildConsumptionCSV).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildCSV -End $endBuildCSV).TotalSeconds) seconds)" } } -} -#endregion BuildConsumptionCSV -#region BuildJSON -if (-not $htParameters.NoJsonExport) { - #$fileTimestamp = Get-Date -Format "yyyyMM-dd HHmmss" - $startJSON = Get-Date - $startBuildHt = Get-Date + $policyPolicySetBaseQueryUniqueAssignments = $policyBaseQueryUniqueAssignments.where({ $_.PolicyVariant -eq 'PolicySet' } ) + $policyBaseQueryUniqueCustomDefinitions = ($policyBaseQuery.where({ $_.PolicyType -eq 'Custom' } )) | select-object PolicyVariant, PolicyDefinitionId -Unique + $policyPolicyBaseQueryUniqueCustomDefinitions = ($policyBaseQueryUniqueCustomDefinitions.where({ $_.PolicyVariant -eq 'Policy' } )).PolicyDefinitionId + $policyPolicySetBaseQueryUniqueCustomDefinitions = ($policyBaseQueryUniqueCustomDefinitions.where({ $_.PolicyVariant -eq 'PolicySet' } )).PolicyDefinitionId + + $rbacBaseQueryArrayListNotGroupOwner = $rbacBaseQuery.where({ $_.RoleAssignmentIdentityObjectType -ne 'Group' -and $_.RoleDefinitionName -eq 'Owner' }) | Select-Object -Property mgid, SubscriptionId, RoleAssignmentId, RoleDefinitionName, RoleDefinitionId, RoleAssignmentIdentityObjectType, RoleAssignmentIdentityDisplayname, RoleAssignmentIdentitySignInName, RoleAssignmentIdentityObjectId + $rbacBaseQueryArrayListNotGroupUserAccessAdministrator = $rbacBaseQuery.where({ $_.RoleAssignmentIdentityObjectType -ne 'Group' -and $_.RoleDefinitionName -eq 'User Access Administrator' }) | Select-Object -Property mgid, SubscriptionId, RoleAssignmentId, RoleDefinitionName, RoleDefinitionId, RoleAssignmentIdentityObjectType, RoleAssignmentIdentityDisplayname, RoleAssignmentIdentitySignInName, RoleAssignmentIdentityObjectId + $roleAssignmentsForServicePrincipals = (($roleAssignmentsUniqueById.where({ $_.RoleAssignmentIdentityObjectType -eq 'ServicePrincipal' }))) + $htRoleAssignmentsForServicePrincipals = @{} + foreach ($spWithRoleAssignment in $roleAssignmentsForServicePrincipals | Group-Object -Property RoleAssignmentIdentityObjectId) { + if (-not $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name)) { + $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name) = @{} + $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name).RoleAssignments = $spWithRoleAssignment.group + } + } - Write-Host "Create Hierarchy JSON" - Write-Host " Create ht for JSON" + $blueprintBaseQuery = ($newTable | Select-Object mgid, SubscriptionId, Blueprint*).where({ -not [String]::IsNullOrEmpty($_.BlueprintName) } ) + $mgsAndSubs = (($optimizedTableForPathQuery.where({ $_.mgId -ne '' -and $_.Level -ne '0' } )) | select-object MgId, SubscriptionId -unique) - $htJSON = [ordered]@{} - $htJSON.ManagementGroups = [ordered]@{} + #region create array Policy definitions + $tenantAllPoliciesCount = (($htCacheDefinitionsPolicy).Values).count + $tenantCustomPolicies = (($htCacheDefinitionsPolicy).Values).where({ $_.Type -eq 'Custom' } ) + $tenantCustomPoliciesCount = ($tenantCustomPolicies).count + #endregion create array Policy definitions + + #region create array PolicySet definitions + $tenantCustomPolicySets = $tenantAllPolicySets.where({ $_.Type -eq 'Custom' } ) + $tenantCustompolicySetsCount = ($tenantCustomPolicySets).count + #endregion create array PolicySet definitions + + #region assignmentRgRes + $htPoliciesWithAssignmentOnRgRes = @{} + foreach ($policyAssignmentRgRes in ($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values | Sort-Object -Property id -Unique) { + $hlperPolDefId = (($policyAssignmentRgRes.properties.policyDefinitionId).ToLower()) + if (-not $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId)) { + $pscustomObj = [System.Collections.ArrayList]@() + $null = $pscustomObj.Add([PSCustomObject]@{ + PolicyAssignmentId = ($policyAssignmentRgRes.Id).ToLower() + PolicyAssignmentDisplayName = $policyAssignmentRgRes.properties.displayName + }) + $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId) = @{} + $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments = [array](($pscustomObj)) + } + else { + $pscustomObj = [System.Collections.ArrayList]@() + $null = $pscustomObj.Add([PSCustomObject]@{ + PolicyAssignmentId = ($policyAssignmentRgRes.Id).ToLower() + PolicyAssignmentDisplayName = $policyAssignmentRgRes.properties.displayName + }) + $array = @() + $array += $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments + $array += (($pscustomObj)) + $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments = $array + } + } + #endregion assignmentRgRes - $MgIds = ($optimizedTableForPathQuery) | select-object -property level, MgId, MgName, mgParentId, mgParentName | Sort-Object -property level, MgId -unique - $grpScopePolicyDefinitionsCustom = (($htCacheDefinitionsPolicy).values).where( { $_.Type -eq "Custom" }) | Group-Object ScopeMgSub - $grpMgScopePolicyDefinitionsCustom = ($grpScopePolicyDefinitionsCustom.where( { $_.Name -eq "Mg" }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId) - $grpSubScopePolicyDefinitionsCustom = ($grpScopePolicyDefinitionsCustom.where( { $_.Name -eq "Sub" }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId) + $tenantAllRoles = ($htCacheDefinitionsRole).Values + $tenantAllRolesCount = ($tenantAllRoles).Count + $tenantCustomRoles = $tenantAllRoles.where({ $_.IsCustom -eq $True } ) + $tenantCustomRolesCount = ($tenantCustomRoles).Count + $tenantAllRolesCanDoRoleAssignments = $tenantAllRoles.where({ $_.RoleCanDoRoleAssignments -eq $True } ) + $tenantAllRolesCanDoRoleAssignmentsCount = $tenantAllRolesCanDoRoleAssignments.Count - $grpScopePolicySetDefinitionsCustom = (($htCacheDefinitionsPolicySet).values).where( { $_.Type -eq "Custom" }) | Group-Object ScopeMgSub - $grpMgScopePolicySetDefinitionsCustom = $grpScopePolicySetDefinitionsCustom.where( { $_.Name -eq "Mg" }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId - $grpSubScopePolicySetDefinitionsCustom = $grpScopePolicySetDefinitionsCustom.where( { $_.Name -eq "Sub" }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId + $mgSubRoleAssignmentsArrayFromHTValues = ($htCacheAssignmentsRole).Values.Assignment + if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + $rgResRoleAssignmentsArrayFromHTValues = ($htCacheAssignmentsRBACOnResourceGroupsAndResources).Values + } - $grpScopePolicyAssignments = ($htCacheAssignmentsPolicy).values | Group-Object -Property AssignmentScopeMgSubRg - $grpMgScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq "Mg" }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId - $grpSubScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq "Sub" }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId + #region diagnostics Mg/Sub + $diagnosticSettingsMg = $arrayDiagnosticSettingsMgSub.where({ $_.Scope -eq 'Mg' -and $_.DiagnosticsPresent -eq 'true' }) + $diagnosticSettingsMgCount = $diagnosticSettingsMg.Count + $diagnosticSettingsMgCategories = ($diagnosticSettingsMg.DiagnosticCategories | Group-Object -Property Category).Name + $diagnosticSettingsMgGrouped = $diagnosticSettingsMg | Group-Object -Property ScopeId + $diagnosticSettingsMgManagementGroupsCount = ($diagnosticSettingsMgGrouped | Measure-Object).Count - if (-not $htParameters.DoNotIncludeResourceGroupsOnPolicy) { - if (-not $JsonExportExcludeResourceGroups) { - $grpRGScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq "RG" }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId - $htSubRGPolicyAssignments = @{} - foreach ($rgpa in $grpRGScopePolicyAssignments) { - $subId = ($rgpa.Name).split("/")[0] - if (-not $htSubRGPolicyAssignments.($subId)) { - $htSubRGPolicyAssignments.($subId) = @{} + foreach ($entry in $diagnosticSettingsMgGrouped) { + $dsgrouped = $entry.group | Group-Object -property DiagnosticSettingName + + foreach ($ds in $dsgrouped) { + $targetTypegrouped = $ds.group | Group-Object -property DiagnosticTargetType + foreach ($tt in $targetTypegrouped) { + if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name)) { + ($htDiagnosticSettingsMgSub).mg.($entry.Name) = @{} } - if (-not $htSubRGPolicyAssignments.($subId).PolicyAssignments) { - $htSubRGPolicyAssignments.($subId).PolicyAssignments = @() + if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name)) { + ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name) = @{} + } + if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name).($tt.Name)) { + ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name).($tt.Name) = $tt.group } - $htSubRGPolicyAssignments.($subId).PolicyAssignments += $rgpa.group } } } - $grpScopeRoleAssignments = ($htCacheAssignmentsRole).values | Group-Object -Property AssignmentScopeTenMgSubRgRes - $grpTenantScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq "Tenant" }).Group | Group-Object -Property AssignmentScopeId - $grpMgScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq "Mg" }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId - $grpSubScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq "Sub" }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId - - if (-not $htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - if (-not $JsonExportExcludeResourceGroups) { - $grpRGScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq "RG" }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId - $htSubRGRoleAssignments = @{} - foreach ($rgra in $grpRGScopeRoleAssignments) { - $subId = ($rgra.Name).split("/")[0] - if (-not $htSubRGRoleAssignments.($subId)) { - $htSubRGRoleAssignments.($subId) = @{} - } - if (-not $htSubRGRoleAssignments.($subId).RoleAssignments) { - $htSubRGRoleAssignments.($subId).RoleAssignments = @() + foreach ($mg in $htManagementGroupsMgPath.Values) { + foreach ($mgWithDiag in ($htDiagnosticSettingsMgSub).mg.keys) { + if ($mg.ParentNameChain -contains $mgWithDiag) { + foreach ($diagSet in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).keys) { + foreach ($tt in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).($diagset).keys) { + foreach ($tid in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).($diagset).($tt)) { + $null = $script:diagnosticSettingsMg.Add([PSCustomObject]@{ + Scope = 'Mg' + ScopeName = $mg.displayName + ScopeId = $mg.Id + ScopeMgPath = $htManagementGroupsMgPath.($mg.Id).pathDelimited + DiagnosticsInheritedOrnot = $true + DiagnosticsInheritedFrom = $mgWithDiag + DiagnosticsPresent = 'true' + DiagnosticSettingName = $diagSet + DiagnosticTargetType = $tt + DiagnosticTargetId = $tid.DiagnosticTargetId + DiagnosticCategories = $tid.DiagnosticCategories + DiagnosticCategoriesHt = $tid.DiagnosticCategoriesHt + }) + } + } } - $htSubRGRoleAssignments.($subId).RoleAssignments += $rgra.group } + } + } + $mgsDiagnosticsApplicableCount = $diagnosticSettingsMg.Count - #res - if (-not $htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - if (-not $JsonExportExcludeResources) { - $grpResScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq "Res" }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId - $htSubResRoleAssignments = @{} - foreach ($resra in $grpResScopeRoleAssignments.Group) { - $raSplit = ($resra.Assignment.RoleAssignmentId).split("/") - $splitSubId = $raSplit[2] - $splitRg = $raSplit[4] - if (-not $htSubResRoleAssignments.($splitSubId)) { - $htSubResRoleAssignments.($splitSubId) = @{} - } - if (-not $htSubResRoleAssignments.($splitSubId).($splitRg)) { - $htSubResRoleAssignments.($splitSubId).($splitRg) = @{} + $arrayMgsWithoutDiagnostics = [System.Collections.ArrayList]@() + foreach ($mg in $htManagementGroupsMgPath.Values) { + if ($diagnosticSettingsMg.ScopeId -notcontains $mg.Id) { + $null = $arrayMgsWithoutDiagnostics.Add([PSCustomObject]@{ + ScopeName = $mg.DisplayName + ScopeId = $mg.Id + ScopeMgPath = $mg.pathDelimited + }) + } + } + $arrayMgsWithoutDiagnosticsCount = $arrayMgsWithoutDiagnostics.Count - } - $resourceName = $resra.AssignmentScopeId.split("/")[2] - if (-not $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)")) { - $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)") = @{} + $diagnosticSettingsSub = $arrayDiagnosticSettingsMgSub.where({ $_.Scope -eq 'Sub' -and $_.DiagnosticsPresent -eq 'true' }) + $diagnosticSettingsSubCount = $diagnosticSettingsSub.Count + $diagnosticSettingsSubNoDiag = $arrayDiagnosticSettingsMgSub.where({ $_.Scope -eq 'Sub' -and $_.DiagnosticsPresent -eq 'false' }) + $diagnosticSettingsSubNoDiagCount = $diagnosticSettingsSubNoDiag.Count + $diagnosticSettingsSubCategories = ($diagnosticSettingsSub.DiagnosticCategories | Group-Object -Property Category).Name + $diagnosticSettingsSubGrouped = $diagnosticSettingsSub | Group-Object -Property ScopeId + $diagnosticSettingsSubSubscriptionsCount = ($diagnosticSettingsSubGrouped | Measure-Object).Count - } - if (-not $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments) { - $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments = [ordered]@{} + foreach ($entry in $diagnosticSettingsSubGrouped) { + $dsgrouped = $entry.group | Group-Object -property DiagnosticSettingName - } - ($htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments.($resra.Assignment.RoleAssignmentId)) = $resra.Assignment - } + foreach ($ds in $dsgrouped) { + $targetTypegrouped = $ds.group | Group-Object -property DiagnosticTargetType + foreach ($tt in $targetTypegrouped) { + if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name)) { + ($htDiagnosticSettingsMgSub).sub.($entry.Name) = @{} + } + if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name)) { + ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name) = @{} + } + if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name).($tt.Name)) { + ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name).($tt.Name) = $tt.group } } } - } + #endregion diagnostics Mg/Sub - $bluePrintsAssignmentsAtScope = ($htCacheAssignmentsBlueprint).keys | Sort-Object - $bluePrintDefinitions = ($htCacheDefinitionsBlueprint).Keys | Sort-Object - $subscriptions = ($optimizedTableForPathQuery.where( { -not [string]::IsNullOrEmpty($_.subscriptionId) })) | Select-Object mgId, Subscription* | Sort-Object -Property subscriptionId -Unique - foreach ($mg in $MgIds) { - - $htJSON.ManagementGroups.($mg.MgId) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).MgId = $mg.MgId - $htJSON.ManagementGroups.($mg.MgId).MgName = $mg.MgName - $htJSON.ManagementGroups.($mg.MgId).mgParentId = $mg.mgParentId - $htJSON.ManagementGroups.($mg.MgId).mgParentName = $mg.mgParentName - $htJSON.ManagementGroups.($mg.MgId).level = $mg.level - $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).RoleAssignments = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions = [ordered]@{} - - foreach ($PolDef in (($grpMgScopePolicyDefinitionsCustom).where( { $_.Name -eq $mg.MgId })).group) { - $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom.($PolDef.Id) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom.($PolDef.Id) = $PolDef.Json + #region DefenderPlans + $defenderPlansGroupedBySub = $arrayDefenderPlans | Sort-Object -Property subscriptionName | Group-Object -Property subscriptionName, subscriptionId, subscriptionMgPath + $subsDefenderPlansCount = ($defenderPlansGroupedBySub | Measure-Object).Count + $defenderCapabilities = ($arrayDefenderPlans.defenderPlan | Sort-Object -Unique) + $defenderCapabilitiesCount = $defenderCapabilities.Count + $defenderPlansGroupedByPlan = $arrayDefenderPlans | Group-Object -Property defenderPlan, defenderPlanTier + $defenderPlansGroupedByPlanCount = ($defenderPlansGroupedByPlan | Measure-Object).Count + if ($defenderPlansGroupedByPlan.Name -contains 'ContainerRegistry, Standard' -or $defenderPlansGroupedByPlan.Name -contains 'KubernetesService, Standard') { + if ($defenderPlansGroupedByPlan.Name -contains 'ContainerRegistry, Standard') { + $defenderPlanDeprecatedContainerRegistry = $true } - - foreach ($PolSetDef in (($grpMgScopePolicySetDefinitionsCustom).where( { $_.Name -eq $mg.MgId })).group) { - $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom.($PolSetDef.Id) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom.($PolSetDef.Id) = $PolSetDef.Json + if ($defenderPlansGroupedByPlan.Name -contains 'KubernetesService, Standard') { + $defenderPlanDeprecatedKubernetesService = $true } + } + #endregion DefenderPlans - foreach ($PolAssignment in ($grpMgScopePolicyAssignments).where( { $_.Name -eq $mg.MgId }).group) { - $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments.($PolAssignment.Assignment.id) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments.($PolAssignment.Assignment.id) = $PolAssignment.Assignment - } + $endPreQueries = Get-Date + Write-Host " Pre Queries duration: $((NEW-TIMESPAN -Start $startPreQueries -End $endPreQueries).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startPreQueries -End $endPreQueries).TotalSeconds) seconds)" + showMemoryUsage + #endregion preQueries - foreach ($RoleAssignment in ($grpMgScopeRoleAssignments).where( { $_.Name -eq $mg.MgId }).group) { - $htJSON.ManagementGroups.($mg.MgId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = $RoleAssignment.Assignment - } + #region summarizeDataCollectionResults + $startSummarizeDataCollectionResults = Get-Date + Write-Host 'Summary data collection' + $mgsDetails = ($optimizedTableForPathQueryMg | Select-Object Level, MgId -Unique) + $mgDepth = ($mgsDetails.Level | measure-object -maximum).Maximum + $totalMgCount = ($mgsDetails).count + $totalSubCount = ($optimizedTableForPathQuerySub).count + $totalSubOutOfScopeCount = ($outOfScopeSubscriptions).count + $totalSubIncludedAndExcludedCount = $totalSubCount + $totalSubOutOfScopeCount + $totalResourceCount = $($resourcesIdsAll.Count) - foreach ($BlueprintDefinition in ($bluePrintDefinitions).where( { $_ -like "/providers/Microsoft.Management/managementGroups/$($mg.MgId)/*" })) { - $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions.($BlueprintDefinition) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions.($BlueprintDefinition) = $BlueprintDefinition - } + $totalPolicyAssignmentsCount = (($htCacheAssignmentsPolicy).keys).count - if (($htDiagnosticSettingsMgSub).mg.($mg.MgId)) { - foreach ($entry in ($htDiagnosticSettingsMgSub).mg.($mg.MgId).keys | Sort-Object) { - $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry) = [ordered]@{} - foreach ($diagset in ($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.keys | Sort-Object) { - $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Name = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticSettingName) - $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Type = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticTargetType) - $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).TargetId = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticTargetId) - $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Settings = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticCategories) - } - } - } + $policyAssignmentsMg = (($htCacheAssignmentsPolicy).Values.where({ $_.AssignmentScopeMgSubRg -eq 'Mg' } )) + $totalPolicyAssignmentsCountMg = $policyAssignmentsMg.Count - foreach ($subscription in $subscriptions) { - if ($subscription.MgId -eq $mg.MgId) { + $totalPolicyAssignmentsCountSub = (($htCacheAssignmentsPolicy).Values.where({ $_.AssignmentScopeMgSubRg -eq 'Sub' } )).count - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionName = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionQuotaId = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionState = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionTags = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionName = $subscription.Subscription - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionQuotaId = $subscription.SubscriptionQuotaId - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionState = $subscription.SubscriptionState - if ($htSubscriptionTags.($subscription.SubscriptionId)) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionTags = $htSubscriptionTags.($subscription.SubscriptionId).getEnumerator() | Sort-Object Key -CaseSensitive - } - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings = [ordered]@{} + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + $totalPolicyAssignmentsCountRg = (($htCacheAssignmentsPolicy).Values.where({ $_.AssignmentScopeMgSubRg -eq 'Rg' -or $_.AssignmentScopeMgSubRg -eq 'Res' } )).count + } + else { + $totalPolicyAssignmentsCountRg = (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values).count + $totalPolicyAssignmentsCount = $totalPolicyAssignmentsCount + $totalPolicyAssignmentsCountRg + } - foreach ($PolDef in (($grpSubScopePolicyDefinitionsCustom).where( { $_.Name -eq $subscription.subscriptionId })).group) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom.($PolDef.Id) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom.($PolDef.Id) = $PolDef.Json - } + $totalRoleAssignmentsCount = (($htCacheAssignmentsRole).keys).count + $totalRoleAssignmentsCountTen = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'Tenant' } )).count + $totalRoleAssignmentsCountMG = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'MG' } )).count + $totalRoleAssignmentsCountSub = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'Sub' } )).count + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + $totalRoleAssignmentsCountRG = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'RG' } )).count + $totalRoleAssignmentsCountRes = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'Res' } )).count + $totalRoleAssignmentsResourceGroupsAndResourcesCount = $totalRoleAssignmentsCountRG + $totalRoleAssignmentsCountRes + } + else { + $totalRoleAssignmentsResourceGroupsAndResourcesCount = (($htCacheAssignmentsRBACOnResourceGroupsAndResources).values).count + $totalRoleAssignmentsCount = $totalRoleAssignmentsCount + $totalRoleAssignmentsResourceGroupsAndResourcesCount + } - foreach ($PolSetDef in (($grpSubScopePolicySetDefinitionsCustom).where( { $_.Name -eq $subscription.subscriptionId })).group) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom.($PolSetDef.Id) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom.($PolSetDef.Id) = $PolSetDef.Json - } + $totalRoleDefinitionsCustomCount = ((($htCacheDefinitionsRole).keys.where({ ($htCacheDefinitionsRole).($_).IsCustom -eq $True } ))).count + $totalBlueprintDefinitionsCount = ((($htCacheDefinitionsBlueprint).keys)).count + $totalBlueprintAssignmentsCount = (($htCacheAssignmentsBlueprint).keys).count + $totalResourceTypesCount = ($resourceTypesDiagnosticsArray).Count - foreach ($PolAssignment in ($grpSubScopePolicyAssignments).where( { $_.Name -eq $subscription.subscriptionId }).group) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments.($PolAssignment.Assignment.id) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments.($PolAssignment.Assignment.id) = $PolAssignment.Assignment - } + Write-Host " Total Management Groups: $totalMgCount (depth $mgDepth)" + $htDailySummary.'ManagementGroups' = $totalMgCount + Write-Host " Total Subscriptions: $totalSubIncludedAndExcludedCount ($totalSubCount included; $totalSubOutOfScopeCount out-of-scope)" + $htDailySummary.'Subscriptions' = $totalSubCount + $htDailySummary.'SubscriptionsOutOfScope' = $totalSubOutOfScopeCount + Write-Host " Total Custom Policy definitions: $tenantCustomPoliciesCount" + $htDailySummary.'PolicyDefinitionsCustom' = $tenantCustomPoliciesCount + Write-Host " Total Custom PolicySet definitions: $tenantCustompolicySetsCount" + $htDailySummary.'PolicySetDefinitionsCustom' = $tenantCustompolicySetsCount + Write-Host " Total Policy assignments: $($totalPolicyAssignmentsCount)" + $htDailySummary.'PolicyAssignments' = $totalPolicyAssignmentsCount + Write-Host " Total Policy assignments ManagementGroups $($totalPolicyAssignmentsCountMg)" + $htDailySummary.'PolicyAssignments_ManagementGroups' = $totalPolicyAssignmentsCountMg + Write-Host " Total Policy assignments Subscriptions $($totalPolicyAssignmentsCountSub)" + $htDailySummary.'PolicyAssignments_Subscriptions' = $totalPolicyAssignmentsCountSub + Write-Host " Total Policy assignments ResourceGroups: $($totalPolicyAssignmentsCountRg)" + $htDailySummary.'PolicyAssignments_ResourceGroups' = $totalPolicyAssignmentsCountRg + Write-Host " Total Custom Role definitions: $totalRoleDefinitionsCustomCount" + $htDailySummary.'RoleDefinitionsCustom' = $totalRoleDefinitionsCustomCount + Write-Host " Total Role assignments: $totalRoleAssignmentsCount" + $htDailySummary.'TotalRoleAssignments' = $totalRoleAssignmentsCount + Write-Host " Total Role assignments (Tenant): $totalRoleAssignmentsCountTen" + $htDailySummary.'TotalRoleAssignments_Tenant' = $totalRoleAssignmentsCountTen + Write-Host " Total Role assignments (ManagementGroups): $totalRoleAssignmentsCountMG" + $htDailySummary.'TotalRoleAssignments_ManagementGroups' = $totalRoleAssignmentsCountMG + Write-Host " Total Role assignments (Subscriptions): $totalRoleAssignmentsCountSub" + $htDailySummary.'TotalRoleAssignments_Subscriptions' = $totalRoleAssignmentsCountSub + Write-Host " Total Role assignments (ResourceGroups and Resources): $totalRoleAssignmentsResourceGroupsAndResourcesCount" + $htDailySummary.'TotalRoleAssignments_RgRes' = $totalRoleAssignmentsResourceGroupsAndResourcesCount + Write-Host " Total Blueprint definitions: $totalBlueprintDefinitionsCount" + $htDailySummary.'Blueprints' = $totalBlueprintDefinitionsCount + Write-Host " Total Blueprint assignments: $totalBlueprintAssignmentsCount" + $htDailySummary.'BlueprintAssignments' = $totalBlueprintAssignmentsCount + Write-Host " Total Resources: $totalResourceCount" + $htDailySummary.'Resources' = $totalResourceCount + Write-Host " Total Resource Types: $totalResourceTypesCount" + $htDailySummary.'ResourceTypes' = $totalResourceTypesCount - foreach ($RoleAssignment in ($grpSubScopeRoleAssignments).where( { $_.Name -eq $subscription.subscriptionId }).group) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = $RoleAssignment.Assignment - } + $rbacUnique = $rbacAll | Sort-Object -Property RoleAssignmentId -Unique + $rbacUniqueObjectIds = $rbacUnique | Sort-Object -Property ObjectId -Unique + $rbacUniqueObjectIdsNonPIM = $rbacUnique.where({ $_.RoleAssignmentPIMRelated -eq $false } ) | Sort-Object -Property ObjectId -Unique + $rbacUniqueObjectIdsPIM = $rbacUnique.where({ $_.RoleAssignmentPIMRelated -eq $true } ) | Sort-Object -Property ObjectId -Unique - foreach ($BlueprintDefinition in ($bluePrintDefinitions).where( { $_ -like "/subscriptions/$($subscription.subscriptionId)/*" })) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions.($BlueprintDefinition) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions.($BlueprintDefinition) = $BlueprintDefinition - } + if ($rbacUniqueObjectIds.Count -gt 0) { + $rbacUniqueObjectIdsGrouped = $rbacUniqueObjectIds | Group-Object -Property ObjectType + foreach ($principalType in $rbacUniqueObjectIdsGrouped) { + $htDailySummary."TotalUniquePrincipalWithPermission_$($principalType.Name)" = $principalType.Count + } + $htDailySummary.'TotalUniquePrincipalWithPermission_SP' = $rbacUniqueObjectIds.where({ $_.ObjectType -like 'SP*' } ).count + $htDailySummary.'TotalUniquePrincipalWithPermission_User' = $rbacUniqueObjectIds.where({ $_.ObjectType -like 'User*' } ).count + } - foreach ($BlueprintsAssignment in ($blueprintsAssignmentsAtScope).where( { $_ -like "/subscriptions/$($subscription.subscriptionId)/*" })) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments.($BlueprintsAssignment) = [ordered]@{} - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments.($BlueprintsAssignment) = $BlueprintsAssignment - } + if ($rbacUniqueObjectIdsNonPIM.Count -gt 0) { + $rbacUniqueObjectIdsNonPIMGrouped = $rbacUniqueObjectIdsNonPIM | Group-Object -Property ObjectType + foreach ($principalType in $rbacUniqueObjectIdsNonPIMGrouped) { + $htDailySummary."TotalUniquePrincipalWithPermissionStatic_$($principalType.Name)" = $principalType.Count + } + $htDailySummary.'TotalUniquePrincipalWithPermissionStatic_SP' = $rbacUniqueObjectIdsNonPIM.where({ $_.ObjectType -like 'SP*' } ).count + $htDailySummary.'TotalUniquePrincipalWithPermissionStatic_User' = $rbacUniqueObjectIdsNonPIM.where({ $_.ObjectType -like 'User*' } ).count + } - if (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId)) { - foreach ($entry in ($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).keys | Sort-Object) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry) = [ordered]@{} - foreach ($diagset in ($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.keys | Sort-Object) { - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Name = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticSettingName) - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Type = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticTargetType) - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).TargetId = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticTargetId) - $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Settings = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticCategories) - } - } - } + if ($rbacUniqueObjectIdsPIM.Count -gt 0) { + $rbacUniqueObjectIdsPIMGrouped = $rbacUniqueObjectIdsPIM | Group-Object -Property ObjectType + foreach ($principalType in $rbacUniqueObjectIdsPIMGrouped) { + $htDailySummary."TotalUniquePrincipalWithPermissionPIM_$($principalType.Name)" = $principalType.Count + } + $htDailySummary.'TotalUniquePrincipalWithPermissionPIM_SP' = $rbacUniqueObjectIdsPIM.where({ $_.ObjectType -like 'SP*' } ).count + $htDailySummary.'TotalUniquePrincipalWithPermissionPIM_User' = $rbacUniqueObjectIdsPIM.where({ $_.ObjectType -like 'User*' } ).count + } + $endSummarizeDataCollectionResults = Get-Date + Write-Host " Summary data collection duration: $((NEW-TIMESPAN -Start $startSummarizeDataCollectionResults -End $endSummarizeDataCollectionResults).TotalSeconds) seconds" + showMemoryUsage + #endregion summarizeDataCollectionResults +} - if (-not $htParameters.DoNotIncludeResourceGroupsOnPolicy) { - if (-not $JsonExportExcludeResourceGroups) { - $htTemp = @{} - if (-not $htTemp.ResourceGroups) { - $htTemp.ResourceGroups = @{} - } +$html = @" + + + + + + + + + AzGovViz + + + + + + + + + + + + - if ($htSubRGPolicyAssignments.($subscription.subscriptionId)) { - foreach ($rgpa in $htSubRGPolicyAssignments.($subscription.subscriptionId).PolicyAssignments) { - $rgName = ($rgpa.AssignmentScopeId).split("/")[1] - if (-not $htTemp.ResourceGroups.($rgName)) { - $htTemp.ResourceGroups.($rgName) = [ordered]@{} - } - if (-not $htTemp.ResourceGroups.($rgName).PolicyAssignments) { - $htTemp.ResourceGroups.($rgName).PolicyAssignments = [ordered]@{} - } - $htTemp.ResourceGroups.($rgName).PolicyAssignments.($rgpa.Assignment.id) = $rgpa.Assignment - } - } - } - } + - if (-not $htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC) { - if (-not $JsonExportExcludeResourceGroups) { - if (-not $htTemp) { - $htTemp = @{} - } - if (-not $htTemp.ResourceGroups) { - $htTemp.ResourceGroups = @{} - } - if ($htSubRGRoleAssignments.($subscription.subscriptionId)) { - foreach ($rgra in $htSubRGRoleAssignments.($subscription.subscriptionId).RoleAssignments) { - $rgName = ($rgra.AssignmentScopeId).split("/")[1] - if (-not $htTemp.ResourceGroups.($rgName)) { - $htTemp.ResourceGroups.($rgName) = [ordered]@{} - } - if (-not $htTemp.ResourceGroups.($rgName).RoleAssignments) { - $htTemp.ResourceGroups.($rgName).RoleAssignments = [ordered]@{} - } - $htTemp.ResourceGroups.($rgName).RoleAssignments.($rgra.Assignment.RoleAssignmentId) = $rgra.Assignment - } - } - # - if (-not $JsonExportExcludeResources) { - if (-not $htTemp.ResourceGroups) { - $htTemp.ResourceGroups = @{} - } - if ($htSubResRoleAssignments.($subscription.subscriptionId)) { - foreach ($rg in $htSubResRoleAssignments.($subscription.subscriptionId).keys) { - foreach ($res in $htSubResRoleAssignments.($subscription.subscriptionId).($rg).Keys | Sort-Object) { - $rgName = ($resra.AssignmentScopeId).split("/")[1] - if (-not $htTemp.ResourceGroups.($rg)) { - $htTemp.ResourceGroups.($rg) = [ordered]@{} - } - if (-not $htTemp.ResourceGroups.($rg).Resources) { - $htTemp.ResourceGroups.($rg).Resources = [ordered]@{} - } - if (-not $htTemp.ResourceGroups.($rg).Resources.($res)) { - $htTemp.ResourceGroups.($rg).Resources.($res) = [ordered]@{} - } - if (-not $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments) { - $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments = [ordered]@{} - } - $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments = $htSubResRoleAssignments.($subscription.subscriptionId).($rg).($res).RoleAssignments - } - } - } - } - } + - if ($htParameters.onAzureDevOpsOrGitHubActions) { - if ($ManagementGroupsOnly) { - $JSONPath = "JSON_ManagementGroupsOnly_$($ManagementGroupId)" - } - else { - $JSONPath = "JSON_$($ManagementGroupId)" + + +"@ + +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + if (-not $NoSingleSubscriptionOutput) { + + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + $HTMLPath = "HTML-Subscriptions_$($ManagementGroupId)" + if (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($HTMLPath)") { + Write-Host ' Cleaning old state (Pipeline only)' + Remove-Item -Recurse -Force "$($outputPath)$($DirectorySeparatorChar)$($HTMLPath)" + } } else { - $JSONPath = "JSON_$($ManagementGroupId)_$($fileTimestamp)" + $HTMLPath = "HTML-Subscriptions_$($ManagementGroupId)_$($fileTimestamp)" + Write-Host " Creating new state ($($HTMLPath)) (local only))" } - Write-Host " Creating new state ($($JSONPath)) (local only))" - } - $null = new-item -Name $JSONPath -ItemType directory -path $outputPath + $null = new-item -Name $HTMLPath -ItemType directory -path $outputPath - if ($htParameters.onAzureDevOpsOrGitHubActions) { - "The directory '$($JSONPath)' will be rebuilt during the AzDO Pipeline run. __Do not save any files in this directory, files and folders will be deleted!__" | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)ReadMe_important.md" -Encoding utf8 - } + $htmlSubscriptionOnlyStart = $html + $htmlSubscriptionOnlyStart += @' + +
    +
    - $null = new-item -Name "$($JSONPath)$($DirectorySeparatorChar)Definitions" -ItemType directory -path $outputPath +
    +
    +
    +

    ScopeInsights

    + +'@ - 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 '"', "_") - } + $htmlSubscriptionOnlyEnd = @' +
    +
    +
    + + + + + + + + - $htJSON.RoleDefinitions = [ordered]@{} - $pathRoleDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)RoleDefinitions" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitions)")) { - $null = new-item -Name $pathRoleDefinitions -ItemType directory -path $outputPath - $pathRoleDefinitionCustom = "$($pathRoleDefinitions)$($DirectorySeparatorChar)Custom" - $pathRoleDefinitionBuiltIn = "$($pathRoleDefinitions)$($DirectorySeparatorChar)BuiltIn" - $null = new-item -Name "$($pathRoleDefinitionCustom)" -ItemType directory -path $outputPath - $null = new-item -Name "$($pathRoleDefinitionBuiltIn)" -ItemType directory -path $outputPath + +'@ } - if (($htCacheDefinitionsRole).Keys.Count -gt 0) { - foreach ($roleDefinition in ($htCacheDefinitionsRole).Keys.where( { ($htCacheDefinitionsRole).($_).IsCustom }) | Sort-Object) { - $htJSON.RoleDefinitions.($roleDefinition) = ($htCacheDefinitionsRole).($roleDefinition).Json.properties - $jsonConverted = ($htCacheDefinitionsRole).($roleDefinition).Json.properties | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitionCustom)$($DirectorySeparatorChar)$(RemoveInvalidFileNameChars ($htCacheDefinitionsRole).($roleDefinition).Name) ($(($htCacheDefinitionsRole).($roleDefinition).Id)).json" -Encoding utf8 - } - foreach ($roleDefinition in ($htCacheDefinitionsRole).Keys.where( { -not ($htCacheDefinitionsRole).($_).IsCustom })) { - $jsonConverted = ($htCacheDefinitionsRole).($roleDefinition).Json | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitionBuiltIn)$($DirectorySeparatorChar)$(RemoveInvalidFileNameChars ($htCacheDefinitionsRole).($roleDefinition).Name ) ($(($htCacheDefinitionsRole).($roleDefinition).Id)).json" -Encoding utf8 - } - } + $htmlShowHideScopeInfo = + @" +

    + + +

    +"@ +} +else { + $htmlShowHideScopeInfo = '' +} - $pathPolicyDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicyDefinitions)")) { - $null = new-item -Name $pathPolicyDefinitions -ItemType directory -path $outputPath - $pathPolicyDefinitionBuiltIn = "$($pathPolicyDefinitions)$($DirectorySeparatorChar)BuiltIn" - $null = new-item -Name "$($pathPolicyDefinitionBuiltIn)" -ItemType directory -path $outputPath +$html += @" + +
    +
    +
    +
    +

    HierarchyMap

    + $($htmlShowHideScopeInfo) +
    +"@ + +$html += @' +
      +
    • +'@ + +if ($tenantDisplayName) { + $tenantDetailsDisplay = "$tenantDisplayName
      $tenantDefaultDomain
      $($azAPICallConf['checkContext'].Tenant.Id)" +} +else { + $tenantDetailsDisplay = "$($azAPICallConf['checkContext'].Tenant.Id)" +} + +$tenantRoleAssignmentCount = 0 +if ($htMgAtScopeRoleAssignments.tenantLevelRoleAssignments) { + $tenantRoleAssignmentCount = $htMgAtScopeRoleAssignments.tenantLevelRoleAssignments.AssignmentsCount +} +$html += @' +
      +
      + +
      +
      +
      +'@ + +$html += @' +
      + +
      +'@ +if ($tenantRoleAssignmentCount -gt 0) { + $html += @" +
      + $($tenantRoleAssignmentCount) +
      +"@ +} +else { + $html += @' +
      +'@ +} +$html += @" +
      +
      + +
      $($tenantDetailsDisplay) +
      +
      +
      + +"@ + +if ($getMgParentName -eq 'Tenant Root') { + $html += @' + +
        +'@ +} +else { + if ($parentMgNamex -eq $parentMgIdx) { + $mgNameAndOrId = $parentMgNamex } - if (($htCacheDefinitionsPolicy).Keys.Count -gt 0) { - foreach ($policyDefinition in ($htCacheDefinitionsPolicy).Keys.where( { ($htCacheDefinitionsPolicy).($_).Type -eq "BuiltIn" })) { - $jsonConverted = ($htCacheDefinitionsPolicy).($policyDefinition).Json.properties | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicyDefinitionBuiltIn)$($DirectorySeparatorChar)$(RemoveInvalidFileNameChars ($htCacheDefinitionsPolicy).($policyDefinition).displayName) ($(($htCacheDefinitionsPolicy).($policyDefinition).Json.name)).json" -Encoding utf8 - } + else { + $mgNameAndOrId = "$parentMgNamex
        $parentMgIdx" } - $pathPolicySetDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicySetDefinitions)")) { - $null = new-item -Name $pathPolicySetDefinitions -ItemType directory -path $outputPath - $pathPolicySetDefinitionBuiltIn = "$($pathPolicySetDefinitions)$($DirectorySeparatorChar)BuiltIn" - $null = new-item -Name "$($pathPolicySetDefinitionBuiltIn)" -ItemType directory -path $outputPath + if ($tenantDisplayName) { + $tenantDetailsDisplay = "$tenantDisplayName
        $tenantDefaultDomain
        " } - if (($htCacheDefinitionsPolicySet).Keys.Count -gt 0) { - foreach ($policySetDefinition in ($htCacheDefinitionsPolicySet).Keys.where( { ($htCacheDefinitionsPolicySet).($_).Type -eq "BuiltIn" })) { - $jsonConverted = ($htCacheDefinitionsPolicySet).($policySetDefinition).Json.properties | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicySetDefinitionBuiltIn)$($DirectorySeparatorChar)$(RemoveInvalidFileNameChars ($htCacheDefinitionsPolicySet).($policySetDefinition).displayName) ($(($htCacheDefinitionsPolicySet).($policySetDefinition).Json.name)).json" -Encoding utf8 - } + else { + $tenantDetailsDisplay = '' } - $endBuildHt = Get-Date - Write-Host " ht for JSON creation duration: $((NEW-TIMESPAN -Start $startBuildHt -End $endBuildHt).TotalSeconds) seconds" + $policiesMgScoped = ($htCacheDefinitionsPolicy).values.where({ $_.ScopeMgSub -eq 'Mg' }) + $policySetsMgScoped = ($htCacheDefinitionsPolicySet).values.where({ $_.ScopeMgSub -eq 'Mg' }) + $roleAssignmentsMg = (($htCacheAssignmentsRole).values.where({ $_.AssignmentScopeTenMgSubRgRes -eq 'Mg' })) - $startBuildJSON = Get-Date - Write-Host " Build JSON" - function buildTree($mgId, $prnt) { - $getMg = $arrayEntitiesFromAPI.where( { $_.type -eq "Microsoft.Management/managementGroups" -and $_.name -eq $mgId }) - $childrenManagementGroups = $arrayEntitiesFromAPI.where( { $_.type -eq "Microsoft.Management/managementGroups" -and $_.properties.parent.id -eq "/providers/Microsoft.Management/managementGroups/$($getMg.Name)" }) - $mgNameValid = RemoveInvalidFileNameChars $getMg.Name - $mgDisplayNameValid = RemoveInvalidFileNameChars $getMg.properties.displayName - $prntx = "$($prnt)$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)")) { - $null = new-item -Name $prntx -ItemType directory -path $outputPath - } - - if (-not $json."ManagementGroups") { - $json."ManagementGroups" = [ordered]@{} - } - $json = $json."ManagementGroups".($getMg.Name) = [ordered]@{} - foreach ($mgCap in $htJSON.ManagementGroups.($getMg.Name).keys) { - $json.$mgCap = $htJSON.ManagementGroups.($getMg.Name).$mgCap - if ($mgCap -eq "PolicyDefinitionsCustom") { - $mgCapShort = "pd" - foreach ($pdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($pdc) - if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = RemoveInvalidFileNameChars $hlp.properties.displayName - } - $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - } - } - if ($mgCap -eq "PolicySetDefinitionsCustom") { - $mgCapShort = "psd" - foreach ($psdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($psdc) - if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = RemoveInvalidFileNameChars $hlp.properties.displayName - } - $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - } - } - if ($mgCap -eq "PolicyAssignments") { - $mgCapShort = "pa" - foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($pa) - if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = RemoveInvalidFileNameChars $hlp.properties.displayName - } - $jsonConverted = $hlp | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($mgCap)$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } + foreach ($parentMgId in $htManagementGroupsMgPath.($ManagementGroupId).ParentNameChain) { + if ($parentMgId -eq $defaultManagementGroupId) { + $classdefaultMG = 'defaultMG' + } + else { + $classdefaultMG = '' + } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - } - } - #marker - if ($mgCap -eq "RoleAssignments") { - $mgCapShort = "ra" - foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($ra) - if ($hlp.PIM -eq "true") { - $pim = "PIM_" - } - else { - $pim = "" - } - $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace ".*/").json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($mgCap)$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace ".*/").json" -Encoding utf8 - } - } - - if ($mgCap -eq "Subscriptions") { - foreach ($sub in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { - $subNameValid = RemoveInvalidFileNameChars $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).SubscriptionName - $subFolderName = "$($prntx)$($DirectorySeparatorChar)$($subNameValid) ($($sub))" - $null = new-item -Name $subFolderName -ItemType directory -path $outputPath - foreach ($subCap in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).Keys) { - if ($subCap -eq "PolicyDefinitionsCustom") { - $subCapShort = "pd" - foreach ($pdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($pdc) - if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = RemoveInvalidFileNameChars $hlp.properties.displayName - } - $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - } - } - if ($subCap -eq "PolicySetDefinitionsCustom") { - $subCapShort = "psd" - foreach ($psdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($psdc) - if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = RemoveInvalidFileNameChars $hlp.properties.displayName - } - $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - } - } - if ($subCap -eq "PolicyAssignments") { - $subCapShort = "pa" - foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($pa) - if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = RemoveInvalidFileNameChars $hlp.properties.displayName - } - $jsonConverted = $hlp | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($subCap)$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(RemoveInvalidFileNameChars $hlp.name)).json" -Encoding utf8 - } - } - #marker - if ($subCap -eq "RoleAssignments") { - $subCapShort = "ra" - foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($ra) - if ($hlp.PIM -eq "true") { - $pim = "PIM_" - } - else { - $pim = "" - } - $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($pim)$($hlp.ObjectType)_$($hlp.RoleAssignmentId -replace ".*/").json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($subCap)$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace ".*/").json" -Encoding utf8 - } - } + $mgPolicyAssignmentCount = ($totalPolicyAssignmentsMg.where({ $_.AssignmentScopeId -eq $parentMgId })).Count + $mgPolicyPolicySetScopedCount = ($policiesMgScoped.where({ $_.ScopeId -eq $parentMgId }).Count) + ($policySetsMgScoped.where({ $_.ScopeId -eq $parentMgId }).Count) + $mgIdRoleAssignmentCount = $roleAssignmentsMg.where({ $_.AssignmentScopeId -eq $parentMgId }).Count - #RG Pol - if (-not $htParameters.DoNotIncludeResourceGroupsOnPolicy) { - if (-not $JsonExportExcludeResourceGroups) { - if ($subCap -eq "ResourceGroups") { - foreach ($rg in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys | Sort-Object) { - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)")) { - $null = new-item -Name "$($subFolderName)$($DirectorySeparatorChar)$($rg)" -ItemType directory -path "$($outputPath)" - } - foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).PolicyAssignments.keys) { - $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).PolicyAssignments.($pa) - if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { - $displayName = "noDisplayNameGiven" - } - else { - $displayName = RemoveInvalidFileNameChars $hlp.properties.displayName - } - $jsonConverted = $hlp | ConvertTo-Json -Depth 99 - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)pa_$($displayName) ($($hlp.name)).json" -Encoding utf8 - $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)PolicyAssignments$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))$($DirectorySeparatorChar)$($rg)" - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { - $null = new-item -Name $path -ItemType directory -path $outputPath - } - $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($($hlp.name)).json" -Encoding utf8 - } - } - } - } - } + $html += @" + +
    +
    +'@ +} +else { + $html += @' + + + + + +
    +
    +'@ +} - if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)Assignments")) { - $null = new-item -Name "$($JSONPath)$($DirectorySeparatorChar)Assignments" -ItemType directory -path $outputPath - } +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { - buildTree -mgId $ManagementGroupId -json $json -prnt "$($JSONPath)$($DirectorySeparatorChar)Tenant" - #buildTree -mgId $checkContext.Tenant.Id -json $json + $html += @' +
    +

    TenantSummary

    +'@ - $htTree."Tenant"."CustomRoleDefinitions" = $htJSON.RoleDefinitions + $html | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $html = $null - $htTree | ConvertTo-JSON -Depth 99 | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)$($fileName).json" -Encoding utf8 -Force + $startSummary = Get-Date - $endBuildJSON = Get-Date - Write-Host " Building JSON duration: $((NEW-TIMESPAN -Start $startBuildJSON -End $endBuildJSON).TotalSeconds) seconds" + processTenantSummary + showMemoryUsage - $endJSON = Get-Date - Write-Host "Creating Hierarchy JSON duration: $((NEW-TIMESPAN -Start $startJSON -End $endJSON).TotalSeconds) seconds" -} -#endregion BuildJSON + #region BuildDailySummaryCSV + $dailySummary4ExportToCSV = [System.Collections.ArrayList]@() + foreach ($entry in $htDailySummary.keys | sort-Object) { + $null = $dailySummary4ExportToCSV.Add([PSCustomObject]@{ + capability = $entry + count = $htDailySummary.($entry) + }) + } + Write-Host " Exporting DailySummary CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_DailySummary.csv'" + $dailySummary4ExportToCSV | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_DailySummary.csv" -Delimiter "$csvDelimiter" -NoTypeInformation + #endregion BuildDailySummaryCSV -#endregion createoutputs + #[System.GC]::Collect() -#APITracking -$APICallTrackingCount = ($arrayAPICallTracking).Count -$APICallTrackingManagementCount = ($arrayAPICallTracking.where( { $_.TargetEndpoint -eq "ManagementAPI" } )).Count -$APICallTrackingGraphCount = ($arrayAPICallTracking.where( { $_.TargetEndpoint -eq "MSGraphAPI" } )).Count -$APICallTrackingRetriesCount = ($arrayAPICallTracking.where( { $_.TryCounter -gt 0 } )).Count -$APICallTrackingRestartDueToDuplicateNextlinkCounterCount = ($arrayAPICallTracking.where( { $_.RestartDueToDuplicateNextlinkCounter -gt 0 } )).Count -Write-Host "AzGovViz APICalls total count: $APICallTrackingCount ($APICallTrackingManagementCount ManagementAPI; $APICallTrackingGraphCount MSGraphAPI; $APICallTrackingRetriesCount retries; $APICallTrackingRestartDueToDuplicateNextlinkCounterCount nextLinkReset)" + $endSummary = Get-Date + Write-Host " Building TenantSummary duration: $((NEW-TIMESPAN -Start $startSummary -End $endSummary).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startSummary -End $endSummary).TotalSeconds) seconds)" -$endAzGovViz = Get-Date -$durationProduct = (NEW-TIMESPAN -Start $startAzGovViz -End $endAzGovViz) -Write-Host "AzGovViz duration: $($durationProduct.TotalMinutes) minutes" + $html += @' +
    +
    -#end -$endTime = Get-Date -Format "dd-MMM-yyyy HH:mm:ss" -Write-Host "End AzGovViz $endTime" +
    +

    DefinitionInsights

    +'@ + $html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $html = $null -Write-Host "Checking for errors" -if ($Error.Count -gt 0) { - Write-Host "Dumping $($Error.Count) Errors (handled by AzGovViz):" - $Error | Out-host -} -else { - Write-Host "Error count is 0" -} + processDefinitionInsights + showMemoryUsage + #[System.GC]::Collect() -#region Stats -if (-not $StatsOptOut) { + $html += @' +
    +
    +'@ - if ($htParameters.onAzureDevOps) { - if ($env:BUILD_REPOSITORY_ID) { - $hashTenantIdOrRepositoryId = [string]($env:BUILD_REPOSITORY_ID) - } - else { - $hashTenantIdOrRepositoryId = [string]($checkContext.Tenant.Id) - } - } - else { - $hashTenantIdOrRepositoryId = [string]($checkContext.Tenant.Id) - } + if ((-not $NoScopeInsights) -or (-not $NoSingleSubscriptionOutput)) { - $hashAccId = [string]($checkContext.Account.Id) + if ((-not $NoScopeInsights)) { + $html += @' +
    +

    ScopeInsights

    +'@ + $html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $html = $null + Write-Host ' Building ScopeInsights' + } - $hasher384 = [System.Security.Cryptography.HashAlgorithm]::Create('sha384') - $hasher512 = [System.Security.Cryptography.HashAlgorithm]::Create('sha512') + $startHierarchyTable = Get-Date + $script:scopescnter = 0 + processScopeInsights -mgChild $ManagementGroupId -mgChildOf $getMgParentId + showMemoryUsage + #[System.GC]::Collect() - $hashTenantIdOrRepositoryIdSplit = $hashTenantIdOrRepositoryId.split("-") - $hashAccIdSplit = $hashAccId.split("-") + $endHierarchyTable = Get-Date + Write-Host " Building ScopeInsights duration: $((NEW-TIMESPAN -Start $startHierarchyTable -End $endHierarchyTable).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startHierarchyTable -End $endHierarchyTable).TotalSeconds) seconds)" - 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 ((-not $NoScopeInsights)) { + $html += @' +
    +
    +'@ + } } +} + +$html += @' + + + + + + + + + +'@ - $accountInfo = "$($accountType)$($userType)" - if ($accountType -eq "ServicePrincipal" -or $accountType -eq "ManagedService" -or $accountType -eq "ClientAssertion") { - $accountInfo = $accountType - } +$html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force - $scopeUsage = "childManagementGroup" - if ($ManagementGroupId -eq $checkContext.Tenant.Id) { - $scopeUsage = "rootManagementGroup" - } +$endBuildHTML = Get-Date +Write-Host "Building HTML total duration: $((NEW-TIMESPAN -Start $startBuildHTML -End $endBuildHTML).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildHTML -End $endBuildHTML).TotalSeconds) seconds)" +#endregion BuildHTML - $statsCountSubscriptions = "less than 100" - if (($htSubscriptionsMgPath.Keys).Count -ge 100) { - $statsCountSubscriptions = "more than 100" - } +buildMD +showMemoryUsage +if (-not $azAPICallConf['htParameters'].NoJsonExport) { + buildJSON + showMemoryUsage +} - $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": "$($checkContext.Environment.Name)", - "identifier": "$($identifier)", - "platform": "$($checkCodeRunPlatform)", - "productVersion": "$($ProductVersion)", - "psAzAccountsVersion": "$($resolvedAzModuleVersion)", - "psVersion": "$($PSVersionTable.PSVersion)", - "scopeUsage": "$($scopeUsage)", - "statsCountErrors": "$($Error.Count)", - "statsCountSubscriptions": "$($statsCountSubscriptions)", - "statsParametersDoNotIncludeResourceGroupsAndResourcesOnRBAC": "$($htParameters.DoNotIncludeResourceGroupsAndResourcesOnRBAC)", - "statsParametersDoNotIncludeResourceGroupsOnPolicy": "$($htParameters.DoNotIncludeResourceGroupsOnPolicy)", - "statsParametersDoNotShowRoleAssignmentsUserData": "$($htParameters.DoNotShowRoleAssignmentsUserData)", - "statsParametersHierarchyMapOnly": "$($htParameters.HierarchyMapOnly)", - "statsParametersManagementGroupsOnly": "$($htParameters.ManagementGroupsOnly)", - "statsParametersLargeTenant": "$($htParameters.LargeTenant)", - "statsParametersNoASCSecureScore": "$($htParameters.NoMDfCSecureScore)", - "statsParametersDoAzureConsumption": "$($htParameters.DoAzureConsumption)", - "statsParametersNoJsonExport": "$($htParameters.NoJsonExport)", - "statsParametersNoScopeInsights": "$($NoScopeInsights)", - "statsParametersNoSingleSubscriptionOutput": "$($NoSingleSubscriptionOutput)", - "statsParametersNoPolicyComplianceStates": "$($htParameters.NoPolicyComplianceStates)", - "statsParametersNoResourceProvidersDetailed": "$($htParameters.NoResourceProvidersDetailed)", - "statsParametersNoResources": "$($htParameters.NoResources)", - "statsParametersPolicyAtScopeOnly": "$($htParameters.PolicyAtScopeOnly)", - "statsParametersRBACAtScopeOnly": "$($htParameters.RBACAtScopeOnly)", - "statsTry": "$($tryCounter)" - } - } - } +buildPolicyAllJSON +showMemoryUsage + +#endregion createoutputs + +#APITracking +$APICallTrackingCount = ($azAPICallConf['arrayAPICallTracking']).Count +$APICallTrackingRetriesCount = ($azAPICallConf['arrayAPICallTracking'].where({ $_.TryCounter -gt 1 } )).Count +$APICallTrackingGroupedByTargetEndpoint = $azAPICallConf['arrayAPICallTracking'] | Group-Object -Property TargetEndpoint +$APICallTrackingRestartDueToDuplicateNextlinkCounterCount = ($azAPICallConf['arrayAPICallTracking'].where({ $_.RestartDueToDuplicateNextlinkCounter -gt 0 } )).Count +Write-Host 'AzGovViz API call stats:' +$duarationStats = ($azAPICallConf['arrayAPICallTracking'].Duration | Measure-Object -Average -Maximum -Minimum) +Write-Host " API calls total count: $APICallTrackingCount ($APICallTrackingRetriesCount retries; $APICallTrackingRestartDueToDuplicateNextlinkCounterCount nextLinkReset) | average: $($duarationStats.Average) sec, maximum: $($duarationStats.Maximum) sec, minimum: $($duarationStats.Minimum) sec" +foreach ($targetEndpoint in $APICallTrackingGroupedByTargetEndpoint | Sort-Object -Property Name) { + $APICallTrackingRetriesCount = ($targetEndpoint.Group.where({ $_.TryCounter -gt 1 } )).Count + $APICallTrackingRestartDueToDuplicateNextlinkCounterCount = ($targetEndpoint.Group.where({ $_.RestartDueToDuplicateNextlinkCounter -gt 0 } )).Count + $duarationStats = ($targetEndpoint.Group.Duration | Measure-Object -Average -Maximum -Minimum) + Write-Host " API calls endpoint '$($targetEndpoint.Name) ($($azAPICallConf['azAPIEndpointUrls'].($targetEndpoint.Name)))' count: $($targetEndpoint.Count) ($APICallTrackingRetriesCount retries; $APICallTrackingRestartDueToDuplicateNextlinkCounterCount nextLinkReset) | average: $($duarationStats.Average) sec, maximum: $($duarationStats.Maximum) sec, minimum: $($duarationStats.Minimum) sec" } -"@ - $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) +$endAzGovViz = Get-Date +$durationProduct = (NEW-TIMESPAN -Start $startAzGovViz -End $endAzGovViz) +Write-Host "AzGovViz duration: $($durationProduct.TotalMinutes) minutes" + +#end +$endTime = Get-Date -Format 'dd-MMM-yyyy HH:mm:ss' +Write-Host "End AzGovViz $endTime" + +Write-Host 'Checking for errors' +if ($Error.Count -gt 0) { + Write-Host "Dumping $($Error.Count) Errors (handled by AzGovViz):" + $Error | Out-host } else { - #noStats - $identifier = (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": "$($identifier)", - "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) + Write-Host 'Error count is 0' } -#endregion Stats + +stats if ($DoTranscript) { Stop-Transcript } -Write-Host "" -Write-Host "--------------------" -Write-Host "Completed successful" -ForegroundColor Green +Write-Host '' +Write-Host '--------------------' +Write-Host 'Completed successful' -ForegroundColor Green +showMemoryUsage if ($Error.Count -gt 0) { Write-Host "Don't bother about dumped errors" -} \ No newline at end of file +} + diff --git a/pwsh/dev/README.md b/pwsh/dev/README.md new file mode 100644 index 00000000..b887b0ba --- /dev/null +++ b/pwsh/dev/README.md @@ -0,0 +1 @@ +contents in the __dev__ directory are for dev/test/build AzGovVizParallel.ps1 \ No newline at end of file diff --git a/pwsh/dev/buildAzGovVizParallel.ps1 b/pwsh/dev/buildAzGovVizParallel.ps1 new file mode 100644 index 00000000..a45f78c6 --- /dev/null +++ b/pwsh/dev/buildAzGovVizParallel.ps1 @@ -0,0 +1,19 @@ +$allFunctionLines = foreach ($file in Get-ChildItem -path .\pwsh\dev\functions -Recurse -filter *.ps1) { + Get-Content -LiteralPath $file.FullName +} +$functionCode = $allFunctionLines -join "`n" +$AzGovVizScriptFile = Get-Content -Path .\pwsh\dev\devAzGovVizParallel.ps1 -Raw + +$newContent = @" + +#region Functions +$functionCode +"@ + +$startIndex = $AzGovVizScriptFile.IndexOf('#region Functions') +$endIndex = $AzGovVizScriptFile.IndexOf('#endregion Functions') + +$textBefore = $AzGovVizScriptFile.SubString(0, $startIndex) +$textAfter = $AzGovVizScriptFile.SubString($endIndex) + +$textBefore.TrimEnd(), $newContent, $textAfter | Set-Content -Path .\pwsh\AzGovVizParallel.ps1 \ No newline at end of file diff --git a/pwsh/dev/devAzGovVizParallel.ps1 b/pwsh/dev/devAzGovVizParallel.ps1 new file mode 100644 index 00000000..c9d13e2b --- /dev/null +++ b/pwsh/dev/devAzGovVizParallel.ps1 @@ -0,0 +1,1883 @@ +<# +.SYNOPSIS + This script creates the following files to help better understand and audit your governance setup + csv file + Management Groups, Subscriptions, Policy, PolicySet (Initiative), RBAC + html file + Management Groups, Subscriptions, Policy, PolicySet (Initiative), RBAC + The html file uses Java Script and CSS files which are hosted on various CDNs (Content Delivery Network). For details review the BuildHTML region in this script. + markdown file for use with Azure DevOps Wiki leveraging the Mermaid plugin + Management Groups, Subscriptions + +.DESCRIPTION + Do you want to get granular insights on your technical Azure Governance implementation? - document it in csv, html and markdown? AzGovViz is a PowerShell based script that iterates your Azure Tenants Management Group hierarchy down to Subscription level. It captures most relevant Azure governance capabilities such as Azure Policy, RBAC and Blueprints and a lot more. From the collected data AzGovViz provides visibility on your Hierarchy Map, creates a Tenant Summary and builds granular Scope Insights on Management Groups and Subscriptions. The technical requirements as well as the required permissions are minimal. + +.PARAMETER ManagementGroupId + Define the Management Group Id for which the outputs/files should be generated + +.PARAMETER CsvDelimiter + The script outputs a csv file depending on your delimit defaults choose semicolon or comma + +.PARAMETER OutputPath + Full- or relative path + +.PARAMETER DoNotShowRoleAssignmentsUserData + default is to capture the DisplayName and SignInName for RoleAssignments on ObjectType=User; for data protection and security reasons this may not be acceptable + +.PARAMETER HierarchyMapOnly + default is to query all Management groups and Subscription for Governance capabilities, if you use the parameter -HierarchyMapOnly then only the HierarchyMap will be created + +.PARAMETER NoMDfCSecureScore + default is to query all Subscriptions for Azure Microsoft Defender for Cloud Secure Score and summarize Secure Score for Management Groups. + +.PARAMETER LimitCriticalPercentage + default is 80%, this parameter defines the warning level for approaching Limits (e.g. 80% of Role Assignment limit reached) change as per your preference + +.PARAMETER SubscriptionQuotaIdWhitelist + default is 'undefined', this parameter defines the QuotaIds the subscriptions must match so that AzGovViz processes them. The script checks if the QuotaId startswith the string that you have put in. Separate multiple strings with backslash e.g. MSDN_,EnterpriseAgreement_ + +.PARAMETER NoPolicyComplianceStates + use this parameter if policy compliance states should not be queried + +.PARAMETER NoResourceDiagnosticsPolicyLifecycle + use this parameter if Resource Diagnostics Policy Lifecycle recommendations should not be created + +.PARAMETER NoAADGroupsResolveMembers + use this parameter if Azure Active Directory Group memberships should not be resolved for Role assignments where identity type is 'Group' + +.PARAMETER AADServicePrincipalExpiryWarningDays + define Service Principal Secret and Certificate grace period (lifetime below the defined will be marked for warning / default is 14 days) + +.PARAMETER NoAzureConsumption + #obsolete + use this parameter if Azure Consumption data should not be reported + +.PARAMETER DoAzureConsumption + use this parameter if Azure Consumption data should be reported + +.PARAMETER AzureConsumptionPeriod + use this parameter to define for which time period Azure Consumption data should be gathered; default is 1 day + +.PARAMETER NoAzureConsumptionReportExportToCSV + use this parameter if Azure Consumption data should not be exported (CSV) + +.PARAMETER ThrottleLimit + Leveraging PowerShell Core´s parallel capability you can define the ThrottleLimit (default=5) + +.PARAMETER DoTranscript + Log the console output + +.PARAMETER MermaidDirection + Define the direction the Mermaid based HierarchyMap should be built TD (default) = TopDown (Horizontal), LR = LeftRight (Vertical) + +.PARAMETER SubscriptionId4AzContext + Define the Subscription Id to use for AzContext (default is to use a random Subscription Id) + +.PARAMETER NoCsvExport + Export enriched 'Role assignments' data, enriched 'Policy assignments' data and 'all resources' (subscriptionId, mgPath, resourceType, id, name, location, tags, createdTime, changedTime) + +.PARAMETER DoNotIncludeResourceGroupsOnPolicy + Do not include Policy assignments on ResourceGroups + +.PARAMETER DoNotIncludeResourceGroupsAndResourcesOnRBAC + Do not include Role assignments on ResourceGroups and Resources + +.PARAMETER ChangeTrackingDays + Define the period for Change tracking on newly created and updated custom Policy, PolicySet and RBAC Role definitions and Policy/RBAC Role assignments (default is '14') + +.PARAMETER FileTimeStampFormat + Ddefine the time format for the output files (default is `yyyyMMdd_HHmmss`) + +.PARAMETER NoJsonExport + Enable export of ManagementGroup Hierarchy including all MG/Sub Policy/RBAC definitions, Policy/RBAC assignments and some more relevant information to JSON + +.PARAMETER JsonExportExcludeResourceGroups + JSON Export will not include ResourceGroups (Policy & Role assignments) + +.PARAMETER JsonExportExcludeResources + JSON Export will not include Resources (Role assignments) + +.PARAMETER LargeTenant + A large tenant is a tenant with more than ~500 Subscriptions - the HTML output for large tenants simply becomes too big. + If the parameter switch is true then the following parameters will be set: + -PolicyAtScopeOnly $true + -RBACAtScopeOnly $true + -NoResourceProvidersDetailed $true + -NoScopeInsights $true + +.PARAMETER PolicyAtScopeOnly + Removing 'inherited' lines in the HTML file; use this parameter if you run against a larger tenants + +.PARAMETER RBACAtScopeOnly + Removing 'inherited' lines in the HTML file; use this parameter if you run against a larger tenants + +.PARAMETER NoResourceProvidersDetailed + Note if you use parameter -LargeTenant then parameter -NoResourceProvidersDetailed will be set to true + default is to output all ResourceProvider states for all Subscriptions in the TenantSummary. In large Tenants this can become time consuming and may blow off the html file. + +.PARAMETER NoScopeInsights + Note if you use parameter -LargeTenant then parameter -NoScopeInsights will be set to true + Q: Why would you want to do this? A: In larger tenants the ScopeInsights section blows up the html file (up to unusable due to html file size) + +.PARAMETER AADGroupMembersLimit + Defines the limit (default=500) of AAD Group members; For AAD Groups that have more members than the defined limit Group members will not be resolved + +.PARAMETER NoResources + Will speed up the processing time but information like Resource diagnostics capability, resource type stats, UserAssigned Identities assigned to Resources is excluded (featured for large tenants) + +.PARAMETER StatsOptOut + Will opt-out sending stats + +.PARAMETER NoSingleSubscriptionOutput + Single Scope Insights output per Subscription should not be created + +.PARAMETER HtmlTableRowsLimit + Although the parameter -LargeTenant was introduced recently, still the html output may become too large to be processed properly. The new parameter defines the limit of rows - if for the html processing part the limit is reached then the html table will not be created (csv and json output will still be created). Default rows limit is 20.000 + +.PARAMETER ManagementGroupsOnly + Collect data only for Management Groups (Subscription data such as e.g. Policy assignments etc. will not be collected) + +.EXAMPLE + Define the ManagementGroup ID + PS C:\> .\AzGovVizParallel.ps1 -ManagementGroupId + + Define how the CSV output should be delimited. Valid input is ; or , (semicolon is default) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -CsvDelimiter "," + + Define the outputPath (must exist) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -OutputPath 123 + + Define if User information should be scrubbed (default prints Userinformation to the CSV and HTML output) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -DoNotShowRoleAssignmentsUserData + + Define if only the HierarchyMap output should be created. Will ignore the parameters 'LimitCriticalPercentage' and 'DoNotShowRoleAssignmentsUserData' (default queries for Governance capabilities such as policy-, role-, blueprints assignments and more) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -HierarchyMapOnly + + Define if Microsoft Defender for Cloud SecureScore should be queried for Subscriptions and Management Groups + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoMDfCSecureScore + + Define when limits should be highlighted as warning (default is 80 percent) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -LimitCriticalPercentage 90 + + Define the QuotaId whitelist by providing strings separated by a backslash + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -SubscriptionQuotaIdWhitelist MSDN_, EnterpriseAgreement_ + + Define if policy compliance states should be queried + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoPolicyComplianceStates + + Define if Resource Diagnostics Policy Lifecycle recommendations should not be created + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoResourceDiagnosticsPolicyLifecycle + + Define if Azure Active Directory Group memberships should not be resolved for Role assignments where identity type is 'Group' + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoAADGroupsResolveMembers + + Define Service Principal Secret and Certificate grace period (lifetime below the defined will be marked for warning / default is 14 days) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -AADServicePrincipalExpiryWarningDays 30 + + #obsolete Define if Azure Consumption data should not be reported + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoAzureConsumption + + Define if Azure Consumption data should be reported + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -DoAzureConsumption + + Define for which time period (days) Azure Consumption data should be gathered; e.g. 14 days; default is 1 day + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -AzureConsumptionPeriod 14 + + Define the number of script blocks running in parallel. Leveraging PowerShell Core´s parallel capability you can define the ThrottleLimit (default=5) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -ThrottleLimit 10 + + Define if you want to log the console output + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -DoTranscript + + Define the direction the Mermaid based HierarchyMap should be built in Markdown TD = TopDown (Horizontal), LR = LeftRight (Vertical) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -MermaidDirection "LR" + + Define the Subscription Id to use for AzContext (default is to use a random Subscription Id) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -SubscriptionId4AzContext "" + + Do not Export enriched 'Role assignments' data, enriched 'Policy assignments' data and 'all resources' (subscriptionId, mgPath, resourceType, id, name, location, tags, createdTime, changedTime) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoCsvExport + + Do not include Policy assignments on ResourceGroups + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -DoNotIncludeResourceGroupsOnPolicy + + Do not include Role assignments on ResourceGroups and Resources + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -DoNotIncludeResourceGroupsAndResourcesOnRBAC + + Define the period for Change tracking on newly created and updated custom Policy, PolicySet and RBAC Role definitions and Policy/RBAC Role assignments (default is '14') + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -ChangeTrackingDays 30 + + Define the time format for the output files (default is `yyyyMMdd_HHmmss`) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -FileTimeStampFormat "yyyyMM-dd_HHmm" (default is `yyyyMMdd_HHmmss`) + + Do not enable export of ManagementGroup Hierarchy including all MG/Sub Policy/RBAC definitions, Policy/RBAC assignments and some more relevant information to JSON + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoJsonExport + + JSON Export will not include ResourceGroups (Policy & Role assignments) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -JsonExportExcludeResourceGroups + + JSON Export will not include Resources (Role assignments) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -JsonExportExcludeResources + + A large tenant is a tenant with more than ~500 Subscriptions - the HTML output for large tenants simply becomes too big. + If the parameter switch is true then the following parameters will be set: + -PolicyAtScopeOnly $true + -RBACAtScopeOnly $true + -NoResourceProvidersDetailed $true + -NoScopeInsights $true + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -LargeTenant + + Removing 'inherited' lines in the HTML file for 'Policy Assignments'; use this parameter if you run against a larger tenants + Note if you use parameter -LargeTenant then parameter -PolicyAtScopeOnly will be set to true + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -PolicyAtScopeOnly + + Removing 'inherited' lines in the HTML file for 'Role Assignments'; use this parameter if you run against a larger tenants + Note if you use parameter -LargeTenant then parameter -RBACAtScopeOnly will be set to true + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -RBACAtScopeOnly + + Define if a detailed summary on Resource Provider states per Subscription should be created in the TenantSummary section + Note if you use parameter -LargeTenant then parameter -NoResourceProvidersDetailed will be set to true + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoResourceProvidersDetailed + + Define if ScopeInsights should be created or not. Q: Why would you want to do this? A: In larger tenants the ScopeInsights section blows up the html file (up to unusable due to html file size) + Note if you use parameter -LargeTenant then parameter -NoScopeInsights will be set to true + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoScopeInsights + + Defines the limit (default=500) of AAD Group members; For AAD Groups that have more members than the defined limit Group members will not be resolved + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -AADGroupMembersLimit 750 + + Will speed up the processing time but information like Resource diagnostics capability, resource type stats, UserAssigned Identities assigned to Resources is excluded (featured for large tenants) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoResources + + Will opt-out sending stats + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -StatsOptOut + + Will not create a single Scope Insights output per Subscription + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -NoSingleSubscriptionOutput + + Although the parameter -LargeTenant was introduced recently, still the html output may become too large to be processed properly. The new parameter defines the limit of rows - if for the html processing part the limit is reached then the html table will not be created (csv and json output will still be created). Default rows limit is 20.000 + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -HtmlTableRowsLimit 23077 + + Define if data should be collected for Management Groups only (Subscription data such as e.g. Policy assignments etc. will not be collected) + PS C:\>.\AzGovVizParallel.ps1 -ManagementGroupId -ManagementGroupsOnly + + .NOTES + AUTHOR: Julian Hayward - Customer Engineer - Customer Success Unit | Azure Infrastucture/Automation/Devops/Governance | Microsoft + +.LINK + https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting (aka.ms/AzGovViz) + https://github.com/microsoft/CloudAdoptionFramework/tree/master/govern/AzureGovernanceVisualizer + Please note that while being developed by a Microsoft employee, AzGovViz is not a Microsoft service or product. AzGovViz is a personal/community driven project, there are none implicit or explicit obligations related to this project, it is provided 'as is' with no warranties and confer no rights. +#> + +[CmdletBinding()] +Param +( + [string] + $Product = 'AzGovViz', + + [string] + $AzAPICallVersion = '1.1.11', + + [string] + $ProductVersion = 'v6_major_20220425_1', + + [string] + $GithubRepository = 'aka.ms/AzGovViz', + + [string] + $ScriptPath = 'pwsh', #e.g. 'myfolder\pwsh' + + [string] + $ManagementGroupId, + + [switch] + $AzureDevOpsWikiAsCode, #deprecated - Based on environment variables the script will detect the code run platform + + [switch] + $DebugAzAPICall, + + [switch] + $NoCsvExport, + + [string] + [parameter(ValueFromPipeline)][ValidateSet(';', ',')][string]$CsvDelimiter = ';', + + [switch] + $CsvExportUseQuotesAsNeeded, + + [string] + $OutputPath, + + [switch] + $DoNotShowRoleAssignmentsUserData, + + [switch] + $HierarchyMapOnly, + + [Alias('NoASCSecureScore')] + [switch] + $NoMDfCSecureScore, + + [switch] + $NoResourceProvidersDetailed, + + [int] + $LimitCriticalPercentage = 80, + + [array] + $SubscriptionQuotaIdWhitelist = @('undefined'), + + [switch] + $NoPolicyComplianceStates, + + [switch] + $NoResourceDiagnosticsPolicyLifecycle, + + [switch] + $NoAADGroupsResolveMembers, + + [int] + $AADServicePrincipalExpiryWarningDays = 14, + + [switch] + $NoAzureConsumption, #obsolete + + [switch] + $DoAzureConsumption, + + [int] + $AzureConsumptionPeriod = 1, + + [switch] + $NoAzureConsumptionReportExportToCSV, + + [switch] + $DoTranscript, + + [int] + $HtmlTableRowsLimit = 20000, #HTML TenantSummary may become unresponsive depending on client device performance. A recommendation will be shown to use the CSV file instead of opening the TF table + + [int] + $ThrottleLimit = 10, + + [Alias('ExludedResourceTypesDiagnosticsCapable')] + [array] + $ExcludedResourceTypesDiagnosticsCapable = @('microsoft.web/certificates'), + + [switch] + $DoNotIncludeResourceGroupsOnPolicy, + + [switch] + $DoNotIncludeResourceGroupsAndResourcesOnRBAC, + + [Alias('AzureDevOpsWikiHierarchyDirection')] + [parameter(ValueFromPipeline)][ValidateSet('TD', 'LR')][string]$MermaidDirection = 'TD', + + [string] + $SubscriptionId4AzContext = 'undefined', + + [int] + $ChangeTrackingDays = 14, + + [string] + $FileTimeStampFormat = 'yyyyMMdd_HHmmss', + + [switch] + $NoJsonExport, + + [switch] + $JsonExportExcludeResourceGroups, + + [switch] + $JsonExportExcludeResources, + + [switch] + $LargeTenant, + + [switch] + $NoScopeInsights, + + [int] + $AADGroupMembersLimit = 500, + + [switch] + $PolicyAtScopeOnly, + + [switch] + $RBACAtScopeOnly, + + [switch] + $NoResources, + + [switch] + $StatsOptOut, + + [switch] + $NoSingleSubscriptionOutput, + + [switch] + $ManagementGroupsOnly, + + [string] + $DirectorySeparatorChar = [IO.Path]::DirectorySeparatorChar, + + [switch] + $ShowMemoryUsage, + + #https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#role-based-access-control-limits + [int] + $LimitRBACCustomRoleDefinitionsTenant = 5000, + + [int] + $LimitRBACRoleAssignmentsManagementGroup = 500, + + #https://docs.microsoft.com/en-us/azure/governance/policy/overview#maximum-count-of-azure-policy-objects + [int] + $LimitPOLICYPolicyAssignmentsManagementGroup = 200, + + [int] + $LimitPOLICYPolicyAssignmentsSubscription = 200, + + [int] + $LimitPOLICYPolicyDefinitionsScopedManagementGroup = 500, + + [int] + $LimitPOLICYPolicyDefinitionsScopedSubscription = 500, + + [int] + $LimitPOLICYPolicySetAssignmentsManagementGroup = 200, + + [int] + $LimitPOLICYPolicySetAssignmentsSubscription = 200, + + [int] + $LimitPOLICYPolicySetDefinitionsScopedTenant = 2500, + + [int] + $LimitPOLICYPolicySetDefinitionsScopedManagementGroup = 200, + + [int] + $LimitPOLICYPolicySetDefinitionsScopedSubscription = 200, + + #https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#subscription-limits + [int] + $LimitResourceGroups = 980, + + [int] + $LimitTagsSubscription = 50 +) + +$Error.clear() +$ErrorActionPreference = 'Stop' +#removeNoise +$ProgressPreference = 'SilentlyContinue' +Set-Item Env:\SuppressAzurePowerShellBreakingChangeWarnings 'true' + +#start +$startAzGovViz = Get-Date +$startTime = Get-Date -Format 'dd-MMM-yyyy HH:mm:ss' +Write-Host "Start AzGovViz $($startTime) (#$($ProductVersion))" + +#region Functions +. ".\$($ScriptPath)\functions\addRowToTable.ps1" +. ".\$($ScriptPath)\functions\testPowerShellVersion.ps1" +. ".\$($ScriptPath)\functions\setOutput.ps1" +. ".\$($ScriptPath)\functions\setTranscript.ps1" +. ".\$($ScriptPath)\functions\handleCloudEnvironment.ps1" +. ".\$($ScriptPath)\functions\addHtParameters.ps1" +. ".\$($ScriptPath)\functions\selectMg.ps1" +. ".\$($ScriptPath)\functions\validateAccess.ps1" +. ".\$($ScriptPath)\functions\getEntities.ps1" +. ".\$($ScriptPath)\functions\setBaseVariablesMG.ps1" +. ".\$($ScriptPath)\functions\getTenantDetails.ps1" +. ".\$($ScriptPath)\functions\getDefaultManagementGroup.ps1" +. ".\$($ScriptPath)\functions\runInfo.ps1" +. ".\$($ScriptPath)\functions\processHierarchyMapOnly.ps1" +. ".\$($ScriptPath)\functions\getSubscriptions.ps1" +. ".\$($ScriptPath)\functions\detailSubscriptions.ps1" +. ".\$($ScriptPath)\functions\getMDfCSecureScoreMG.ps1" +. ".\$($ScriptPath)\functions\getConsumption.ps1" +. ".\$($ScriptPath)\functions\cacheBuiltIn.ps1" +. ".\$($ScriptPath)\functions\prepareData.ps1" +. ".\$($ScriptPath)\functions\getGroupmembers.ps1" +. ".\$($ScriptPath)\functions\processAADGroups.ps1" +. ".\$($ScriptPath)\functions\processApplications.ps1" +. ".\$($ScriptPath)\functions\processManagedIdentities.ps1" +. ".\$($ScriptPath)\functions\createTagList.ps1" +. ".\$($ScriptPath)\functions\getResourceDiagnosticsCapability.ps1" +. ".\$($ScriptPath)\functions\getFileNaming.ps1" +. ".\$($ScriptPath)\functions\resolveObjectIds.ps1" +. ".\$($ScriptPath)\functions\namingValidation.ps1" +. ".\$($ScriptPath)\functions\removeInvalidFileNameChars.ps1" +. ".\$($ScriptPath)\functions\addIndexNumberToArray.ps1" +. ".\$($ScriptPath)\functions\processDiagramMermaid.ps1" +. ".\$($ScriptPath)\functions\buildMD.ps1" +. ".\$($ScriptPath)\functions\buildTree.ps1" +. ".\$($ScriptPath)\functions\buildJSON.ps1" +. ".\$($ScriptPath)\functions\buildPolicyAllJSON.ps1" +. ".\$($ScriptPath)\functions\stats.ps1" +#Region dataCollectionFunctions +. ".\$($ScriptPath)\functions\dataCollection\dataCollectionFunctions.ps1" +. ".\$($ScriptPath)\functions\processDataCollection.ps1" +. ".\$($ScriptPath)\functions\exportBaseCSV.ps1" +. ".\$($ScriptPath)\functions\html\htmlFunctions.ps1" +. ".\$($ScriptPath)\functions\processTenantSummary.ps1" +. ".\$($ScriptPath)\functions\processDefinitionInsights.ps1" +. ".\$($ScriptPath)\functions\processScopeInsightsMgOrSub.ps1" +. ".\$($ScriptPath)\functions\showMemoryUsage.ps1" +#EndRegion dataCollectionFunctions +#endregion Functions + +$funcAddRowToTable = $function:addRowToTable.ToString() +$funcGetGroupmembers = $function:GetGroupmembers.ToString() +$funcResolveObjectIds = $function:ResolveObjectIds.ToString() +$funcNamingValidation = $function:NamingValidation.ToString() + +testPowerShellVersion +showMemoryUsage +setOutput +if ($DoTranscript) { + setTranscript +} + +#region verifyAzAPICall +if ($AzAPICallVersion) { + Write-Host " Verify 'AzAPICall' ($AzAPICallVersion)" +} +else { + Write-Host " Verify 'AzAPICall' (latest)" +} + +$maxRetry = 3 +$tryCount = 0 +do { + $tryCount++ + if ($tryCount -gt $maxRetry) { + Write-Host " Managing 'AzAPICall' failed (tried $($tryCount - 1)x)" + throw " Managing 'AzAPICall' failed" + } + + $importAzAPICallModuleSuccess = $false + try { + + if (-not $AzAPICallVersion) { + Write-Host ' Check latest module version' + try { + $AzAPICallVersion = (Find-Module -name AzAPICall).Version + Write-Host " Latest module version: $AzAPICallVersion" + } + catch { + Write-Host ' Check latest module version failed' + throw + } + } + + try { + $azAPICallModuleDeviation = $false + $azAPICallModuleVersionLoaded = ((Get-Module -name AzAPICall).Version) + foreach ($moduleLoaded in $azAPICallModuleVersionLoaded) { + if ($moduleLoaded.toString() -ne $AzAPICallVersion) { + Write-Host " Deviating loaded version found ('$($moduleLoaded.toString())' != '$($AzAPICallVersion)')" + $azAPICallModuleDeviation = $true + } + else { + if ($azAPICallModuleVersionLoaded.count -eq 1) { + Write-Host " AzAPICall module ($($moduleLoaded.toString())) is already loaded" -ForegroundColor Green + $importAzAPICallModuleSuccess = $true + } + } + } + + if ($azAPICallModuleDeviation) { + $importAzAPICallModuleSuccess = $false + try { + Write-Host " Remove-Module AzAPICall ($(($azAPICallModuleVersionLoaded -join ', ').ToString()))" + Remove-Module -Name AzAPICall -Force + } + catch { + Write-Host ' Remove-Module AzAPICall failed' + throw + } + } + } + catch { + #Write-Host ' AzAPICall module is not loaded' + } + + if (-not $importAzAPICallModuleSuccess) { + Write-Host " Try (#$tryCount) importing AzAPICall module ($AzAPICallVersion)" + if (($env:SYSTEM_TEAMPROJECTID -and $env:BUILD_REPOSITORY_ID) -or $env:GITHUB_ACTIONS) { + Import-Module ".\$($ScriptPath)\AzAPICallModule\AzAPICall\$($AzAPICallVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + Write-Host " Import PS module 'AzAPICall' ($($AzAPICallVersion)) succeeded" -ForegroundColor Green + } + else { + Import-Module -Name AzAPICall -RequiredVersion $AzAPICallVersion -Force + Write-Host " Import PS module 'AzAPICall' ($($AzAPICallVersion)) succeeded" -ForegroundColor Green + } + $importAzAPICallModuleSuccess = $true + } + } + catch { + Write-Host ' Importing AzAPICall module failed' + if (($env:SYSTEM_TEAMPROJECTID -and $env:BUILD_REPOSITORY_ID) -or $env:GITHUB_ACTIONS) { + Write-Host " Saving AzAPICall module ($($AzAPICallVersion))" + try { + $params = @{ + Name = 'AzAPICall' + Path = ".\$($ScriptPath)\AzAPICallModule" + Force = $true + RequiredVersion = $AzAPICallVersion + } + Save-Module @params + } + catch { + Write-Host " Saving AzAPICall module ($($AzAPICallVersion)) failed" + throw + } + } + else { + do { + $installAzAPICallModuleUserChoice = Read-Host " Do you want to install AzAPICall module ($($AzAPICallVersion)) from the PowerShell Gallery? (y/n)" + if ($installAzAPICallModuleUserChoice -eq 'y') { + try { + Install-Module -Name AzAPICall -RequiredVersion $AzAPICallVersion + } + catch { + Write-Host " Install-Module AzAPICall ($($AzAPICallVersion)) Failed" + throw + } + } + elseif ($installAzAPICallModuleUserChoice -eq 'n') { + Write-Host ' AzAPICall module is required, please visit https://aka.ms/AZAPICall or https://www.powershellgallery.com/packages/AzAPICall' + throw ' AzAPICall module is required' + } + else { + Write-Host " Accepted input 'y' or 'n'; start over.." + } + } + until ($installAzAPICallModuleUserChoice -eq 'y') + } + } +} +until ($importAzAPICallModuleSuccess) +#endregion verifyAzAPICall + +#Region initAZAPICall +Write-Host "Initialize 'AzAPICall'" +$parameters4AzAPICallModule = @{ + DebugAzAPICall = $DebugAzAPICall + SubscriptionId4AzContext = $SubscriptionId4AzContext + GithubRepository = $GithubRepository +} +$azAPICallConf = initAzAPICall @parameters4AzAPICallModule +Write-Host " Initialize 'AzAPICall' succeeded" -ForegroundColor Green +#EndRegion initAZAPICall + +#obsolete +#$AzAPICallFunctions = getAzAPICallFunctions + +handleCloudEnvironment +addHtParameters + +#region delimiterOpposite +if ($CsvDelimiter -eq ';') { + $CsvDelimiterOpposite = ',' +} +if ($CsvDelimiter -eq ',') { + $CsvDelimiterOpposite = ';' +} +#endregion delimiterOpposite + +#region runDataCollection + +#run +$arrayAPICallTrackingCustomDataCollection = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + +validateAccess +getFileNaming + +Write-Host "Running AzGovViz for ManagementGroupId: '$ManagementGroupId'" -ForegroundColor Yellow + +$newTable = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) +$htMgDetails = @{} +$htSubDetails = @{} + +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + #helper ht / collect results /save some time + $htCacheDefinitionsPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheDefinitionsPolicySet = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheDefinitionsRole = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheDefinitionsBlueprint = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htRoleDefinitionIdsUsedInPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htPoliciesUsedInPolicySets = @{} + $htSubscriptionTags = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsPolicyOnResourceGroupsAndResources = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsRole = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsRBACOnResourceGroupsAndResources = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsBlueprint = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCacheAssignmentsPolicy = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCachePolicyComplianceMG = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCachePolicyComplianceSUB = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCachePolicyComplianceResponseTooLargeMG = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htCachePolicyComplianceResponseTooLargeSUB = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $outOfScopeSubscriptions = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htAllSubscriptionsFromAPI = @{} + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + $htManagementGroupsCost = @{} + $htAzureConsumptionSubscriptions = @{} + $arrayConsumptionData = [System.Collections.ArrayList]@() + $arrayTotalCostSummary = @() + $azureConsumptionStartDate = ((Get-Date).AddDays( - ($($AzureConsumptionPeriod)))).ToString('yyyy-MM-dd') + $azureConsumptionEndDate = ((Get-Date).AddDays(-1)).ToString('yyyy-MM-dd') + } + $customDataCollectionDuration = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htResourceLocks = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList.AllScopes = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList.Subscription = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList.ResourceGroup = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAllTagList.Resource = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $arrayTagList = [System.Collections.ArrayList]@() + $htSubscriptionTagList = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htPolicyAssignmentExemptions = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htUserTypesGuest = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $resourcesAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $resourcesIdsAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $resourceGroupsAll = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htResourceProvidersAll = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htResourceTypesUniqueResource = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $arrayDataCollectionProgressMg = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayDataCollectionProgressSub = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arraySubResourcesAddArrayDuration = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayDiagnosticSettingsMgSub = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htDiagnosticSettingsMgSub = @{} + ($htDiagnosticSettingsMgSub).mg = @{} + ($htDiagnosticSettingsMgSub).sub = @{} + $htMgAtScopePolicyAssignments = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htMgAtScopePoliciesScoped = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htMgAtScopeRoleAssignments = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htMgASCSecureScore = @{} + $htConsumptionExceptionLog = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htConsumptionExceptionLog.Mg = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htConsumptionExceptionLog.Sub = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htRoleAssignmentsFromAPIInheritancePrevention = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.PolicyAssignment = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.Policy = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.PolicySet = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.Role = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.Subscription = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htNamingValidation.ManagementGroup = @{} #[System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htPrincipals = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htServicePrincipals = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htDailySummary = @{} + $arrayDefenderPlans = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayDefenderPlansSubscriptionNotRegistered = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayUserAssignedIdentities4Resources = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $htSubscriptionsRoleAssignmentLimit = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + $htMgASCSecureScore = @{} + } + $htManagedIdentityForPolicyAssignment = @{} + $htPolicyAssignmentManagedIdentity = @{} + $htManagedIdentityDisplayName = @{} + $htAppDetails = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + + if (-not $NoAADGroupsResolveMembers) { + + $htAADGroupsDetails = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $htAADGroupsExeedingMemberLimit = [System.Collections.Hashtable]::Synchronized((New-Object System.Collections.Hashtable)) #@{} + $arrayGroupRoleAssignmentsOnServicePrincipals = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayGroupRequestResourceNotFound = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $arrayProgressedAADGroups = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + } + + if ($DoAzureConsumption) { + $allConsumptionData = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + } +} + +getEntities +showMemoryUsage +setBaseVariablesMG + +if ($azAPICallConf['htParameters'].accountType -eq 'User') { + getTenantDetails +} + +getDefaultManagementGroup + +runInfo + +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + + #checkContextSubscriptionQuotaId -AADQuotaId $AADQuotaId + #testAzContext + getSubscriptions + detailSubscriptions + showMemoryUsage + + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + getMDfCSecureScoreMG + } + + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + getConsumption + } + + cacheBuiltIn + showMemoryUsage + + Write-Host 'Collecting custom data' + $startDataCollection = Get-Date + + processDataCollection -mgId $ManagementGroupId + showMemoryUsage + + exportBaseCSV + + $endDataCollection = Get-Date + Write-Host "Collecting custom data duration: $((NEW-TIMESPAN -Start $startDataCollection -End $endDataCollection).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startDataCollection -End $endDataCollection).TotalSeconds) seconds)" +} +else { + processHierarchyMapOnly + exportBaseCSV +} + +prepareData +showMemoryUsage + +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + + $rbacBaseQuery = $newTable.where({ -not [String]::IsNullOrEmpty($_.RoleDefinitionName) } ) | Sort-Object -Property RoleIsCustom, RoleDefinitionName | Select-Object -Property Level, Role*, mg*, Subscription* + $roleAssignmentsUniqueById = $rbacBaseQuery | Sort-Object -Property RoleAssignmentId -Unique + + if (-not $NoAADGroupsResolveMembers) { + processAADGroups + showMemoryUsage + } + + processApplications + showMemoryUsage + + processManagedIdentities + showMemoryUsage + + createTagList + showMemoryUsage + + if ($azAPICallConf['htParameters'].NoResources -eq $false) { + getResourceDiagnosticsCapability + showMemoryUsage + } +} +#endregion runDataCollection + +#region createoutputs + +#region BuildHTML +#testhelper +#$fileTimestamp = (Get-Date -Format $FileTimeStampFormat) + +$startBuildHTML = Get-Date +Write-Host 'Building HTML' +$html = $null + +#getFileNaming + +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + #region preQueries + Write-Host ' Building preQueries' + $startPreQueries = Get-Date + + Write-Host 'Create Policy/Set helper hash table' + $startHelperHt = Get-Date + $tenantAllPolicySets = ($htCacheDefinitionsPolicySet).Values + $tenantAllPolicySetsCount = ($tenantAllPolicySets).count + if ($tenantAllPolicySetsCount -gt 0) { + foreach ($policySet in $tenantAllPolicySets) { + $PolicySetPolicyIds = $policySet.PolicySetPolicyIds + foreach ($PolicySetPolicyId in $PolicySetPolicyIds) { + + if ($policySet.LinkToAzAdvertizer) { + $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer = "$($policySet.LinkToAzAdvertizer) ($($policySet.PolicyDefinitionId))" + } + else { + $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer = "$($policySet.DisplayName) ($($policySet.PolicyDefinitionId))" + } + $hlper4CSVOutput = "$($policySet.DisplayName) ($($policySet.PolicyDefinitionId))" + if (-not $htPoliciesUsedInPolicySets.($PolicySetPolicyId)) { + $htPoliciesUsedInPolicySets.($PolicySetPolicyId) = @{} + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet = [array]$hlperDisplayNameWithOrWithoutLinkToAzAdvertizer + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV = [array]$hlper4CSVOutput + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySetIdOnly = [array]($policySet.PolicyDefinitionId) + } + else { + $array = $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet + $array += $hlperDisplayNameWithOrWithoutLinkToAzAdvertizer + $arrayCSV = $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV + $arrayCSV += $hlper4CSVOutput + $arrayIdOnly = $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySetIdOnly + $arrayIdOnly += $policySet.PolicyDefinitionId + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet = $array + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySet4CSV = $arrayCSV + $htPoliciesUsedInPolicySets.($PolicySetPolicyId).policySetIdOnly = $arrayIdOnly + } + } + } + } + $endHelperHt = Get-Date + Write-Host "Create Policy/Set helper hash table duration: $((NEW-TIMESPAN -Start $startHelperHt -End $endHelperHt).TotalSeconds) seconds" + + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + $policyBaseQuery = $newTable.where({ -not [String]::IsNullOrEmpty($_.PolicyVariant) } ) | Sort-Object -Property PolicyType, Policy | Select-Object -Property Level, Policy*, mg*, Subscription* + } + else { + $policyBaseQuery = $newTable.where({ -not [String]::IsNullOrEmpty($_.PolicyVariant) -and ($_.PolicyAssignmentScopeMgSubRg -eq 'Mg' -or $_.PolicyAssignmentScopeMgSubRg -eq 'Sub') } ) | Sort-Object -Property PolicyType, Policy | Select-Object -Property Level, Policy*, mg*, Subscription* + } + + $policyBaseQuerySubscriptions = $policyBaseQuery.where({ -not [String]::IsNullOrEmpty($_.SubscriptionId) } ) + $policyBaseQueryManagementGroups = $policyBaseQuery.where({ [String]::IsNullOrEmpty($_.SubscriptionId) } ) + $policyPolicyBaseQueryScopeInsights = ($policyBaseQuery | Select-Object Mg*, Subscription*, PolicyAssignmentAtScopeCount, PolicySetAssignmentAtScopeCount, PolicyAndPolicySetAssignmentAtScopeCount, PolicyAssignmentLimit -Unique) + $policyBaseQueryUniqueAssignments = $policyBaseQuery | Select-Object -Property Policy* | Sort-Object -Property PolicyAssignmentId -Unique + $policyAssignmentsOrphaned = $policyBaseQuery.where({ $_.PolicyAvailability -eq 'na' } ) | Sort-Object -Property PolicyAssignmentId -Unique + $policyAssignmentsOrphanedCount = $policyAssignmentsOrphaned.Count + Write-Host " $policyAssignmentsOrphanedCount orphaned Policy assignments found" + + $htPolicyWithAssignmentsBase = @{} + foreach ($policyAssignment in $policyBaseQueryUniqueAssignments) { + if ($policyAssignment.PolicyVariant -eq 'Policy') { + if (-not $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId)) { + $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId) = @{} + $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments = [array]$policyAssignment.PolicyAssignmentId + } + else { + $usedInAssignments = $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments + $usedInAssignments += $policyAssignment.PolicyAssignmentId + $htPolicyWithAssignmentsBase.($policyAssignment.PolicyDefinitionId).Assignments = $usedInAssignments + } + } + } + + $policyPolicySetBaseQueryUniqueAssignments = $policyBaseQueryUniqueAssignments.where({ $_.PolicyVariant -eq 'PolicySet' } ) + $policyBaseQueryUniqueCustomDefinitions = ($policyBaseQuery.where({ $_.PolicyType -eq 'Custom' } )) | select-object PolicyVariant, PolicyDefinitionId -Unique + $policyPolicyBaseQueryUniqueCustomDefinitions = ($policyBaseQueryUniqueCustomDefinitions.where({ $_.PolicyVariant -eq 'Policy' } )).PolicyDefinitionId + $policyPolicySetBaseQueryUniqueCustomDefinitions = ($policyBaseQueryUniqueCustomDefinitions.where({ $_.PolicyVariant -eq 'PolicySet' } )).PolicyDefinitionId + + $rbacBaseQueryArrayListNotGroupOwner = $rbacBaseQuery.where({ $_.RoleAssignmentIdentityObjectType -ne 'Group' -and $_.RoleDefinitionName -eq 'Owner' }) | Select-Object -Property mgid, SubscriptionId, RoleAssignmentId, RoleDefinitionName, RoleDefinitionId, RoleAssignmentIdentityObjectType, RoleAssignmentIdentityDisplayname, RoleAssignmentIdentitySignInName, RoleAssignmentIdentityObjectId + $rbacBaseQueryArrayListNotGroupUserAccessAdministrator = $rbacBaseQuery.where({ $_.RoleAssignmentIdentityObjectType -ne 'Group' -and $_.RoleDefinitionName -eq 'User Access Administrator' }) | Select-Object -Property mgid, SubscriptionId, RoleAssignmentId, RoleDefinitionName, RoleDefinitionId, RoleAssignmentIdentityObjectType, RoleAssignmentIdentityDisplayname, RoleAssignmentIdentitySignInName, RoleAssignmentIdentityObjectId + $roleAssignmentsForServicePrincipals = (($roleAssignmentsUniqueById.where({ $_.RoleAssignmentIdentityObjectType -eq 'ServicePrincipal' }))) + $htRoleAssignmentsForServicePrincipals = @{} + foreach ($spWithRoleAssignment in $roleAssignmentsForServicePrincipals | Group-Object -Property RoleAssignmentIdentityObjectId) { + if (-not $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name)) { + $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name) = @{} + $htRoleAssignmentsForServicePrincipals.($spWithRoleAssignment.Name).RoleAssignments = $spWithRoleAssignment.group + } + } + + $blueprintBaseQuery = ($newTable | Select-Object mgid, SubscriptionId, Blueprint*).where({ -not [String]::IsNullOrEmpty($_.BlueprintName) } ) + $mgsAndSubs = (($optimizedTableForPathQuery.where({ $_.mgId -ne '' -and $_.Level -ne '0' } )) | select-object MgId, SubscriptionId -unique) + + #region create array Policy definitions + $tenantAllPoliciesCount = (($htCacheDefinitionsPolicy).Values).count + $tenantCustomPolicies = (($htCacheDefinitionsPolicy).Values).where({ $_.Type -eq 'Custom' } ) + $tenantCustomPoliciesCount = ($tenantCustomPolicies).count + #endregion create array Policy definitions + + #region create array PolicySet definitions + $tenantCustomPolicySets = $tenantAllPolicySets.where({ $_.Type -eq 'Custom' } ) + $tenantCustompolicySetsCount = ($tenantCustomPolicySets).count + #endregion create array PolicySet definitions + + #region assignmentRgRes + $htPoliciesWithAssignmentOnRgRes = @{} + foreach ($policyAssignmentRgRes in ($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values | Sort-Object -Property id -Unique) { + $hlperPolDefId = (($policyAssignmentRgRes.properties.policyDefinitionId).ToLower()) + if (-not $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId)) { + $pscustomObj = [System.Collections.ArrayList]@() + $null = $pscustomObj.Add([PSCustomObject]@{ + PolicyAssignmentId = ($policyAssignmentRgRes.Id).ToLower() + PolicyAssignmentDisplayName = $policyAssignmentRgRes.properties.displayName + }) + $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId) = @{} + $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments = [array](($pscustomObj)) + } + else { + $pscustomObj = [System.Collections.ArrayList]@() + $null = $pscustomObj.Add([PSCustomObject]@{ + PolicyAssignmentId = ($policyAssignmentRgRes.Id).ToLower() + PolicyAssignmentDisplayName = $policyAssignmentRgRes.properties.displayName + }) + $array = @() + $array += $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments + $array += (($pscustomObj)) + $htPoliciesWithAssignmentOnRgRes.($hlperPolDefId).Assignments = $array + } + } + #endregion assignmentRgRes + + $tenantAllRoles = ($htCacheDefinitionsRole).Values + $tenantAllRolesCount = ($tenantAllRoles).Count + $tenantCustomRoles = $tenantAllRoles.where({ $_.IsCustom -eq $True } ) + $tenantCustomRolesCount = ($tenantCustomRoles).Count + $tenantAllRolesCanDoRoleAssignments = $tenantAllRoles.where({ $_.RoleCanDoRoleAssignments -eq $True } ) + $tenantAllRolesCanDoRoleAssignmentsCount = $tenantAllRolesCanDoRoleAssignments.Count + + $mgSubRoleAssignmentsArrayFromHTValues = ($htCacheAssignmentsRole).Values.Assignment + if ($azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + $rgResRoleAssignmentsArrayFromHTValues = ($htCacheAssignmentsRBACOnResourceGroupsAndResources).Values + } + + #region diagnostics Mg/Sub + $diagnosticSettingsMg = $arrayDiagnosticSettingsMgSub.where({ $_.Scope -eq 'Mg' -and $_.DiagnosticsPresent -eq 'true' }) + $diagnosticSettingsMgCount = $diagnosticSettingsMg.Count + $diagnosticSettingsMgCategories = ($diagnosticSettingsMg.DiagnosticCategories | Group-Object -Property Category).Name + $diagnosticSettingsMgGrouped = $diagnosticSettingsMg | Group-Object -Property ScopeId + $diagnosticSettingsMgManagementGroupsCount = ($diagnosticSettingsMgGrouped | Measure-Object).Count + + foreach ($entry in $diagnosticSettingsMgGrouped) { + $dsgrouped = $entry.group | Group-Object -property DiagnosticSettingName + + foreach ($ds in $dsgrouped) { + $targetTypegrouped = $ds.group | Group-Object -property DiagnosticTargetType + foreach ($tt in $targetTypegrouped) { + if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name)) { + ($htDiagnosticSettingsMgSub).mg.($entry.Name) = @{} + } + if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name)) { + ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name) = @{} + } + if (-not ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name).($tt.Name)) { + ($htDiagnosticSettingsMgSub).mg.($entry.Name).($ds.Name).($tt.Name) = $tt.group + } + } + } + } + + foreach ($mg in $htManagementGroupsMgPath.Values) { + foreach ($mgWithDiag in ($htDiagnosticSettingsMgSub).mg.keys) { + if ($mg.ParentNameChain -contains $mgWithDiag) { + foreach ($diagSet in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).keys) { + foreach ($tt in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).($diagset).keys) { + foreach ($tid in ($htDiagnosticSettingsMgSub).mg.($mgWithDiag).($diagset).($tt)) { + $null = $script:diagnosticSettingsMg.Add([PSCustomObject]@{ + Scope = 'Mg' + ScopeName = $mg.displayName + ScopeId = $mg.Id + ScopeMgPath = $htManagementGroupsMgPath.($mg.Id).pathDelimited + DiagnosticsInheritedOrnot = $true + DiagnosticsInheritedFrom = $mgWithDiag + DiagnosticsPresent = 'true' + DiagnosticSettingName = $diagSet + DiagnosticTargetType = $tt + DiagnosticTargetId = $tid.DiagnosticTargetId + DiagnosticCategories = $tid.DiagnosticCategories + DiagnosticCategoriesHt = $tid.DiagnosticCategoriesHt + }) + } + } + } + } + } + } + $mgsDiagnosticsApplicableCount = $diagnosticSettingsMg.Count + + $arrayMgsWithoutDiagnostics = [System.Collections.ArrayList]@() + foreach ($mg in $htManagementGroupsMgPath.Values) { + if ($diagnosticSettingsMg.ScopeId -notcontains $mg.Id) { + $null = $arrayMgsWithoutDiagnostics.Add([PSCustomObject]@{ + ScopeName = $mg.DisplayName + ScopeId = $mg.Id + ScopeMgPath = $mg.pathDelimited + }) + } + } + $arrayMgsWithoutDiagnosticsCount = $arrayMgsWithoutDiagnostics.Count + + + $diagnosticSettingsSub = $arrayDiagnosticSettingsMgSub.where({ $_.Scope -eq 'Sub' -and $_.DiagnosticsPresent -eq 'true' }) + $diagnosticSettingsSubCount = $diagnosticSettingsSub.Count + $diagnosticSettingsSubNoDiag = $arrayDiagnosticSettingsMgSub.where({ $_.Scope -eq 'Sub' -and $_.DiagnosticsPresent -eq 'false' }) + $diagnosticSettingsSubNoDiagCount = $diagnosticSettingsSubNoDiag.Count + $diagnosticSettingsSubCategories = ($diagnosticSettingsSub.DiagnosticCategories | Group-Object -Property Category).Name + $diagnosticSettingsSubGrouped = $diagnosticSettingsSub | Group-Object -Property ScopeId + $diagnosticSettingsSubSubscriptionsCount = ($diagnosticSettingsSubGrouped | Measure-Object).Count + + foreach ($entry in $diagnosticSettingsSubGrouped) { + $dsgrouped = $entry.group | Group-Object -property DiagnosticSettingName + + foreach ($ds in $dsgrouped) { + $targetTypegrouped = $ds.group | Group-Object -property DiagnosticTargetType + foreach ($tt in $targetTypegrouped) { + if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name)) { + ($htDiagnosticSettingsMgSub).sub.($entry.Name) = @{} + } + if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name)) { + ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name) = @{} + } + if (-not ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name).($tt.Name)) { + ($htDiagnosticSettingsMgSub).sub.($entry.Name).($ds.Name).($tt.Name) = $tt.group + } + } + } + } + #endregion diagnostics Mg/Sub + + #region DefenderPlans + $defenderPlansGroupedBySub = $arrayDefenderPlans | Sort-Object -Property subscriptionName | Group-Object -Property subscriptionName, subscriptionId, subscriptionMgPath + $subsDefenderPlansCount = ($defenderPlansGroupedBySub | Measure-Object).Count + $defenderCapabilities = ($arrayDefenderPlans.defenderPlan | Sort-Object -Unique) + $defenderCapabilitiesCount = $defenderCapabilities.Count + $defenderPlansGroupedByPlan = $arrayDefenderPlans | Group-Object -Property defenderPlan, defenderPlanTier + $defenderPlansGroupedByPlanCount = ($defenderPlansGroupedByPlan | Measure-Object).Count + if ($defenderPlansGroupedByPlan.Name -contains 'ContainerRegistry, Standard' -or $defenderPlansGroupedByPlan.Name -contains 'KubernetesService, Standard') { + if ($defenderPlansGroupedByPlan.Name -contains 'ContainerRegistry, Standard') { + $defenderPlanDeprecatedContainerRegistry = $true + } + if ($defenderPlansGroupedByPlan.Name -contains 'KubernetesService, Standard') { + $defenderPlanDeprecatedKubernetesService = $true + } + } + #endregion DefenderPlans + + $endPreQueries = Get-Date + Write-Host " Pre Queries duration: $((NEW-TIMESPAN -Start $startPreQueries -End $endPreQueries).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startPreQueries -End $endPreQueries).TotalSeconds) seconds)" + showMemoryUsage + #endregion preQueries + + #region summarizeDataCollectionResults + $startSummarizeDataCollectionResults = Get-Date + Write-Host 'Summary data collection' + $mgsDetails = ($optimizedTableForPathQueryMg | Select-Object Level, MgId -Unique) + $mgDepth = ($mgsDetails.Level | measure-object -maximum).Maximum + $totalMgCount = ($mgsDetails).count + $totalSubCount = ($optimizedTableForPathQuerySub).count + $totalSubOutOfScopeCount = ($outOfScopeSubscriptions).count + $totalSubIncludedAndExcludedCount = $totalSubCount + $totalSubOutOfScopeCount + $totalResourceCount = $($resourcesIdsAll.Count) + + $totalPolicyAssignmentsCount = (($htCacheAssignmentsPolicy).keys).count + + $policyAssignmentsMg = (($htCacheAssignmentsPolicy).Values.where({ $_.AssignmentScopeMgSubRg -eq 'Mg' } )) + $totalPolicyAssignmentsCountMg = $policyAssignmentsMg.Count + + $totalPolicyAssignmentsCountSub = (($htCacheAssignmentsPolicy).Values.where({ $_.AssignmentScopeMgSubRg -eq 'Sub' } )).count + + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + $totalPolicyAssignmentsCountRg = (($htCacheAssignmentsPolicy).Values.where({ $_.AssignmentScopeMgSubRg -eq 'Rg' -or $_.AssignmentScopeMgSubRg -eq 'Res' } )).count + } + else { + $totalPolicyAssignmentsCountRg = (($htCacheAssignmentsPolicyOnResourceGroupsAndResources).values).count + $totalPolicyAssignmentsCount = $totalPolicyAssignmentsCount + $totalPolicyAssignmentsCountRg + } + + $totalRoleAssignmentsCount = (($htCacheAssignmentsRole).keys).count + $totalRoleAssignmentsCountTen = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'Tenant' } )).count + $totalRoleAssignmentsCountMG = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'MG' } )).count + $totalRoleAssignmentsCountSub = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'Sub' } )).count + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + $totalRoleAssignmentsCountRG = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'RG' } )).count + $totalRoleAssignmentsCountRes = (($htCacheAssignmentsRole).keys.where({ ($htCacheAssignmentsRole).($_).AssignmentScopeTenMgSubRgRes -eq 'Res' } )).count + $totalRoleAssignmentsResourceGroupsAndResourcesCount = $totalRoleAssignmentsCountRG + $totalRoleAssignmentsCountRes + } + else { + $totalRoleAssignmentsResourceGroupsAndResourcesCount = (($htCacheAssignmentsRBACOnResourceGroupsAndResources).values).count + $totalRoleAssignmentsCount = $totalRoleAssignmentsCount + $totalRoleAssignmentsResourceGroupsAndResourcesCount + } + + $totalRoleDefinitionsCustomCount = ((($htCacheDefinitionsRole).keys.where({ ($htCacheDefinitionsRole).($_).IsCustom -eq $True } ))).count + $totalBlueprintDefinitionsCount = ((($htCacheDefinitionsBlueprint).keys)).count + $totalBlueprintAssignmentsCount = (($htCacheAssignmentsBlueprint).keys).count + $totalResourceTypesCount = ($resourceTypesDiagnosticsArray).Count + + Write-Host " Total Management Groups: $totalMgCount (depth $mgDepth)" + $htDailySummary.'ManagementGroups' = $totalMgCount + Write-Host " Total Subscriptions: $totalSubIncludedAndExcludedCount ($totalSubCount included; $totalSubOutOfScopeCount out-of-scope)" + $htDailySummary.'Subscriptions' = $totalSubCount + $htDailySummary.'SubscriptionsOutOfScope' = $totalSubOutOfScopeCount + Write-Host " Total Custom Policy definitions: $tenantCustomPoliciesCount" + $htDailySummary.'PolicyDefinitionsCustom' = $tenantCustomPoliciesCount + Write-Host " Total Custom PolicySet definitions: $tenantCustompolicySetsCount" + $htDailySummary.'PolicySetDefinitionsCustom' = $tenantCustompolicySetsCount + Write-Host " Total Policy assignments: $($totalPolicyAssignmentsCount)" + $htDailySummary.'PolicyAssignments' = $totalPolicyAssignmentsCount + Write-Host " Total Policy assignments ManagementGroups $($totalPolicyAssignmentsCountMg)" + $htDailySummary.'PolicyAssignments_ManagementGroups' = $totalPolicyAssignmentsCountMg + Write-Host " Total Policy assignments Subscriptions $($totalPolicyAssignmentsCountSub)" + $htDailySummary.'PolicyAssignments_Subscriptions' = $totalPolicyAssignmentsCountSub + Write-Host " Total Policy assignments ResourceGroups: $($totalPolicyAssignmentsCountRg)" + $htDailySummary.'PolicyAssignments_ResourceGroups' = $totalPolicyAssignmentsCountRg + Write-Host " Total Custom Role definitions: $totalRoleDefinitionsCustomCount" + $htDailySummary.'RoleDefinitionsCustom' = $totalRoleDefinitionsCustomCount + Write-Host " Total Role assignments: $totalRoleAssignmentsCount" + $htDailySummary.'TotalRoleAssignments' = $totalRoleAssignmentsCount + Write-Host " Total Role assignments (Tenant): $totalRoleAssignmentsCountTen" + $htDailySummary.'TotalRoleAssignments_Tenant' = $totalRoleAssignmentsCountTen + Write-Host " Total Role assignments (ManagementGroups): $totalRoleAssignmentsCountMG" + $htDailySummary.'TotalRoleAssignments_ManagementGroups' = $totalRoleAssignmentsCountMG + Write-Host " Total Role assignments (Subscriptions): $totalRoleAssignmentsCountSub" + $htDailySummary.'TotalRoleAssignments_Subscriptions' = $totalRoleAssignmentsCountSub + Write-Host " Total Role assignments (ResourceGroups and Resources): $totalRoleAssignmentsResourceGroupsAndResourcesCount" + $htDailySummary.'TotalRoleAssignments_RgRes' = $totalRoleAssignmentsResourceGroupsAndResourcesCount + Write-Host " Total Blueprint definitions: $totalBlueprintDefinitionsCount" + $htDailySummary.'Blueprints' = $totalBlueprintDefinitionsCount + Write-Host " Total Blueprint assignments: $totalBlueprintAssignmentsCount" + $htDailySummary.'BlueprintAssignments' = $totalBlueprintAssignmentsCount + Write-Host " Total Resources: $totalResourceCount" + $htDailySummary.'Resources' = $totalResourceCount + Write-Host " Total Resource Types: $totalResourceTypesCount" + $htDailySummary.'ResourceTypes' = $totalResourceTypesCount + + $rbacUnique = $rbacAll | Sort-Object -Property RoleAssignmentId -Unique + $rbacUniqueObjectIds = $rbacUnique | Sort-Object -Property ObjectId -Unique + $rbacUniqueObjectIdsNonPIM = $rbacUnique.where({ $_.RoleAssignmentPIMRelated -eq $false } ) | Sort-Object -Property ObjectId -Unique + $rbacUniqueObjectIdsPIM = $rbacUnique.where({ $_.RoleAssignmentPIMRelated -eq $true } ) | Sort-Object -Property ObjectId -Unique + + if ($rbacUniqueObjectIds.Count -gt 0) { + $rbacUniqueObjectIdsGrouped = $rbacUniqueObjectIds | Group-Object -Property ObjectType + foreach ($principalType in $rbacUniqueObjectIdsGrouped) { + $htDailySummary."TotalUniquePrincipalWithPermission_$($principalType.Name)" = $principalType.Count + } + $htDailySummary.'TotalUniquePrincipalWithPermission_SP' = $rbacUniqueObjectIds.where({ $_.ObjectType -like 'SP*' } ).count + $htDailySummary.'TotalUniquePrincipalWithPermission_User' = $rbacUniqueObjectIds.where({ $_.ObjectType -like 'User*' } ).count + } + + if ($rbacUniqueObjectIdsNonPIM.Count -gt 0) { + $rbacUniqueObjectIdsNonPIMGrouped = $rbacUniqueObjectIdsNonPIM | Group-Object -Property ObjectType + foreach ($principalType in $rbacUniqueObjectIdsNonPIMGrouped) { + $htDailySummary."TotalUniquePrincipalWithPermissionStatic_$($principalType.Name)" = $principalType.Count + } + $htDailySummary.'TotalUniquePrincipalWithPermissionStatic_SP' = $rbacUniqueObjectIdsNonPIM.where({ $_.ObjectType -like 'SP*' } ).count + $htDailySummary.'TotalUniquePrincipalWithPermissionStatic_User' = $rbacUniqueObjectIdsNonPIM.where({ $_.ObjectType -like 'User*' } ).count + } + + if ($rbacUniqueObjectIdsPIM.Count -gt 0) { + $rbacUniqueObjectIdsPIMGrouped = $rbacUniqueObjectIdsPIM | Group-Object -Property ObjectType + foreach ($principalType in $rbacUniqueObjectIdsPIMGrouped) { + $htDailySummary."TotalUniquePrincipalWithPermissionPIM_$($principalType.Name)" = $principalType.Count + } + $htDailySummary.'TotalUniquePrincipalWithPermissionPIM_SP' = $rbacUniqueObjectIdsPIM.where({ $_.ObjectType -like 'SP*' } ).count + $htDailySummary.'TotalUniquePrincipalWithPermissionPIM_User' = $rbacUniqueObjectIdsPIM.where({ $_.ObjectType -like 'User*' } ).count + } + + $endSummarizeDataCollectionResults = Get-Date + Write-Host " Summary data collection duration: $((NEW-TIMESPAN -Start $startSummarizeDataCollectionResults -End $endSummarizeDataCollectionResults).TotalSeconds) seconds" + showMemoryUsage + #endregion summarizeDataCollectionResults +} + +$html = @" + + + + + + + + + AzGovViz + + + + + + + + + + + + + + + + + + + +"@ + +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + if (-not $NoSingleSubscriptionOutput) { + + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + $HTMLPath = "HTML-Subscriptions_$($ManagementGroupId)" + if (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($HTMLPath)") { + Write-Host ' Cleaning old state (Pipeline only)' + Remove-Item -Recurse -Force "$($outputPath)$($DirectorySeparatorChar)$($HTMLPath)" + } + } + else { + $HTMLPath = "HTML-Subscriptions_$($ManagementGroupId)_$($fileTimestamp)" + Write-Host " Creating new state ($($HTMLPath)) (local only))" + } + + $null = new-item -Name $HTMLPath -ItemType directory -path $outputPath + + $htmlSubscriptionOnlyStart = $html + $htmlSubscriptionOnlyStart += @' + +
    +
    + +
    + +
    +
    +

    ScopeInsights

    + +'@ + + $htmlSubscriptionOnlyEnd = @' +
    +
    +
    + + + + + + + + + + +'@ + } + + $htmlShowHideScopeInfo = + @" +

    + + +

    +"@ +} +else { + $htmlShowHideScopeInfo = '' +} + +$html += @" + +
    +
    +
    +
    +

    HierarchyMap

    + $($htmlShowHideScopeInfo) +
    +"@ + +$html += @' +
    +
    +'@ +} +else { + $html += @' + + + + + +
    +
    +'@ +} + +if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + + $html += @' +
    +

    TenantSummary

    +'@ + + $html | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $html = $null + + $startSummary = Get-Date + + processTenantSummary + showMemoryUsage + + #region BuildDailySummaryCSV + $dailySummary4ExportToCSV = [System.Collections.ArrayList]@() + foreach ($entry in $htDailySummary.keys | sort-Object) { + $null = $dailySummary4ExportToCSV.Add([PSCustomObject]@{ + capability = $entry + count = $htDailySummary.($entry) + }) + } + Write-Host " Exporting DailySummary CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName)_DailySummary.csv'" + $dailySummary4ExportToCSV | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_DailySummary.csv" -Delimiter "$csvDelimiter" -NoTypeInformation + #endregion BuildDailySummaryCSV + + #[System.GC]::Collect() + + $endSummary = Get-Date + Write-Host " Building TenantSummary duration: $((NEW-TIMESPAN -Start $startSummary -End $endSummary).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startSummary -End $endSummary).TotalSeconds) seconds)" + + $html += @' +
    +
    + +
    +

    DefinitionInsights

    +'@ + $html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $html = $null + + processDefinitionInsights + showMemoryUsage + #[System.GC]::Collect() + + $html += @' +
    +
    +'@ + + if ((-not $NoScopeInsights) -or (-not $NoSingleSubscriptionOutput)) { + + if ((-not $NoScopeInsights)) { + $html += @' +
    +

    ScopeInsights

    +'@ + $html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $html = $null + Write-Host ' Building ScopeInsights' + } + + $startHierarchyTable = Get-Date + $script:scopescnter = 0 + processScopeInsights -mgChild $ManagementGroupId -mgChildOf $getMgParentId + showMemoryUsage + #[System.GC]::Collect() + + $endHierarchyTable = Get-Date + Write-Host " Building ScopeInsights duration: $((NEW-TIMESPAN -Start $startHierarchyTable -End $endHierarchyTable).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startHierarchyTable -End $endHierarchyTable).TotalSeconds) seconds)" + + if ((-not $NoScopeInsights)) { + $html += @' +
    +
    +'@ + } + } +} + +$html += @' + + + + + + + + + +'@ + +$html | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + +$endBuildHTML = Get-Date +Write-Host "Building HTML total duration: $((NEW-TIMESPAN -Start $startBuildHTML -End $endBuildHTML).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildHTML -End $endBuildHTML).TotalSeconds) seconds)" +#endregion BuildHTML + +buildMD +showMemoryUsage + +if (-not $azAPICallConf['htParameters'].NoJsonExport) { + buildJSON + showMemoryUsage +} + +buildPolicyAllJSON +showMemoryUsage + +#endregion createoutputs + +#APITracking +$APICallTrackingCount = ($azAPICallConf['arrayAPICallTracking']).Count +$APICallTrackingRetriesCount = ($azAPICallConf['arrayAPICallTracking'].where({ $_.TryCounter -gt 1 } )).Count +$APICallTrackingGroupedByTargetEndpoint = $azAPICallConf['arrayAPICallTracking'] | Group-Object -Property TargetEndpoint +$APICallTrackingRestartDueToDuplicateNextlinkCounterCount = ($azAPICallConf['arrayAPICallTracking'].where({ $_.RestartDueToDuplicateNextlinkCounter -gt 0 } )).Count +Write-Host 'AzGovViz API call stats:' +$duarationStats = ($azAPICallConf['arrayAPICallTracking'].Duration | Measure-Object -Average -Maximum -Minimum) +Write-Host " API calls total count: $APICallTrackingCount ($APICallTrackingRetriesCount retries; $APICallTrackingRestartDueToDuplicateNextlinkCounterCount nextLinkReset) | average: $($duarationStats.Average) sec, maximum: $($duarationStats.Maximum) sec, minimum: $($duarationStats.Minimum) sec" +foreach ($targetEndpoint in $APICallTrackingGroupedByTargetEndpoint | Sort-Object -Property Name) { + $APICallTrackingRetriesCount = ($targetEndpoint.Group.where({ $_.TryCounter -gt 1 } )).Count + $APICallTrackingRestartDueToDuplicateNextlinkCounterCount = ($targetEndpoint.Group.where({ $_.RestartDueToDuplicateNextlinkCounter -gt 0 } )).Count + $duarationStats = ($targetEndpoint.Group.Duration | Measure-Object -Average -Maximum -Minimum) + Write-Host " API calls endpoint '$($targetEndpoint.Name) ($($azAPICallConf['azAPIEndpointUrls'].($targetEndpoint.Name)))' count: $($targetEndpoint.Count) ($APICallTrackingRetriesCount retries; $APICallTrackingRestartDueToDuplicateNextlinkCounterCount nextLinkReset) | average: $($duarationStats.Average) sec, maximum: $($duarationStats.Maximum) sec, minimum: $($duarationStats.Minimum) sec" +} +$endAzGovViz = Get-Date +$durationProduct = (NEW-TIMESPAN -Start $startAzGovViz -End $endAzGovViz) +Write-Host "AzGovViz duration: $($durationProduct.TotalMinutes) minutes" + +#end +$endTime = Get-Date -Format 'dd-MMM-yyyy HH:mm:ss' +Write-Host "End AzGovViz $endTime" + +Write-Host 'Checking for errors' +if ($Error.Count -gt 0) { + Write-Host "Dumping $($Error.Count) Errors (handled by AzGovViz):" + $Error | Out-host +} +else { + Write-Host 'Error count is 0' +} + +stats + +if ($DoTranscript) { + Stop-Transcript +} + +Write-Host '' +Write-Host '--------------------' +Write-Host 'Completed successful' -ForegroundColor Green +showMemoryUsage +if ($Error.Count -gt 0) { + Write-Host "Don't bother about dumped errors" +} diff --git a/pwsh/dev/functions/addHtParameters.ps1 b/pwsh/dev/functions/addHtParameters.ps1 new file mode 100644 index 00000000..160de99a --- /dev/null +++ b/pwsh/dev/functions/addHtParameters.ps1 @@ -0,0 +1,38 @@ +function addHtParameters { + Write-Host 'Add AzGovViz htParameters' + if ($LargeTenant -eq $true) { + $script:NoScopeInsights = $true + $NoResourceProvidersDetailed = $true + $PolicyAtScopeOnly = $true + $RBACAtScopeOnly = $true + } + + if ($ManagementGroupsOnly) { + $script:NoSingleSubscriptionOutput = $true + } + + if ($HierarchyMapOnly) { + $NoJsonExport = $true + } + + $script:azAPICallConf['htParameters'] += [ordered]@{ + DoAzureConsumption = [bool]$DoAzureConsumption + DoNotIncludeResourceGroupsOnPolicy = [bool]$DoNotIncludeResourceGroupsOnPolicy + DoNotIncludeResourceGroupsAndResourcesOnRBAC = [bool]$DoNotIncludeResourceGroupsAndResourcesOnRBAC + DoNotShowRoleAssignmentsUserData = [bool]$DoNotShowRoleAssignmentsUserData + HierarchyMapOnly = [bool]$HierarchyMapOnly + LargeTenant = [bool]$LargeTenant + ManagementGroupsOnly = [bool]$ManagementGroupsOnly + NoJsonExport = [bool]$NoJsonExport + NoMDfCSecureScore = [bool]$NoMDfCSecureScore + NoResourceProvidersDetailed = [bool]$NoResourceProvidersDetailed + NoPolicyComplianceStates = [bool]$NoPolicyComplianceStates + NoResources = [bool]$NoResources + ProductVersion = $ProductVersion + PolicyAtScopeOnly = [bool]$PolicyAtScopeOnly + RBACAtScopeOnly = [bool]$RBACAtScopeOnly + } + Write-Host 'htParameters:' + $azAPICallConf['htParameters'] | format-table -AutoSize | Out-String + Write-Host 'Add AzGovViz htParameters succeeded' -ForegroundColor Green +} \ No newline at end of file diff --git a/pwsh/dev/functions/addIndexNumberToArray.ps1 b/pwsh/dev/functions/addIndexNumberToArray.ps1 new file mode 100644 index 00000000..49505713 --- /dev/null +++ b/pwsh/dev/functions/addIndexNumberToArray.ps1 @@ -0,0 +1,9 @@ +function addIndexNumberToArray ( + [Parameter(Mandatory = $True)] + [array]$array +) { + for ($i = 0; $i -lt ($array).count; $i++) { + Add-Member -InputObject $array[$i] -Name '#' -Value ($i + 1) -MemberType NoteProperty + } + return $array +} \ No newline at end of file diff --git a/pwsh/dev/functions/addRowToTable.ps1 b/pwsh/dev/functions/addRowToTable.ps1 new file mode 100644 index 00000000..6a3acff4 --- /dev/null +++ b/pwsh/dev/functions/addRowToTable.ps1 @@ -0,0 +1,195 @@ +function addRowToTable() { + Param ( + [string]$level = 0, + [string]$mgName = '', + [string]$mgId = '', + [string]$mgParentId = '', + [string]$mgParentName = '', + [string]$mgASCSecureScore = '', + [string]$Subscription = '', + [string]$SubscriptionId = '', + [string]$SubscriptionQuotaId = '', + [string]$SubscriptionState = '', + [string]$SubscriptionASCSecureScore = '', + [string]$SubscriptionTags = '', + [int]$SubscriptionTagsCount = 0, + [string]$Policy = '', + [string]$PolicyAvailability = '', + [string]$PolicyDescription = '', + [string]$PolicyVariant = '', + [string]$PolicyType = '', + [string]$PolicyCategory = '', + [string]$PolicyDefinitionIdGuid = '', + [string]$PolicyDefinitionId = '', + [string]$PolicyDefintionScope = '', + [string]$PolicyDefintionScopeMgSub = '', + [string]$PolicyDefintionScopeId = '', + [int]$PolicyDefinitionsScopedLimit = 0, + [int]$PolicyDefinitionsScopedCount = 0, + [int]$PolicySetDefinitionsScopedLimit = 0, + [int]$PolicySetDefinitionsScopedCount = 0, + [string]$PolicyDefinitionEffectDefault = '', + [string]$PolicyDefinitionEffectFixed = '', + [string]$PolicyAssignmentScope = '', + [string]$PolicyAssignmentScopeMgSubRg = '', + [string]$PolicyAssignmentScopeName = '', + $PolicyAssignmentNotScopes = '', + [string]$PolicyAssignmentId = '', + [string]$PolicyAssignmentName = '', + [string]$PolicyAssignmentDisplayName = '', + [string]$PolicyAssignmentDescription = '', + [string]$PolicyAssignmentEnforcementMode = '', + $PolicyAssignmentNonComplianceMessages = '', + [string]$PolicyAssignmentIdentity = '', + [int]$PolicyAssignmentLimit = 0, + [int]$PolicyAssignmentCount = 0, + [int]$PolicyAssignmentAtScopeCount = 0, + $PolicyAssignmentParameters, + $PolicyAssignmentParametersFormated, + [int]$PolicySetAssignmentLimit = 0, + [int]$PolicySetAssignmentCount = 0, + [int]$PolicySetAssignmentAtScopeCount = 0, + [int]$PolicyAndPolicySetAssignmentAtScopeCount = 0, + [string]$PolicyAssignmentAssignedBy = '', + [string]$PolicyAssignmentCreatedBy = '', + [string]$PolicyAssignmentCreatedOn = '', + [string]$PolicyAssignmentUpdatedBy = '', + [string]$PolicyAssignmentUpdatedOn = '', + [string]$RoleDefinitionId = '', + [string]$RoleDefinitionName = '', + [string]$RoleAssignmentIdentityDisplayname = '', + [string]$RoleAssignmentIdentitySignInName = '', + [string]$RoleAssignmentIdentityObjectId = '', + [string]$RoleAssignmentIdentityObjectType = '', + [string]$RoleAssignmentId = '', + [string]$RoleAssignmentScope = '', + [string]$RoleAssignmentScopeName = '', + [string]$RoleAssignmentScopeRG = '', + [string]$RoleAssignmentScopeRes = '', + [string]$RoleAssignmentScopeType = '', + [string]$RoleAssignmentCreatedBy = '', + [string]$RoleAssignmentCreatedOn = '', + $RoleAssignmentCreatedOnUnformatted, + [string]$RoleAssignmentUpdatedBy = '', + [string]$RoleAssignmentUpdatedOn = '', + [string]$RoleIsCustom = '', + [string]$RoleAssignableScopes = '', + [int]$RoleAssignmentsLimit = 0, + [int]$RoleAssignmentsCount = 0, + [string]$RoleActions = '', + [string]$RoleNotActions = '', + [string]$RoleDataActions = '', + [string]$RoleNotDataActions = '', + $RoleCanDoRoleAssignments, + [int]$RoleSecurityCustomRoleOwner = 0, + [int]$RoleSecurityOwnerAssignmentSP = 0, + [string]$BlueprintName = '', + [string]$BlueprintId = '', + [string]$BlueprintDisplayName = '', + [string]$BlueprintDescription = '', + [string]$BlueprintScoped = '', + [string]$BlueprintAssignmentVersion = '', + [string]$BlueprintAssignmentId = '', + [string]$RoleAssignmentPIM = '', + [string]$RoleAssignmentPIMAssignmentType = '', + $RoleAssignmentPIMSlotStart = '', + $RoleAssignmentPIMSlotEnd = '' + ) + + $null = $script:newTable.Add([PSCustomObject]@{ + level = $level + mgName = $mgName + mgId = $mgId + mgParentId = $mgParentId + mgParentName = $mgParentName + mgASCSecureScore = $mgASCSecureScore + Subscription = $Subscription + SubscriptionId = $SubscriptionId + SubscriptionQuotaId = $SubscriptionQuotaId + SubscriptionState = $SubscriptionState + SubscriptionASCSecureScore = $SubscriptionASCSecureScore + SubscriptionTags = $SubscriptionTags + SubscriptionTagsCount = $SubscriptionTagsCount + Policy = $Policy + PolicyAvailability = $PolicyAvailability + PolicyDescription = $PolicyDescription + PolicyVariant = $PolicyVariant + PolicyType = $PolicyType + PolicyCategory = $PolicyCategory + PolicyDefinitionIdGuid = $PolicyDefinitionIdGuid + PolicyDefinitionId = $PolicyDefinitionId + PolicyDefintionScope = $PolicyDefintionScope + PolicyDefintionScopeMgSub = $PolicyDefintionScopeMgSub + PolicyDefintionScopeId = $PolicyDefintionScopeId + PolicyDefinitionsScopedLimit = $PolicyDefinitionsScopedLimit + PolicyDefinitionsScopedCount = $PolicyDefinitionsScopedCount + PolicySetDefinitionsScopedLimit = $PolicySetDefinitionsScopedLimit + PolicySetDefinitionsScopedCount = $PolicySetDefinitionsScopedCount + PolicyDefinitionEffectDefault = $PolicyDefinitionEffectDefault + PolicyDefinitionEffectFixed = $PolicyDefinitionEffectFixed + PolicyAssignmentScope = $PolicyAssignmentScope + PolicyAssignmentScopeMgSubRg = $PolicyAssignmentScopeMgSubRg + PolicyAssignmentScopeName = $PolicyAssignmentScopeName + PolicyAssignmentNotScopes = $PolicyAssignmentNotScopes + PolicyAssignmentId = $PolicyAssignmentId + PolicyAssignmentName = $PolicyAssignmentName + PolicyAssignmentDisplayName = $PolicyAssignmentDisplayName + PolicyAssignmentDescription = $PolicyAssignmentDescription + PolicyAssignmentEnforcementMode = $PolicyAssignmentEnforcementMode + PolicyAssignmentNonComplianceMessages = $PolicyAssignmentNonComplianceMessages + PolicyAssignmentIdentity = $PolicyAssignmentIdentity + PolicyAssignmentLimit = $PolicyAssignmentLimit + PolicyAssignmentCount = $PolicyAssignmentCount + PolicyAssignmentAtScopeCount = $PolicyAssignmentAtScopeCount + PolicyAssignmentParameters = $PolicyAssignmentParameters + PolicyAssignmentParametersFormated = $PolicyAssignmentParametersFormated + PolicySetAssignmentLimit = $PolicySetAssignmentLimit + PolicySetAssignmentCount = $PolicySetAssignmentCount + PolicySetAssignmentAtScopeCount = $PolicySetAssignmentAtScopeCount + PolicyAndPolicySetAssignmentAtScopeCount = $PolicyAndPolicySetAssignmentAtScopeCount + PolicyAssignmentAssignedBy = $PolicyAssignmentAssignedBy + PolicyAssignmentCreatedBy = $PolicyAssignmentCreatedBy + PolicyAssignmentCreatedOn = $PolicyAssignmentCreatedOn + PolicyAssignmentUpdatedBy = $PolicyAssignmentUpdatedBy + PolicyAssignmentUpdatedOn = $PolicyAssignmentUpdatedOn + RoleDefinitionId = $RoleDefinitionId + RoleDefinitionName = $RoleDefinitionName + RoleAssignmentIdentityDisplayname = $RoleAssignmentIdentityDisplayname + RoleAssignmentIdentitySignInName = $RoleAssignmentIdentitySignInName + RoleAssignmentIdentityObjectId = $RoleAssignmentIdentityObjectId + RoleAssignmentIdentityObjectType = $RoleAssignmentIdentityObjectType + RoleAssignmentId = $RoleAssignmentId + RoleAssignmentScope = $RoleAssignmentScope + RoleAssignmentScopeName = $RoleAssignmentScopeName + RoleAssignmentScopeRG = $RoleAssignmentScopeRG + RoleAssignmentScopeRes = $RoleAssignmentScopeRes + RoleAssignmentScopeType = $RoleAssignmentScopeType + RoleIsCustom = $RoleIsCustom + RoleAssignableScopes = $RoleAssignableScopes + RoleAssignmentCreatedBy = $RoleAssignmentCreatedBy + RoleAssignmentCreatedOn = $RoleAssignmentCreatedOn + RoleAssignmentCreatedOnUnformatted = $RoleAssignmentCreatedOnUnformatted + RoleAssignmentUpdatedBy = $RoleAssignmentUpdatedBy + RoleAssignmentUpdatedOn = $RoleAssignmentUpdatedOn + RoleAssignmentsLimit = $RoleAssignmentsLimit + RoleAssignmentsCount = $RoleAssignmentsCount + RoleActions = $RoleActions + RoleNotActions = $RoleNotActions + RoleDataActions = $RoleDataActions + RoleNotDataActions = $RoleNotDataActions + RoleCanDoRoleAssignments = $RoleCanDoRoleAssignments + RoleSecurityCustomRoleOwner = $RoleSecurityCustomRoleOwner + RoleSecurityOwnerAssignmentSP = $RoleSecurityOwnerAssignmentSP + BlueprintName = $BlueprintName + BlueprintId = $BlueprintId + BlueprintDisplayName = $BlueprintDisplayName + BlueprintDescription = $BlueprintDescription + BlueprintScoped = $BlueprintScoped + BlueprintAssignmentVersion = $BlueprintAssignmentVersion + BlueprintAssignmentId = $BlueprintAssignmentId + RoleAssignmentPIM = $RoleAssignmentPIM + RoleAssignmentPIMAssignmentType = $RoleAssignmentPIMAssignmentType + RoleAssignmentPIMSlotStart = $RoleAssignmentPIMSlotStart + RoleAssignmentPIMSlotEnd = $RoleAssignmentPIMSlotEnd + }) +} \ No newline at end of file diff --git a/pwsh/dev/functions/buildJSON.ps1 b/pwsh/dev/functions/buildJSON.ps1 new file mode 100644 index 00000000..5e05d79d --- /dev/null +++ b/pwsh/dev/functions/buildJSON.ps1 @@ -0,0 +1,435 @@ +function buildJSON { + #$fileTimestamp = Get-Date -Format "yyyyMM-dd HHmmss" + $startJSON = Get-Date + $startBuildHt = Get-Date + + Write-Host 'Create Hierarchy JSON' + Write-Host ' Create ht for JSON' + + $htJSON = [ordered]@{} + $htJSON.ManagementGroups = [ordered]@{} + + $MgIds = ($optimizedTableForPathQuery) | select-object -property level, MgId, MgName, mgParentId, mgParentName | Sort-Object -property level, MgId -unique + $grpScopePolicyDefinitionsCustom = (($htCacheDefinitionsPolicy).values).where( { $_.Type -eq 'Custom' }) | Group-Object ScopeMgSub + $grpMgScopePolicyDefinitionsCustom = ($grpScopePolicyDefinitionsCustom.where( { $_.Name -eq 'Mg' }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId) + $grpSubScopePolicyDefinitionsCustom = ($grpScopePolicyDefinitionsCustom.where( { $_.Name -eq 'Sub' }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId) + + $grpScopePolicySetDefinitionsCustom = (($htCacheDefinitionsPolicySet).values).where( { $_.Type -eq 'Custom' }) | Group-Object ScopeMgSub + $grpMgScopePolicySetDefinitionsCustom = $grpScopePolicySetDefinitionsCustom.where( { $_.Name -eq 'Mg' }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId + $grpSubScopePolicySetDefinitionsCustom = $grpScopePolicySetDefinitionsCustom.where( { $_.Name -eq 'Sub' }).Group | Sort-Object -Property PolicyDefinitionId | Group-Object ScopeId + + $grpScopePolicyAssignments = ($htCacheAssignmentsPolicy).values | Group-Object -Property AssignmentScopeMgSubRg + $grpMgScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq 'Mg' }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId + $grpSubScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq 'Sub' }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId + + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + if (-not $JsonExportExcludeResourceGroups) { + $grpRGScopePolicyAssignments = $grpScopePolicyAssignments.where( { $_.Name -eq 'RG' }).Group | Sort-Object @{Expression = { $_.Assignment.Id } } | Group-Object -Property AssignmentScopeId + $htSubRGPolicyAssignments = @{} + foreach ($rgpa in $grpRGScopePolicyAssignments) { + $subId = ($rgpa.Name).split('/')[0] + if (-not $htSubRGPolicyAssignments.($subId)) { + $htSubRGPolicyAssignments.($subId) = @{} + } + if (-not $htSubRGPolicyAssignments.($subId).PolicyAssignments) { + $htSubRGPolicyAssignments.($subId).PolicyAssignments = @() + } + $htSubRGPolicyAssignments.($subId).PolicyAssignments += $rgpa.group + } + } + } + + $grpScopeRoleAssignments = ($htCacheAssignmentsRole).values | Group-Object -Property AssignmentScopeTenMgSubRgRes + $grpTenantScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'Tenant' }).Group | Group-Object -Property AssignmentScopeId + $grpMgScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'Mg' }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId + $grpSubScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'Sub' }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId + + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + if (-not $JsonExportExcludeResourceGroups) { + $grpRGScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'RG' }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId + $htSubRGRoleAssignments = @{} + foreach ($rgra in $grpRGScopeRoleAssignments) { + $subId = ($rgra.Name).split('/')[0] + if (-not $htSubRGRoleAssignments.($subId)) { + $htSubRGRoleAssignments.($subId) = @{} + } + if (-not $htSubRGRoleAssignments.($subId).RoleAssignments) { + $htSubRGRoleAssignments.($subId).RoleAssignments = @() + } + $htSubRGRoleAssignments.($subId).RoleAssignments += $rgra.group + } + + #res + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + if (-not $JsonExportExcludeResources) { + $grpResScopeRoleAssignments = $grpScopeRoleAssignments.where( { $_.Name -eq 'Res' }).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } } | Group-Object -Property AssignmentScopeId + $htSubResRoleAssignments = @{} + foreach ($resra in $grpResScopeRoleAssignments.Group) { + $raSplit = ($resra.Assignment.RoleAssignmentId).split('/') + $splitSubId = $raSplit[2] + $splitRg = $raSplit[4] + if (-not $htSubResRoleAssignments.($splitSubId)) { + $htSubResRoleAssignments.($splitSubId) = @{} + } + if (-not $htSubResRoleAssignments.($splitSubId).($splitRg)) { + $htSubResRoleAssignments.($splitSubId).($splitRg) = @{} + + } + + $resourceName = $resra.AssignmentScopeId.split('/')[2] + if (-not $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)")) { + $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)") = @{} + + } + if (-not $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments) { + $htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments = [ordered]@{} + + } + ($htSubResRoleAssignments.($splitSubId).($splitRg).("$($resra.ResourceType)_$($resourceName)").RoleAssignments.($resra.Assignment.RoleAssignmentId)) = $resra.Assignment + } + } + } + } + + } + + $bluePrintsAssignmentsAtScope = ($htCacheAssignmentsBlueprint).keys | Sort-Object + $bluePrintDefinitions = ($htCacheDefinitionsBlueprint).Keys | Sort-Object + $subscriptions = ($optimizedTableForPathQuery.where( { -not [string]::IsNullOrEmpty($_.subscriptionId) })) | Select-Object mgId, Subscription* | Sort-Object -Property subscriptionId -Unique + foreach ($mg in $MgIds) { + + $htJSON.ManagementGroups.($mg.MgId) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).MgId = $mg.MgId + $htJSON.ManagementGroups.($mg.MgId).MgName = $mg.MgName + $htJSON.ManagementGroups.($mg.MgId).mgParentId = $mg.mgParentId + $htJSON.ManagementGroups.($mg.MgId).mgParentName = $mg.mgParentName + $htJSON.ManagementGroups.($mg.MgId).level = $mg.level + $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).RoleAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions = [ordered]@{} + + foreach ($PolDef in (($grpMgScopePolicyDefinitionsCustom).where( { $_.Name -eq $mg.MgId })).group) { + $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom.($PolDef.Id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicyDefinitionsCustom.($PolDef.Id) = $PolDef.Json + } + + foreach ($PolSetDef in (($grpMgScopePolicySetDefinitionsCustom).where( { $_.Name -eq $mg.MgId })).group) { + $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom.($PolSetDef.Id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicySetDefinitionsCustom.($PolSetDef.Id) = $PolSetDef.Json + } + + foreach ($PolAssignment in ($grpMgScopePolicyAssignments).where( { $_.Name -eq $mg.MgId }).group) { + $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments.($PolAssignment.Assignment.id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).PolicyAssignments.($PolAssignment.Assignment.id) = $PolAssignment.Assignment + } + + foreach ($RoleAssignment in ($grpMgScopeRoleAssignments).where( { $_.Name -eq $mg.MgId }).group) { + $htJSON.ManagementGroups.($mg.MgId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = $RoleAssignment.Assignment + } + + foreach ($BlueprintDefinition in ($bluePrintDefinitions).where( { $_ -like "/providers/Microsoft.Management/managementGroups/$($mg.MgId)/*" })) { + $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions.($BlueprintDefinition) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).BlueprintDefinitions.($BlueprintDefinition) = $BlueprintDefinition + } + + if (($htDiagnosticSettingsMgSub).mg.($mg.MgId)) { + foreach ($entry in ($htDiagnosticSettingsMgSub).mg.($mg.MgId).keys | Sort-Object) { + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry) = [ordered]@{} + foreach ($diagset in ($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.keys | Sort-Object) { + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Name = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticSettingName) + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Type = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticTargetType) + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).TargetId = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticTargetId) + $htJSON.ManagementGroups.($mg.MgId).DiagnosticSettings.($entry).Settings = (($htDiagnosticSettingsMgSub).mg.($mg.MgId).$entry.$diagset.DiagnosticCategories) + } + } + } + + foreach ($subscription in $subscriptions) { + if ($subscription.MgId -eq $mg.MgId) { + + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionName = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionQuotaId = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionState = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionTags = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionName = $subscription.Subscription + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionQuotaId = $subscription.SubscriptionQuotaId + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionState = $subscription.SubscriptionState + if ($htSubscriptionTags.($subscription.SubscriptionId)) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).SubscriptionTags = $htSubscriptionTags.($subscription.SubscriptionId).getEnumerator() | Sort-Object Key -CaseSensitive + } + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings = [ordered]@{} + + foreach ($PolDef in (($grpSubScopePolicyDefinitionsCustom).where( { $_.Name -eq $subscription.subscriptionId })).group) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom.($PolDef.Id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyDefinitionsCustom.($PolDef.Id) = $PolDef.Json + } + + foreach ($PolSetDef in (($grpSubScopePolicySetDefinitionsCustom).where( { $_.Name -eq $subscription.subscriptionId })).group) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom.($PolSetDef.Id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicySetDefinitionsCustom.($PolSetDef.Id) = $PolSetDef.Json + } + + foreach ($PolAssignment in ($grpSubScopePolicyAssignments).where( { $_.Name -eq $subscription.subscriptionId }).group) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments.($PolAssignment.Assignment.id) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).PolicyAssignments.($PolAssignment.Assignment.id) = $PolAssignment.Assignment + } + + foreach ($RoleAssignment in ($grpSubScopeRoleAssignments).where( { $_.Name -eq $subscription.subscriptionId }).group) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).RoleAssignments.($RoleAssignment.Assignment.RoleAssignmentId) = $RoleAssignment.Assignment + } + + foreach ($BlueprintDefinition in ($bluePrintDefinitions).where( { $_ -like "/subscriptions/$($subscription.subscriptionId)/*" })) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions.($BlueprintDefinition) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintDefinitions.($BlueprintDefinition) = $BlueprintDefinition + } + + foreach ($BlueprintsAssignment in ($blueprintsAssignmentsAtScope).where( { $_ -like "/subscriptions/$($subscription.subscriptionId)/*" })) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments.($BlueprintsAssignment) = [ordered]@{} + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).BlueprintAssignments.($BlueprintsAssignment) = $BlueprintsAssignment + } + + if (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId)) { + foreach ($entry in ($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).keys | Sort-Object) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry) = [ordered]@{} + foreach ($diagset in ($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.keys | Sort-Object) { + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Name = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticSettingName) + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Type = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticTargetType) + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).TargetId = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticTargetId) + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).DiagnosticSettings.($entry).Settings = (($htDiagnosticSettingsMgSub).sub.($subscription.subscriptionId).$entry.$diagset.DiagnosticCategories) + } + } + } + + + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + if (-not $JsonExportExcludeResourceGroups) { + $htTemp = @{} + if (-not $htTemp.ResourceGroups) { + $htTemp.ResourceGroups = @{} + } + + if ($htSubRGPolicyAssignments.($subscription.subscriptionId)) { + foreach ($rgpa in $htSubRGPolicyAssignments.($subscription.subscriptionId).PolicyAssignments) { + $rgName = ($rgpa.AssignmentScopeId).split('/')[1] + if (-not $htTemp.ResourceGroups.($rgName)) { + $htTemp.ResourceGroups.($rgName) = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rgName).PolicyAssignments) { + $htTemp.ResourceGroups.($rgName).PolicyAssignments = [ordered]@{} + } + $htTemp.ResourceGroups.($rgName).PolicyAssignments.($rgpa.Assignment.id) = $rgpa.Assignment + } + } + } + } + + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + if (-not $JsonExportExcludeResourceGroups) { + if (-not $htTemp) { + $htTemp = @{} + } + if (-not $htTemp.ResourceGroups) { + $htTemp.ResourceGroups = @{} + } + if ($htSubRGRoleAssignments.($subscription.subscriptionId)) { + foreach ($rgra in $htSubRGRoleAssignments.($subscription.subscriptionId).RoleAssignments) { + $rgName = ($rgra.AssignmentScopeId).split('/')[1] + if (-not $htTemp.ResourceGroups.($rgName)) { + $htTemp.ResourceGroups.($rgName) = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rgName).RoleAssignments) { + $htTemp.ResourceGroups.($rgName).RoleAssignments = [ordered]@{} + } + $htTemp.ResourceGroups.($rgName).RoleAssignments.($rgra.Assignment.RoleAssignmentId) = $rgra.Assignment + } + } + # + if (-not $JsonExportExcludeResources) { + if (-not $htTemp.ResourceGroups) { + $htTemp.ResourceGroups = @{} + } + if ($htSubResRoleAssignments.($subscription.subscriptionId)) { + foreach ($rg in $htSubResRoleAssignments.($subscription.subscriptionId).keys) { + foreach ($res in $htSubResRoleAssignments.($subscription.subscriptionId).($rg).Keys | Sort-Object) { + $rgName = ($resra.AssignmentScopeId).split('/')[1] + if (-not $htTemp.ResourceGroups.($rg)) { + $htTemp.ResourceGroups.($rg) = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rg).Resources) { + $htTemp.ResourceGroups.($rg).Resources = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rg).Resources.($res)) { + $htTemp.ResourceGroups.($rg).Resources.($res) = [ordered]@{} + } + if (-not $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments) { + $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments = [ordered]@{} + } + $htTemp.ResourceGroups.($rg).Resources.($res).RoleAssignments = $htSubResRoleAssignments.($subscription.subscriptionId).($rg).($res).RoleAssignments + } + } + } + } + } + } + + if ($htTemp) { + $sortedHt = [ordered]@{} + foreach ($key in ($htTemp.ResourceGroups.keys | Sort-Object)) { + $sortedHt.($key) = $htTemp.ResourceGroups.($key) + } + $htJSON.ManagementGroups.($mg.MgId).Subscriptions.($subscription.subscriptionId).ResourceGroups = $sortedHt + $htTemp = $null + $sortedHt = $null + } + } + } + } + + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + if ($ManagementGroupsOnly) { + $JSONPath = "JSON_ManagementGroupsOnly_$($ManagementGroupId)" + } + else { + $JSONPath = "JSON_$($ManagementGroupId)" + } + + if (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)") { + Write-Host ' Cleaning old state (Pipeline only)' + Remove-Item -Recurse -Force "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)" + } + } + else { + if ($ManagementGroupsOnly) { + $JSONPath = "JSON_ManagementGroupsOnly_$($ManagementGroupId)_$($fileTimestamp)" + } + else { + $JSONPath = "JSON_$($ManagementGroupId)_$($fileTimestamp)" + } + Write-Host " Creating new state ($($JSONPath)) (local only))" + } + + $null = new-item -Name $JSONPath -ItemType directory -path $outputPath + + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + "The directory '$($JSONPath)' will be rebuilt during the AzDO Pipeline run. __Do not save any files in this directory, files and folders will be deleted!__" | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)ReadMe_important.md" -Encoding utf8 + } + + $null = new-item -Name "$($JSONPath)$($DirectorySeparatorChar)Definitions" -ItemType directory -path $outputPath + + + + + $htJSON.RoleDefinitions = [ordered]@{} + $pathRoleDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)RoleDefinitions" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitions)")) { + $null = new-item -Name $pathRoleDefinitions -ItemType directory -path $outputPath + $pathRoleDefinitionCustom = "$($pathRoleDefinitions)$($DirectorySeparatorChar)Custom" + $pathRoleDefinitionBuiltIn = "$($pathRoleDefinitions)$($DirectorySeparatorChar)BuiltIn" + $null = new-item -Name "$($pathRoleDefinitionCustom)" -ItemType directory -path $outputPath + $null = new-item -Name "$($pathRoleDefinitionBuiltIn)" -ItemType directory -path $outputPath + } + + if (($htCacheDefinitionsRole).Keys.Count -gt 0) { + foreach ($roleDefinition in ($htCacheDefinitionsRole).Keys.where( { ($htCacheDefinitionsRole).($_).IsCustom }) | Sort-Object) { + $htJSON.RoleDefinitions.($roleDefinition) = ($htCacheDefinitionsRole).($roleDefinition).Json.properties + $jsonConverted = ($htCacheDefinitionsRole).($roleDefinition).Json.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitionCustom)$($DirectorySeparatorChar)$(removeInvalidFileNameChars ($htCacheDefinitionsRole).($roleDefinition).Name) ($(($htCacheDefinitionsRole).($roleDefinition).Id)).json" -Encoding utf8 + } + foreach ($roleDefinition in ($htCacheDefinitionsRole).Keys.where( { -not ($htCacheDefinitionsRole).($_).IsCustom })) { + $jsonConverted = ($htCacheDefinitionsRole).($roleDefinition).Json | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathRoleDefinitionBuiltIn)$($DirectorySeparatorChar)$(removeInvalidFileNameChars ($htCacheDefinitionsRole).($roleDefinition).Name ) ($(($htCacheDefinitionsRole).($roleDefinition).Id)).json" -Encoding utf8 + } + } + + $pathPolicyDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicyDefinitions)")) { + $null = new-item -Name $pathPolicyDefinitions -ItemType directory -path $outputPath + $pathPolicyDefinitionBuiltIn = "$($pathPolicyDefinitions)$($DirectorySeparatorChar)BuiltIn" + $null = new-item -Name "$($pathPolicyDefinitionBuiltIn)" -ItemType directory -path $outputPath + } + if (($htCacheDefinitionsPolicy).Keys.Count -gt 0) { + foreach ($policyDefinition in ($htCacheDefinitionsPolicy).Keys.where( { ($htCacheDefinitionsPolicy).($_).Type -eq 'BuiltIn' })) { + $jsonConverted = ($htCacheDefinitionsPolicy).($policyDefinition).Json.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicyDefinitionBuiltIn)$($DirectorySeparatorChar)$(removeInvalidFileNameChars ($htCacheDefinitionsPolicy).($policyDefinition).displayName) ($(($htCacheDefinitionsPolicy).($policyDefinition).Json.name)).json" -Encoding utf8 + } + } + + $pathPolicySetDefinitions = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicySetDefinitions)")) { + $null = new-item -Name $pathPolicySetDefinitions -ItemType directory -path $outputPath + $pathPolicySetDefinitionBuiltIn = "$($pathPolicySetDefinitions)$($DirectorySeparatorChar)BuiltIn" + $null = new-item -Name "$($pathPolicySetDefinitionBuiltIn)" -ItemType directory -path $outputPath + } + if (($htCacheDefinitionsPolicySet).Keys.Count -gt 0) { + foreach ($policySetDefinition in ($htCacheDefinitionsPolicySet).Keys.where( { ($htCacheDefinitionsPolicySet).($_).Type -eq 'BuiltIn' })) { + $jsonConverted = ($htCacheDefinitionsPolicySet).($policySetDefinition).Json.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($pathPolicySetDefinitionBuiltIn)$($DirectorySeparatorChar)$(removeInvalidFileNameChars ($htCacheDefinitionsPolicySet).($policySetDefinition).displayName) ($(($htCacheDefinitionsPolicySet).($policySetDefinition).Json.name)).json" -Encoding utf8 + } + } + + $endBuildHt = Get-Date + Write-Host " ht for JSON creation duration: $((NEW-TIMESPAN -Start $startBuildHt -End $endBuildHt).TotalSeconds) seconds" + + $startBuildJSON = Get-Date + Write-Host ' Build JSON' + + + $null = new-item -Name "$($JSONPath)$($DirectorySeparatorChar)Tenant" -ItemType directory -path $outputPath + + $htTree = [ordered]@{} + $htTree.'Tenant' = [ordered] @{} + $htTree.Tenant.TenantId = $azAPICallConf['checkContext'].Tenant.Id + $htTree.Tenant.RoleAssignments = [ordered]@{} + foreach ($RoleAssignment in ($grpTenantScopeRoleAssignments).Group | Sort-Object @{Expression = { $_.Assignment.RoleAssignmentId } }) { + + $htTree.Tenant.RoleAssignments.$($RoleAssignment.Assignment.RoleAssignmentId) = [ordered]@{} + $htTree.Tenant.RoleAssignments.$($RoleAssignment.Assignment.RoleAssignmentId) = $RoleAssignment.Assignment + + if ($RoleAssignment.Assignment.PIM -eq 'true') { + $pim = 'PIM_' + } + else { + $pim = '' + } + $jsonConverted = ($RoleAssignment.Assignment | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)Tenant$($DirectorySeparatorChar)ra_$($RoleAssignment.Assignment.ObjectType)_$($pim)$($RoleAssignment.Assignment.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)RoleAssignments$($DirectorySeparatorChar)Tenant" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($RoleAssignment.Assignment.ObjectType)_$($pim)$($RoleAssignment.Assignment.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + + $htTree.'Tenant'.'ManagementGroups' = [ordered] @{} + $json = $htTree.'Tenant' + + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)Assignments")) { + $null = new-item -Name "$($JSONPath)$($DirectorySeparatorChar)Assignments" -ItemType directory -path $outputPath + } + + buildTree -mgId $ManagementGroupId -json $json -prnt "$($JSONPath)$($DirectorySeparatorChar)Tenant" + + $htTree.'Tenant'.'CustomRoleDefinitions' = $htJSON.RoleDefinitions + + Write-Host " Exporting Tenant JSON '$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)$($fileName).json'" + $htTree | ConvertTo-JSON -Depth 99 | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($JSONPath)$($DirectorySeparatorChar)$($fileName).json" -Encoding utf8 -Force + + $endBuildJSON = Get-Date + Write-Host " Building JSON duration: $((NEW-TIMESPAN -Start $startBuildJSON -End $endBuildJSON).TotalSeconds) seconds" + + $endJSON = Get-Date + Write-Host "Creating Hierarchy JSON duration: $((NEW-TIMESPAN -Start $startJSON -End $endJSON).TotalSeconds) seconds" +} \ No newline at end of file diff --git a/pwsh/dev/functions/buildMD.ps1 b/pwsh/dev/functions/buildMD.ps1 new file mode 100644 index 00000000..dfaedaa3 --- /dev/null +++ b/pwsh/dev/functions/buildMD.ps1 @@ -0,0 +1,170 @@ +function buildMD { + Write-Host 'Building Markdown' + #test + Write-Host 'htParameters.onAzureDevOpsOrGitHubActions:' $azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions + Write-Host 'htParameters.codeRunPlatform:' $azAPICallConf['htParameters'].codeRunPlatform + $startBuildMD = Get-Date + $script:arrayMgs = [System.Collections.ArrayList]@() + $script:arraySubs = [System.Collections.ArrayList]@() + $script:arraySubsOos = [System.Collections.ArrayList]@() + $markdown = $null + $script:markdownhierarchyMgs = $null + $script:markdownhierarchySubs = $null + $script:markdownTable = $null + + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions -eq $true) { + if ($azAPICallConf['htParameters'].onAzureDevOps -eq $true) { + $markdown += @" +# AzGovViz - Management Group Hierarchy + +## Hierarchy Diagram (Mermaid) + +::: mermaid + graph $($MermaidDirection.ToUpper());`n +"@ + } + if ($azAPICallConf['htParameters'].onGitHubActions -eq $true) { + $marks = '```' + $markdown += @" +# AzGovViz - Management Group Hierarchy + +## Hierarchy Diagram (Mermaid) + +$($marks)mermaid + graph $($MermaidDirection.ToUpper());`n +"@ + } + + } + else { + $markdown += @" +# AzGovViz - Management Group Hierarchy + +$executionDateTimeInternationalReadable ($currentTimeZone) + +## Hierarchy Diagram (Mermaid) + +::: mermaid + graph $($MermaidDirection.ToUpper());`n +"@ + } + + processDiagramMermaid + + $markdown += @" +$markdownhierarchyMgs +$markdownhierarchySubs + classDef mgr fill:#D9F0FF,stroke:#56595E,color:#000000,stroke-width:1px; + classDef subs fill:#EEEEEE,stroke:#56595E,color:#000000,stroke-width:1px; +"@ + + if (($arraySubsOos).count -gt 0) { + $markdown += @' + classDef subsoos fill:#FFCBC7,stroke:#56595E,color:#000000,stroke-width:1px; +'@ + } + + $markdown += @" + classDef mgrprnts fill:#FFFFFF,stroke:#56595E,color:#000000,stroke-width:1px; + class $(($arrayMgs | Sort-Object -unique) -join ',') mgr; + class $(($arraySubs | Sort-Object -unique) -join ',') subs; +"@ + + if (($arraySubsOos).count -gt 0) { + $markdown += @" + class $(($arraySubsOos | Sort-Object -unique) -join ',') subsoos; +"@ + } + + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions -eq $true) { + if ($azAPICallConf['htParameters'].onAzureDevOps -eq $true) { + $markdown += @" +class $mermaidprnts mgrprnts; +::: + +"@ + } + if ($azAPICallConf['htParameters'].onGitHubActions -eq $true) { +` + $marks = '```' + $markdown += @" +class $mermaidprnts mgrprnts; +$marks + +"@ + } + } + else { + $markdown += @" +class $mermaidprnts mgrprnts; +::: + +"@ + } + + $markdown += @" +## Summary +`n +"@ + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + $markdown += @" +Total Management Groups: $totalMgCount (depth $mgDepth)\`n +"@ + + if (($arraySubsOos).count -gt 0) { + $markdown += @" +Total Subscriptions: $totalSubIncludedAndExcludedCount ($totalSubOutOfScopeCount out-of-scope)\`n +"@ + } + else { + $markdown += @" +Total Subscriptions: $totalSubIncludedAndExcludedCount\`n +"@ + } + + $markdown += @" +Total Custom Policy definitions: $tenantCustomPoliciesCount\ +Total Custom PolicySet definitions: $tenantCustompolicySetsCount\ +Total Policy assignments: $($totalPolicyAssignmentsCount)\ +Total Policy assignments ManagementGroups $($totalPolicyAssignmentsCountMg)\ +Total Policy assignments Subscriptions $($totalPolicyAssignmentsCountSub)\ +Total Policy assignments ResourceGroups: $($totalPolicyAssignmentsCountRg)\ +Total Custom Role definitions: $totalRoleDefinitionsCustomCount\ +Total Role assignments: $totalRoleAssignmentsCount\ +Total Role assignments (Tenant): $totalRoleAssignmentsCountTen\ +Total Role assignments (ManagementGroups): $totalRoleAssignmentsCountMG\ +Total Role assignments (Subscriptions): $totalRoleAssignmentsCountSub\ +Total Role assignments (ResourceGroups and Resources): $totalRoleAssignmentsResourceGroupsAndResourcesCount\ +Total Blueprint definitions: $totalBlueprintDefinitionsCount\ +Total Blueprint assignments: $totalBlueprintAssignmentsCount\ +Total Resources: $totalResourceCount\ +Total Resource Types: $totalResourceTypesCount +"@ + + } + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $true) { + $mgsDetails = ($optimizedTableForPathQueryMg | Select-Object Level, MgId -Unique) + $mgDepth = ($mgsDetails.Level | Measure-Object -maximum).Maximum + $totalMgCount = ($mgsDetails).count + $totalSubCount = ($optimizedTableForPathQuerySub).count + + $markdown += @" +Total Management Groups: $totalMgCount (depth $mgDepth)\ +Total Subscriptions: $totalSubCount +"@ + + } + + $markdown += @" +`n +## Hierarchy Table + +| **MgLevel** | **MgName** | **MgId** | **MgParentName** | **MgParentId** | **SubName** | **SubId** | +|-------------|-------------|-------------|-------------|-------------|-------------|-------------| +$markdownTable +"@ + + $markdown | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).md" -Encoding utf8 -Force + $endBuildMD = Get-Date + Write-Host "Building Markdown total duration: $((NEW-TIMESPAN -Start $startBuildMD -End $endBuildMD).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildMD -End $endBuildMD).TotalSeconds) seconds)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/buildPolicyAllJSON.ps1 b/pwsh/dev/functions/buildPolicyAllJSON.ps1 new file mode 100644 index 00000000..55bb149b --- /dev/null +++ b/pwsh/dev/functions/buildPolicyAllJSON.ps1 @@ -0,0 +1,53 @@ +function buildPolicyAllJSON { + Write-Host 'Creating PolicyAll JSON' + $startPolicyAllJSON = Get-Date + $htPolicyAndPolicySet = [ordered]@{} + $htPolicyAndPolicySet.Policy = [ordered]@{} + $htPolicyAndPolicySet.PolicySet = [ordered]@{} + foreach ($policy in ($tenantPoliciesDetailed | Sort-Object -Property Type, ScopeMGLevel, PolicyDefinitionId)) { + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()) = [ordered]@{} + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyType = $policy.Type + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).ScopeMGLevel = $policy.ScopeMGLevel + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).Scope = $policy.Scope + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).ScopeId = $policy.scopeId + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyDisplayName = $policy.PolicyDisplayName + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyDefinitionName = $policy.PolicyDefinitionName + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyDefinitionId = $policy.PolicyDefinitionId + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyEffect = $policy.PolicyEffect + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).PolicyCategory = $policy.PolicyCategory + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UniqueAssignmentsCount = $policy.UniqueAssignmentsCount + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UniqueAssignments = $policy.UniqueAssignments + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UsedInPolicySetsCount = $policy.UsedInPolicySetsCount + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UsedInPolicySets = $policy.UsedInPolicySet4JSON + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).CreatedOn = $policy.CreatedOn + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).CreatedBy = $policy.CreatedByJson + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UpdatedOn = $policy.UpdatedOn + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).UpdatedBy = $policy.UpdatedByJson + $htPolicyAndPolicySet.Policy.($policy.PolicyDefinitionId.ToLower()).JSON = $policy.Json + } + foreach ($policySet in ($tenantPolicySetsDetailed | Sort-Object -Property Type, ScopeMGLevel, PolicySetDefinitionId)) { + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()) = [ordered]@{} + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetType = $policy.Type + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).ScopeMGLevel = $policySet.ScopeMGLevel + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).Scope = $policySet.Scope + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).ScopeId = $policySet.scopeId + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetDisplayName = $policySet.PolicySetDisplayName + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetDefinitionName = $policySet.PolicySetDefinitionName + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetDefinitionId = $policySet.PolicySetDefinitionId + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PolicySetCategory = $policySet.PolicySetCategory + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).UniqueAssignmentsCount = $policySet.UniqueAssignmentsCount + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).UniqueAssignments = $policySet.UniqueAssignments + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PoliciesUsedCount = $policySet.PoliciesUsedCount + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).PoliciesUsed = $policySet.PoliciesUsed4JSON + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).CreatedOn = $policySet.CreatedOn + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).CreatedBy = $policySet.CreatedByJson + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).UpdatedOn = $policySet.UpdatedOn + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).UpdatedBy = $policySet.UpdatedByJson + $htPolicyAndPolicySet.PolicySet.($policySet.PolicySetDefinitionId.ToLower()).JSON = $policySet.Json + } + Write-Host " Exporting PolicyAll JSON '$($outputPath)$($DirectorySeparatorChar)$($fileName)_PolicyAll.json'" + $htPolicyAndPolicySet | ConvertTo-JSON -Depth 99 | Set-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_PolicyAll.json" -Encoding utf8 -Force + + $endPolicyAllJSON = Get-Date + Write-Host "Creating PolicyAll JSON duration: $((NEW-TIMESPAN -Start $startPolicyAllJSON -End $endPolicyAllJSON).TotalSeconds) seconds" +} diff --git a/pwsh/dev/functions/buildTree.ps1 b/pwsh/dev/functions/buildTree.ps1 new file mode 100644 index 00000000..ca63b302 --- /dev/null +++ b/pwsh/dev/functions/buildTree.ps1 @@ -0,0 +1,276 @@ +function buildTree($mgId, $prnt) { + $getMg = $arrayEntitiesFromAPI.where( { $_.type -eq 'Microsoft.Management/managementGroups' -and $_.name -eq $mgId }) + $childrenManagementGroups = $arrayEntitiesFromAPI.where( { $_.type -eq 'Microsoft.Management/managementGroups' -and $_.properties.parent.id -eq "/providers/Microsoft.Management/managementGroups/$($getMg.Name)" }) + $mgNameValid = removeInvalidFileNameChars $getMg.Name + $mgDisplayNameValid = removeInvalidFileNameChars $getMg.properties.displayName + $prntx = "$($prnt)$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)")) { + $null = new-item -Name $prntx -ItemType directory -path $outputPath + } + + if (-not $json.'ManagementGroups') { + $json.'ManagementGroups' = [ordered]@{} + } + $json = $json.'ManagementGroups'.($getMg.Name) = [ordered]@{} + foreach ($mgCap in $htJSON.ManagementGroups.($getMg.Name).keys) { + $json.$mgCap = $htJSON.ManagementGroups.($getMg.Name).$mgCap + if ($mgCap -eq 'PolicyDefinitionsCustom') { + $mgCapShort = 'pd' + foreach ($pdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($pdc) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + if ($mgCap -eq 'PolicySetDefinitionsCustom') { + $mgCapShort = 'psd' + foreach ($psdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($psdc) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + if ($mgCap -eq 'PolicyAssignments') { + $mgCapShort = 'pa' + foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($pa) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($mgCap)$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + #marker + if ($mgCap -eq 'RoleAssignments') { + $mgCapShort = 'ra' + foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($ra) + if ($hlp.PIM -eq 'true') { + $pim = 'PIM_' + } + else { + $pim = '' + } + $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($prntx)$($DirectorySeparatorChar)$($mgCapShort)_$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($mgCap)$($DirectorySeparatorChar)Mg$($DirectorySeparatorChar)$($mgNameValid) ($($mgDisplayNameValid))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + } + + if ($mgCap -eq 'Subscriptions') { + foreach ($sub in $htJSON.ManagementGroups.($getMg.Name).($mgCap).Keys) { + $subNameValid = removeInvalidFileNameChars $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).SubscriptionName + $subFolderName = "$($prntx)$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + $null = new-item -Name $subFolderName -ItemType directory -path $outputPath + foreach ($subCap in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).Keys) { + if ($subCap -eq 'PolicyDefinitionsCustom') { + $subCapShort = 'pd' + foreach ($pdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($pdc) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicyDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + if ($subCap -eq 'PolicySetDefinitionsCustom') { + $subCapShort = 'psd' + foreach ($psdc in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($psdc) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp.properties | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Definitions$($DirectorySeparatorChar)PolicySetDefinitions$($DirectorySeparatorChar)Custom$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + if ($subCap -eq 'PolicyAssignments') { + $subCapShort = 'pa' + foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($pa) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($subCap)$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($(removeInvalidFileNameChars $hlp.name)).json" -Encoding utf8 + } + } + #marker + if ($subCap -eq 'RoleAssignments') { + $subCapShort = 'ra' + foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($ra) + if ($hlp.PIM -eq 'true') { + $pim = 'PIM_' + } + else { + $pim = '' + } + $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($subCapShort)_$($pim)$($hlp.ObjectType)_$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)$($subCap)$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + } + + #RG Pol + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsOnPolicy) { + if (-not $JsonExportExcludeResourceGroups) { + if ($subCap -eq 'ResourceGroups') { + foreach ($rg in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys | Sort-Object) { + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)")) { + $null = new-item -Name "$($subFolderName)$($DirectorySeparatorChar)$($rg)" -ItemType directory -path "$($outputPath)" + } + foreach ($pa in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).PolicyAssignments.keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).PolicyAssignments.($pa) + if ([string]::IsNullOrEmpty($hlp.properties.displayName)) { + $displayName = 'noDisplayNameGiven' + } + else { + $displayName = removeInvalidFileNameChars $hlp.properties.displayName + } + $jsonConverted = $hlp | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)pa_$($displayName) ($($hlp.name)).json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)PolicyAssignments$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))$($DirectorySeparatorChar)$($rg)" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($displayName) ($($hlp.name)).json" -Encoding utf8 + } + } + } + } + } + + #RG RoleAss + #marker + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + if (-not $JsonExportExcludeResourceGroups) { + if ($subCap -eq 'ResourceGroups') { + foreach ($rg in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).Keys | Sort-Object) { + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)")) { + $null = new-item -Name "$($subFolderName)$($DirectorySeparatorChar)$($rg)" -ItemType directory -path "$($outputPath)" + } + foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).RoleAssignments.keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).RoleAssignments.($ra) + if ($hlp.PIM -eq 'true') { + $pim = 'PIM_' + } + else { + $pim = '' + } + $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)ra_$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)RoleAssignments$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))$($DirectorySeparatorChar)$($rg)" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + #res + if (-not $JsonExportExcludeResources) { + + foreach ($res in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).Resources.keys) { + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)$($res)")) { + $null = new-item -Name "$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)$($res)" -ItemType directory -path "$($outputPath)" + } + foreach ($ra in $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).Resources.($res).RoleAssignments.keys) { + $hlp = $htJSON.ManagementGroups.($getMg.Name).($mgCap).($sub).($subCap).($rg).Resources.($res).RoleAssignments.($ra) + if ($hlp.PIM -eq 'true') { + $pim = 'PIM_' + } + else { + $pim = '' + } + $jsonConverted = ($hlp | Select-Object -ExcludeProperty PIM) | ConvertTo-Json -Depth 99 + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($subFolderName)$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)$($res)$($DirectorySeparatorChar)ra_$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + $path = "$($JSONPath)$($DirectorySeparatorChar)Assignments$($DirectorySeparatorChar)RoleAssignments$($DirectorySeparatorChar)Sub$($DirectorySeparatorChar)$($subNameValid) ($($sub))$($DirectorySeparatorChar)$($rg)$($DirectorySeparatorChar)$($res)" + if (-not (Test-Path -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)")) { + $null = new-item -Name $path -ItemType directory -path $outputPath + } + $jsonConverted | Set-Content -LiteralPath "$($outputPath)$($DirectorySeparatorChar)$($path)$($DirectorySeparatorChar)$($hlp.ObjectType)_$($pim)$($hlp.RoleAssignmentId -replace '.*/').json" -Encoding utf8 + } + } + } + } + } + } + } + } + } + } + } + + if ($childrenManagementGroups.Count -eq 0) { + $json.'ManagementGroups' = @{} + } + else { + foreach ($childMg in $childrenManagementGroups) { + buildTree -mgId $childMg.Name -json $json -prnt $prntx + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/cacheBuiltIn.ps1 b/pwsh/dev/functions/cacheBuiltIn.ps1 new file mode 100644 index 00000000..522503b2 --- /dev/null +++ b/pwsh/dev/functions/cacheBuiltIn.ps1 @@ -0,0 +1,211 @@ +function cacheBuiltIn { + $startDefinitionsCaching = Get-Date + Write-Host 'Caching built-in Policy and RBAC Role definitions' + + $arrayBuiltInCaching = @('PolicyDefinitions', 'PolicySetDefinitions', 'RoleDefinitions') + + $arrayBuiltInCaching | ForEach-Object -parallel { + + $builtInCapability = $_ + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $htCacheDefinitionsPolicy = $using:htCacheDefinitionsPolicy + $htCacheDefinitionsPolicySet = $using:htCacheDefinitionsPolicySet + $htCacheDefinitionsRole = $using:htCacheDefinitionsRole + $htRoleDefinitionIdsUsedInPolicy = $using:htRoleDefinitionIdsUsedInPolicy + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + + if ($builtInCapability -eq 'PolicyDefinitions') { + $currentTask = 'Caching built-in Policy definitions' + #Write-Host " $currentTask" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'BuiltIn'" + $method = 'GET' + $requestPolicyDefinitionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask + + Write-Host " $($requestPolicyDefinitionAPI.Count) built-in Policy definitions returned" + $builtinPolicyDefinitions = $requestPolicyDefinitionAPI.where( { $_.properties.policyType -eq 'BuiltIn' } ) + + foreach ($builtinPolicyDefinition in $builtinPolicyDefinitions) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()) = @{} + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Id = ($builtinPolicyDefinition.Id).ToLower() + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeMGLevel = '' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Scope = 'n/a' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeMgSub = 'n/a' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).ScopeId = 'n/a' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).DisplayName = $builtinPolicyDefinition.Properties.displayname + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Description = $builtinPolicyDefinition.Properties.description + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Type = $builtinPolicyDefinition.Properties.policyType + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Category = $builtinPolicyDefinition.Properties.metadata.category + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).PolicyDefinitionId = ($builtinPolicyDefinition.Id).ToLower() + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).LinkToAzAdvertizer = "$($builtinPolicyDefinition.Properties.displayname)" + if ($builtinPolicyDefinition.Properties.metadata.deprecated -eq $true -or $builtinPolicyDefinition.Properties.displayname -like "``[Deprecated``]*") { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Deprecated = $builtinPolicyDefinition.Properties.metadata.deprecated + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Deprecated = $false + } + if ($builtinPolicyDefinition.Properties.metadata.preview -eq $true -or $builtinPolicyDefinition.Properties.displayname -like "``[*Preview``]*") { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Preview = $builtinPolicyDefinition.Properties.metadata.preview + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Preview = $false + } + #effects + if ($builtinPolicyDefinition.properties.parameters.effect.defaultvalue) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = $builtinPolicyDefinition.properties.parameters.effect.defaultvalue + if ($builtinPolicyDefinition.properties.parameters.effect.allowedValues) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = $builtinPolicyDefinition.properties.parameters.effect.allowedValues -join ',' + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = 'n/a' + } + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = 'n/a' + } + else { + if ($builtinPolicyDefinition.properties.parameters.policyEffect.defaultValue) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = $builtinPolicyDefinition.properties.parameters.policyEffect.defaultvalue + if ($builtinPolicyDefinition.properties.parameters.policyEffect.allowedValues) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = $builtinPolicyDefinition.properties.parameters.policyEffect.allowedValues -join ',' + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = 'n/a' + } + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = 'n/a' + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectFixedValue = $builtinPolicyDefinition.Properties.policyRule.then.effect + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectDefaultValue = 'n/a' + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).effectAllowedValue = 'n/a' + } + } + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).Json = $builtinPolicyDefinition + + if (-not [string]::IsNullOrEmpty($builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds)) { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).RoleDefinitionIds = $builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds + foreach ($roledefinitionId in $builtinPolicyDefinition.properties.policyRule.then.details.roleDefinitionIds) { + if (-not $htRoleDefinitionIdsUsedInPolicy.($roledefinitionId)) { + $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId) = @{} + $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = [array]$builtinPolicyDefinition.Id + } + else { + $usedInPolicies = $htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies + $usedInPolicies += $builtinPolicyDefinition.Id + $script:htRoleDefinitionIdsUsedInPolicy.($roledefinitionId).UsedInPolicies = $usedInPolicies + } + } + } + else { + ($script:htCacheDefinitionsPolicy).(($builtinPolicyDefinition.Id).ToLower()).RoleDefinitionIds = 'n/a' + } + } + } + + if ($builtInCapability -eq 'PolicySetDefinitions') { + + $currentTask = 'Caching built-in PolicySet definitions' + #Write-Host " $currentTask" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'BuiltIn'" + $method = 'GET' + $requestPolicySetDefinitionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask + + $builtinPolicySetDefinitions = $requestPolicySetDefinitionAPI.where( { $_.properties.policyType -eq 'BuiltIn' } ) + Write-Host " $($requestPolicySetDefinitionAPI.Count) built-in PolicySet definitions returned" + foreach ($builtinPolicySetDefinition in $builtinPolicySetDefinitions) { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()) = @{} + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Id = ($builtinPolicySetDefinition.Id).ToLower() + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeMGLevel = '' + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Scope = 'n/a' + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeMgSub = 'n/a' + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).ScopeId = 'n/a' + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).DisplayName = $builtinPolicySetDefinition.Properties.displayname + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Description = $builtinPolicySetDefinition.Properties.description + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Type = $builtinPolicySetDefinition.Properties.policyType + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Category = $builtinPolicySetDefinition.Properties.metadata.category + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).PolicyDefinitionId = ($builtinPolicySetDefinition.Id).ToLower() + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).LinkToAzAdvertizer = "$($builtinPolicySetDefinition.Properties.displayname)" + $arrayPolicySetPolicyIdsToLower = @() + $arrayPolicySetPolicyIdsToLower = foreach ($policySetPolicy in $builtinPolicySetDefinition.properties.policydefinitions.policyDefinitionId) { + ($policySetPolicy).ToLower() + } + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).PolicySetPolicyIds = $arrayPolicySetPolicyIdsToLower + if ($builtinPolicySetDefinition.Properties.metadata.deprecated -eq $true -or $builtinPolicySetDefinition.Properties.displayname -like "``[Deprecated``]*") { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Deprecated = $builtinPolicySetDefinition.Properties.metadata.deprecated + } + else { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Deprecated = $false + } + if ($builtinPolicySetDefinition.Properties.metadata.preview -eq $true -or $builtinPolicySetDefinition.Properties.displayname -like "``[*Preview``]*") { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Preview = $builtinPolicySetDefinition.Properties.metadata.preview + } + else { + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Preview = $false + } + ($script:htCacheDefinitionsPolicySet).(($builtinPolicySetDefinition.Id).ToLower()).Json = $builtinPolicySetDefinition + } + } + + if ($builtInCapability -eq 'RoleDefinitions') { + $currentTask = 'Caching built-in Role definitions' + #Write-Host " $currentTask" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($azAPICallConf['checkContext'].Subscription.Id)/providers/Microsoft.Authorization/roleDefinitions?api-version=2018-07-01&`$filter=type eq 'BuiltInRole'" + $method = 'GET' + $requestRoleDefinitionAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask + + Write-Host " $($requestRoleDefinitionAPI.Count) built-in Role definitions returned" + foreach ($roleDefinition in $requestRoleDefinitionAPI) { + if ( + ( + $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/roleassignments/write' -or + $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/roleassignments/*' -or + $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/*/write' -or + $roleDefinition.properties.permissions.actions -contains 'Microsoft.Authorization/*' -or + $roleDefinition.properties.permissions.actions -contains '*/write' -or + $roleDefinition.properties.permissions.actions -contains '*' + ) -and ( + $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/roleassignments/write' -and + $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/roleassignments/*' -and + $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/*/write' -and + $roleDefinition.properties.permissions.notActions -notcontains 'Microsoft.Authorization/*' -and + $roleDefinition.properties.permissions.notActions -notcontains '*/write' -and + $roleDefinition.properties.permissions.notActions -notcontains '*' + ) + ) { + $roleCapable4RoleAssignmentsWrite = $true + } + else { + $roleCapable4RoleAssignmentsWrite = $false + } + + ($script:htCacheDefinitionsRole).($roleDefinition.name) = @{} + ($script:htCacheDefinitionsRole).($roleDefinition.name).Id = ($roleDefinition.name) + ($script:htCacheDefinitionsRole).($roleDefinition.name).Name = ($roleDefinition.properties.roleName) + ($script:htCacheDefinitionsRole).($roleDefinition.name).IsCustom = $false + ($script:htCacheDefinitionsRole).($roleDefinition.name).AssignableScopes = ($roleDefinition.properties.assignableScopes) + ($script:htCacheDefinitionsRole).($roleDefinition.name).Actions = ($roleDefinition.properties.permissions.actions) + ($script:htCacheDefinitionsRole).($roleDefinition.name).NotActions = ($roleDefinition.properties.permissions.notActions) + ($script:htCacheDefinitionsRole).($roleDefinition.name).DataActions = ($roleDefinition.properties.permissions.dataActions) + ($script:htCacheDefinitionsRole).($roleDefinition.name).NotDataActions = ($roleDefinition.properties.permissions.notDataActions) + ($script:htCacheDefinitionsRole).($roleDefinition.name).Json = ($roleDefinition.properties) + ($script:htCacheDefinitionsRole).($roleDefinition.name).LinkToAzAdvertizer = "$($roleDefinition.properties.roleName)" + ($script:htCacheDefinitionsRole).($roleDefinition.name).RoleCanDoRoleAssignments = $roleCapable4RoleAssignmentsWrite + } + } + } + + $script:builtInPolicyDefinitionsCount = $script:htCacheDefinitionsPolicy.Keys.Count + + $endDefinitionsCaching = Get-Date + Write-Host "Caching built-in definitions duration: $((NEW-TIMESPAN -Start $startDefinitionsCaching -End $endDefinitionsCaching).TotalSeconds) seconds" +} \ No newline at end of file diff --git a/pwsh/dev/functions/createTagList.ps1 b/pwsh/dev/functions/createTagList.ps1 new file mode 100644 index 00000000..6d3092b3 --- /dev/null +++ b/pwsh/dev/functions/createTagList.ps1 @@ -0,0 +1,25 @@ +function createTagList { + $startTagListArray = Get-Date + Write-Host 'Creating TagList array' + + $tagsSubRgResCount = ($htAllTagList.'AllScopes'.Keys).Count + $tagsSubsriptionCount = ($htAllTagList.'Subscription'.Keys).Count + $tagsResourceGroupCount = ($htAllTagList.'ResourceGroup'.Keys).Count + $tagsResourceCount = ($htAllTagList.'Resource'.Keys).Count + Write-Host " Total Number of ALL unique Tag Names: $tagsSubRgResCount" + Write-Host " Total Number of Subscription unique Tag Names: $tagsSubsriptionCount" + Write-Host " Total Number of ResourceGroup unique Tag Names: $tagsResourceGroupCount" + Write-Host " Total Number of Resource unique Tag Names: $tagsResourceCount" + + foreach ($tagScope in $htAllTagList.keys) { + foreach ($tagScopeTagName in $htAllTagList.($tagScope).keys) { + $null = $script:arrayTagList.Add([PSCustomObject]@{ + Scope = $tagScope + TagName = ($tagScopeTagName) + TagCount = $htAllTagList.($tagScope).($tagScopeTagName) + }) + } + } + $endTagListArray = Get-Date + Write-Host "Creating TagList array duration: $((NEW-TIMESPAN -Start $startTagListArray -End $endTagListArray).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startTagListArray -End $endTagListArray).TotalSeconds) seconds)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/dataCollection/dataCollectionFunctions.ps1 b/pwsh/dev/functions/dataCollection/dataCollectionFunctions.ps1 new file mode 100644 index 00000000..0df1edf5 --- /dev/null +++ b/pwsh/dev/functions/dataCollection/dataCollectionFunctions.ps1 @@ -0,0 +1,2864 @@ +#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 + ) + + $currentTask = "Getting Microsoft Defender for Cloud plans for Subscription: '$($scopeDisplayName)' ('$scopeId')" + #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') { + #Subscription skipped for MDfC + $null = $script:arrayDefenderPlansSubscriptionNotRegistered.Add([PSCustomObject]@{ + subscriptionId = $scopeId + subscriptionName = $scopeDisplayName + subscriptionMgPath = $childMgMgPath + }) + } + 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 dataCollectionDiagnosticsSub { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $ChildMgMgPath, + $ChildMgId + ) + + $currentTask = "getDiagnosticSettingsSub for Subscription: '$($scopeDisplayName)' ('$scopeId')" + $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 = "getARMDiagnosticSettingsMg '$($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 dataCollectionResources { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName, + $ChildMgMgPath + ) + $currentTask = "Getting ResourceTypes for Subscription: '$($scopeDisplayName)' ('$scopeId')" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/resources?`$expand=createdTime,changedTime&api-version=2021-04-01" + $method = 'GET' + $resourcesSubscriptionResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' + + 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 + foreach ($resource in ($resourcesSubscriptionResult)) { + $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) + }) + + 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] + $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 '.*/' + }) + } + } + } + } + $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 + ) + + #https://management.azure.com/subscriptions/{subscriptionId}/resourcegroups?api-version=2020-06-01 + $currentTask = "Getting ResourceGroups for Subscription: '$($scopeDisplayName)' ('$scopeId')" + $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 + ) + + ($script:htResourceProvidersAll).($scopeId) = @{} + $currentTask = "Getting ResourceProviders for Subscription: '$($scopeDisplayname)' ('$scopeId')" + $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 dataCollectionResourceLocks { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayname + ) + + $currentTask = "Subscription ResourceLocks '$($scopeDisplayname)' ('$scopeId')" + $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 + $resourceGroupsLocksCannotDeleteCount = ($arrayResourceGroupsCannotDeleteLock).Count + $htTemp.ResourceGroupsLocksCannotDeleteCount = $resourceGroupsLocksCannotDeleteCount + + $resourceGroupsLocksReadOnlyCount = ($arrayResourceGroupsReadOnlyLock).Count + $htTemp.ResourceGroupsLocksReadOnlyCount = $resourceGroupsLocksReadOnlyCount + $htTemp.ResourceGroupsLocksCannotDelete = $arrayResourceGroupsCannotDeleteLock + + #resources + $resourcesLocksCannotDeleteCount = ($arrayResourcesCannotDeleteLock).Count + $htTemp.ResourcesLocksCannotDeleteCount = $resourcesLocksCannotDeleteCount + + $resourcesLocksReadOnlyCount = ($arrayResourcesReadOnlyLock).Count + $htTemp.ResourcesLocksReadOnlyCount = $resourcesLocksReadOnlyCount + $htTemp.ResourcesLocksCannotDelete = $arrayResourcesCannotDeleteLock + + $script:htResourceLocks.($scopeId) = $htTemp + } +} +$funcDataCollectionResourceLocks = $function:dataCollectionResourceLocks.ToString() + +function dataCollectionTags { + [CmdletBinding()]Param( + [string]$scopeId, + [string]$scopeDisplayName + ) + + $currentTask = "Subscription Tags '$($scopeDisplayName)' ('$scopeId')" + $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 + ) + + $currentTask = "Policy Compliance $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.PolicyInsights/policyStates/latest/summarize?api-version=2019-10-01" } + if ($TargetMgOrSub -eq 'MG') { $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) = @{} + } + } + 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 + ) + + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + $currentTask = "Microsoft Defender for Cloud Secure Score Sub: '$($scopeDisplayName)' ('$scopeId')" + $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).count -gt 0) { + $subscriptionASCSecureScore = "$($subASCSecureScoreResult.properties.score.current) of $($subASCSecureScoreResult.properties.score.max) points" + } + 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 = "Blueprint definitions MG '$($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 = "Blueprint definitions Sub '$($scopeDisplayName)' ('$scopeId')" + $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).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 = "Blueprint assignments '$($scopeDisplayName)' ('$scopeId')" + $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).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 = " Blueprint definitions related to Blueprint assignments '$($scopeDisplayName)' ('$scopeId')" + $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 + ) + + $currentTask = "Policy exemptions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyExemptions?api-version=2020-07-01-preview" + } + if ($TargetMgOrSub -eq 'MG') { + $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 + ) + + $currentTask = "Policy definitions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'" + } + if ($TargetMgOrSub -eq 'MG') { + $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 (($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 + } + $htTemp.DisplayName = $($scopePolicyDefinition.Properties.displayname) + $htTemp.Description = $($policyDefinitionDescription) + $htTemp.Type = $($scopePolicyDefinition.Properties.policyType) + $htTemp.Category = $($scopePolicyDefinition.Properties.metadata.category) + $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 + } + #effects + if ($scopePolicyDefinition.properties.parameters.effect.defaultvalue) { + $htTemp.effectDefaultValue = $scopePolicyDefinition.properties.parameters.effect.defaultvalue + if ($scopePolicyDefinition.properties.parameters.effect.allowedValues) { + $htTemp.effectAllowedValue = $scopePolicyDefinition.properties.parameters.effect.allowedValues -join ',' + } + else { + $htTemp.effectAllowedValue = 'n/a' + } + $htTemp.effectFixedValue = 'n/a' + } + else { + if ($scopePolicyDefinition.properties.parameters.policyEffect.defaultValue) { + $htTemp.effectDefaultValue = $scopePolicyDefinition.properties.parameters.policyEffect.defaultvalue + if ($scopePolicyDefinition.properties.parameters.policyEffect.allowedValues) { + $htTemp.effectAllowedValue = $scopePolicyDefinition.properties.parameters.policyEffect.allowedValues -join ',' + } + else { + $htTemp.effectAllowedValue = 'n/a' + } + $htTemp.effectFixedValue = 'n/a' + } + else { + $htTemp.effectFixedValue = $scopePolicyDefinition.Properties.policyRule.then.effect + $htTemp.effectDefaultValue = 'n/a' + $htTemp.effectAllowedValue = 'n/a' + } + } + $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 + ) + + $currentTask = "PolicySet definitions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/policySetDefinitions?api-version=2021-06-01&`$filter=policyType eq 'Custom'" + } + if ($TargetMgOrSub -eq 'MG') { + $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 (($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 + } + $htTemp.DisplayName = $($scopePolicySetDefinition.Properties.displayname) + $htTemp.Description = $($policySetDefinitionDescription) + $htTemp.Type = $($scopePolicySetDefinition.Properties.policyType) + $htTemp.Category = $($scopePolicySetDefinition.Properties.metadata.category) + $htTemp.PolicyDefinitionId = $hlpPolicySetDefinitionId + $arrayPolicySetPolicyIdsToLower = @() + $arrayPolicySetPolicyIdsToLower = foreach ($policySetPolicy in $scopePolicySetDefinition.properties.policydefinitions.policyDefinitionId) { + ($policySetPolicy).ToLower() + } + $htTemp.PolicySetPolicyIds = $arrayPolicySetPolicyIdsToLower + $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 = "Policy assignments '$($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 + $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 - AzGovViz: 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' + $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 ` + -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 + $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' + $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 ` + -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 = "Policy assignments '$($scopeDisplayName)' ('$scopeId')" + $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 + $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' + $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 ` + -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 + $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' + $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 ` + -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 + ) + + $currentTask = "Custom Role definitions $($TargetMgOrSub) '$($scopeDisplayName)' ('$scopeId')" + if ($TargetMgOrSub -eq 'Sub') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-07-01&`$filter=type eq 'CustomRole'" + } + if ($TargetMgOrSub -eq 'MG') { + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-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 MGRoleAssignmentSchedules + $currentTask = "getARMRoleAssignmentSchedules '$($scopeDisplayName)' ('$($scopeId)')" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentSchedules?api-version=2020-10-01-preview" + $method = 'GET' + $roleAssignmentSchedulesFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' + + if ($roleAssignmentSchedulesFromAPI -eq 'ResourceNotOnboarded' -or $roleAssignmentSchedulesFromAPI -eq 'TenantNotOnboarded' -or $roleAssignmentSchedulesFromAPI -eq 'InvalidResourceType') { + #Write-Host "Scope '$($scopeDisplayName)' ('$scopeId') not onboarded in PIM" + } + else { + $roleAssignmentSchedules = ($roleAssignmentSchedulesFromAPI.where( { -not [string]::IsNullOrEmpty($_.properties.roleAssignmentScheduleRequestId) })) + $roleAssignmentSchedulesCount = $roleAssignmentSchedules.Count + if ($roleAssignmentSchedulesCount -gt 0) { + $htRoleAssignmentsPIM = @{} + foreach ($roleAssignmentSchedule in $roleAssignmentSchedules) { + $keyName = "$(($roleAssignmentSchedule.properties.scope).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.principal.id).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.roleDefinition.id).ToLower())" + $htRoleAssignmentsPIM.($keyName) = $roleAssignmentSchedule.properties + } + } + } + + #RoleAssignment API MG + $currentTask = "Role assignments API '$($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() + + $keyName = "$(($L0mgmtGroupRoleAssignment.properties.scope).ToLower())-$(($L0mgmtGroupRoleAssignment.properties.principalId).ToLower())-$(($L0mgmtGroupRoleAssignment.properties.roleDefinitionId).ToLower())" + if ($htRoleAssignmentsPIM.($keyName)) { + $hlperPim = $htRoleAssignmentsPIM.($keyName) + $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 = "Role assignments usage metrics '$($scopeDisplayName)' ('$scopeId')" + $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 SubscriptionRoleAssignmentSchedules + $currentTask = "Role assignment schedules API '$($scopeDisplayName)' ('$scopeId')" + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($scopeId)/providers/Microsoft.Authorization/roleAssignmentSchedules?api-version=2020-10-01-preview" + $method = 'GET' + $roleAssignmentSchedulesFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -caller 'CustomDataCollection' + + if ($roleAssignmentSchedulesFromAPI -eq 'ResourceNotOnboarded' -or $roleAssignmentSchedulesFromAPI -eq 'TenantNotOnboarded' -or $roleAssignmentSchedulesFromAPI -eq 'InvalidResourceType') { + #Write-Host "Scope '$($scopeDisplayName)' ('$scopeId') not onboarded in PIM" + } + else { + $roleAssignmentSchedules = ($roleAssignmentSchedulesFromAPI.where( { -not [string]::IsNullOrEmpty($_.properties.roleAssignmentScheduleRequestId) })) + $roleAssignmentSchedulesCount = $roleAssignmentSchedules.Count + if ($roleAssignmentSchedulesCount -gt 0) { + $htRoleAssignmentsPIM = @{} + foreach ($roleAssignmentSchedule in $roleAssignmentSchedules) { + $keyName = "$(($roleAssignmentSchedule.properties.scope).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.principal.id).ToLower())-$(($roleAssignmentSchedule.properties.expandedProperties.roleDefinition.id).ToLower())" + $htRoleAssignmentsPIM.($keyName) = $roleAssignmentSchedule.properties + } + } + } + + #RoleAssignment API Sub + $currentTask = "Role assignments API '$($scopeDisplayName)' ('$scopeId')" + $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] + } + + $keyName = "$(($L1mgmtGroupSubRoleAssignment.properties.scope).ToLower())-$(($L1mgmtGroupSubRoleAssignment.properties.principalId).ToLower())-$(($L1mgmtGroupSubRoleAssignment.properties.roleDefinitionId).ToLower())" + if ($htRoleAssignmentsPIM.($keyName)) { + $hlperPim = $htRoleAssignmentsPIM.($keyName) + $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() + +#endregion functions4DataCollection \ No newline at end of file diff --git a/pwsh/dev/functions/detailSubscriptions.ps1 b/pwsh/dev/functions/detailSubscriptions.ps1 new file mode 100644 index 00000000..43ee87fc --- /dev/null +++ b/pwsh/dev/functions/detailSubscriptions.ps1 @@ -0,0 +1,70 @@ +function detailSubscriptions { + #API in rare cases returns duplicates, therefor sorting unique (id) + $childrenSubscriptions = $arrayEntitiesFromAPI.where( { $_.properties.parentNameChain -contains $ManagementGroupID -and $_.type -eq '/subscriptions' } ) | Sort-Object -Property id -Unique + $script:childrenSubscriptionsCount = ($childrenSubscriptions).Count + $script:subsToProcessInCustomDataCollection = [System.Collections.ArrayList]@() + foreach ($childrenSubscription in $childrenSubscriptions) { + + $sub = $htAllSubscriptionsFromAPI.($childrenSubscription.name) + if ($sub.subDetails.subscriptionPolicies.quotaId.startswith('AAD_', 'CurrentCultureIgnoreCase') -or $sub.subDetails.state -ne 'Enabled') { + if (($sub.subDetails.subscriptionPolicies.quotaId).startswith('AAD_', 'CurrentCultureIgnoreCase')) { + $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + outOfScopeReason = "QuotaId: AAD_ (State: $($sub.subDetails.state))" + ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent + ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName + Level = $htSubscriptionsMgPath.($childrenSubscription.name).level + }) + } + if ($sub.subDetails.state -ne 'Enabled') { + $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + outOfScopeReason = "State: $($sub.subDetails.state)" + ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent + ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName + Level = $htSubscriptionsMgPath.($childrenSubscription.name).level + }) + } + } + else { + if ($SubscriptionQuotaIdWhitelist[0] -ne 'undefined') { + $whitelistMatched = 'unknown' + foreach ($subscriptionQuotaIdWhitelistQuotaId in $SubscriptionQuotaIdWhitelist) { + if (($sub.subDetails.subscriptionPolicies.quotaId).startswith($subscriptionQuotaIdWhitelistQuotaId, 'CurrentCultureIgnoreCase')) { + $whitelistMatched = 'inWhitelist' + } + } + + if ($whitelistMatched -eq 'inWhitelist') { + #write-host "$($childrenSubscription.properties.displayName) in whitelist" + $null = $script:subsToProcessInCustomDataCollection.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + subscriptionQuotaId = $sub.subDetails.subscriptionPolicies.quotaId + }) + } + else { + #Write-Host " preCustomDataCollection: $($childrenSubscription.properties.displayName) ($($childrenSubscription.name)) Subscription Quota Id: $($sub.subDetails.subscriptionPolicies.quotaId) is out of scope for AzGovViz (not in Whitelist)" + $null = $script:outOfScopeSubscriptions.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + outOfScopeReason = "QuotaId: '$($sub.subDetails.subscriptionPolicies.quotaId)' not in Whitelist" + ManagementGroupId = $htSubscriptionsMgPath.($childrenSubscription.name).Parent + ManagementGroupName = $htSubscriptionsMgPath.($childrenSubscription.name).ParentName + Level = $htSubscriptionsMgPath.($childrenSubscription.name).level + }) + } + } + else { + $null = $script:subsToProcessInCustomDataCollection.Add([PSCustomObject]@{ + subscriptionId = $childrenSubscription.name + subscriptionName = $childrenSubscription.properties.displayName + subscriptionQuotaId = $sub.subDetails.subscriptionPolicies.quotaId + }) + } + } + } + $script:subsToProcessInCustomDataCollectionCount = ($subsToProcessInCustomDataCollection).Count +} \ No newline at end of file diff --git a/pwsh/dev/functions/exportBaseCSV.ps1 b/pwsh/dev/functions/exportBaseCSV.ps1 new file mode 100644 index 00000000..3ba0531d --- /dev/null +++ b/pwsh/dev/functions/exportBaseCSV.ps1 @@ -0,0 +1,16 @@ +function exportBaseCSV { + Write-Host "Exporting CSV '$($outputPath)$($DirectorySeparatorChar)$($fileName).csv'" + $startBuildCSV = Get-Date + + $outprops = $newtable[0].PSObject.Properties.Name + $outprops.Set($outprops.IndexOf('PolicyAssignmentNotScopes'), @{L = 'PolicyAssignmentNotScopes'; E = { ($_.PolicyAssignmentNotScopes -join "$CsvDelimiterOpposite ") } }) + if ($CsvExportUseQuotesAsNeeded) { + $newTable | Sort-Object -Property level, mgId, SubscriptionId, PolicyAssignmentId, RoleAssignmentId, BlueprintId, BlueprintAssignmentId | Select-Object -Property $outprops -ExcludeProperty PolicyAssignmentParameters | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded + } + else { + $newTable | Sort-Object -Property level, mgId, SubscriptionId, PolicyAssignmentId, RoleAssignmentId, BlueprintId, BlueprintAssignmentId | Select-Object -Property $outprops -ExcludeProperty PolicyAssignmentParameters | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).csv" -Delimiter "$csvDelimiter" -NoTypeInformation + } + + $endBuildCSV = Get-Date + Write-Host "Exporting CSV total duration: $((NEW-TIMESPAN -Start $startBuildCSV -End $endBuildCSV).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildCSV -End $endBuildCSV).TotalSeconds) seconds)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/getConsumption.ps1 b/pwsh/dev/functions/getConsumption.ps1 new file mode 100644 index 00000000..12906267 --- /dev/null +++ b/pwsh/dev/functions/getConsumption.ps1 @@ -0,0 +1,616 @@ +function getConsumption { + + function addToAllConsumptionData { + [CmdletBinding()]Param( + [Parameter(Mandatory)] + [object] + $consumptiondataFromAPI + ) + + foreach ($consumptionline in $consumptiondataFromAPI.properties.rows) { + $hlper = $htSubscriptionsMgPath.($consumptionline[1]) + $null = $script:allConsumptionData.Add([PSCustomObject]@{ + "$($consumptiondataFromAPI.properties.columns.name[0])" = $consumptionline[0] + "$($consumptiondataFromAPI.properties.columns.name[1])" = $consumptionline[1] + SubscriptionName = $hlper.DisplayName + SubscriptionMgPath = $hlper.ParentNameChainDelimited + "$($consumptiondataFromAPI.properties.columns.name[2])" = $consumptionline[2] + "$($consumptiondataFromAPI.properties.columns.name[3])" = $consumptionline[3] + "$($consumptiondataFromAPI.properties.columns.name[4])" = $consumptionline[4] + "$($consumptiondataFromAPI.properties.columns.name[5])" = $consumptionline[5] + "$($consumptiondataFromAPI.properties.columns.name[6])" = $consumptionline[6] + }) + } + } + + $startConsumptionData = Get-Date + + #cost only for whitelisted quotaId + if ($SubscriptionQuotaIdWhitelist[0] -ne 'undefined') { + if ($subsToProcessInCustomDataCollectionCount -gt 0) { + #region mgScopeWhitelisted + #$subscriptionIdsOptimizedForBody = '"{0}"' -f ($subsToProcessInCustomDataCollection.subscriptionId -join '","') + $currenttask = "Getting Consumption data (scope MG '$($ManagementGroupId)') for $($subsToProcessInCustomDataCollectionCount) Subscriptions (QuotaId Whitelist: '$($SubscriptionQuotaIdWhitelist -join ', ')') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" + Write-Host "$currentTask" + #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" + $method = 'POST' + + $counterBatch = [PSCustomObject] @{ Value = 0 } + $batchSize = 100 + $subscriptionsBatch = ($subsToProcessInCustomDataCollection | Sort-Object -Property subscriptionQuotaId) | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) } + $batchCnt = 0 + + foreach ($batch in $subscriptionsBatch) { + $batchCnt++ + $subscriptionIdsOptimizedForBody = '"{0}"' -f (($batch.Group).subscriptionId -join '","') + $currenttask = "Getting Consumption data #batch$($batchCnt)/$(($subscriptionsBatch | Measure-Object).Count) (scope MG '$($ManagementGroupId)') for $(($batch.Group).Count) Subscriptions (QuotaId Whitelist: '$($SubscriptionQuotaIdWhitelist -join ', ')') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" + Write-Host "$currentTask" -ForegroundColor Cyan + + $body = @" +{ +"type": "ActualCost", +"dataset": { + "granularity": "none", + "filter": { + "dimensions": { + "name": "SubscriptionId", + "operator": "In", + "values": [ + $($subscriptionIdsOptimizedForBody) + ] + } + }, + "aggregation": { + "totalCost": { + "name": "PreTaxCost", + "function": "Sum" + } + }, + "grouping": [ + { + "type": "Dimension", + "name": "SubscriptionId" + }, + { + "type": "Dimension", + "name": "ResourceId" + }, + { + "type": "Dimension", + "name": "ConsumedService" + }, + { + "type": "Dimension", + "name": "MeterCategory" + }, + { + "type": "Dimension", + "name": "ChargeType" + } + ] +}, +"timeframe": "Custom", +"timeperiod": { + "from": "$($azureConsumptionStartDate)", + "to": "$($azureConsumptionEndDate)" +} +} +"@ + + $mgConsumptionData = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + #endregion mgScopeWhitelisted + + <#test + #$mgConsumptionData = "OfferNotSupported" + if ($batchCnt -eq 1){ + $mgConsumptionData = "OfferNotSupported" + } + #> + + if ($mgConsumptionData -eq 'Unauthorized' -or $mgConsumptionData -eq 'OfferNotSupported') { + if (-not $script:htConsumptionExceptionLog.Mg.($ManagementGroupId)) { + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId) = @{} + } + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt) = @{} + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt).Exception = $mgConsumptionData + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).($batchCnt).Subscriptions = ($batch.Group).subscriptionId + Write-Host " Switching to 'foreach Subscription' Subscription scope mode. Getting Consumption data #batch$($batchCnt) using Management Group scope failed." + #region subScopewhitelisted + $body = @" +{ +"type": "ActualCost", +"dataset": { + "granularity": "none", + "aggregation": { + "totalCost": { + "name": "PreTaxCost", + "function": "Sum" + } + }, + "grouping": [ + { + "type": "Dimension", + "name": "SubscriptionId" + }, + { + "type": "Dimension", + "name": "ResourceId" + }, + { + "type": "Dimension", + "name": "ConsumedService" + }, + { + "type": "Dimension", + "name": "MeterCategory" + }, + { + "type": "Dimension", + "name": "ChargeType" + } + ] +}, +"timeframe": "Custom", +"timeperiod": { + "from": "$($azureConsumptionStartDate)", + "to": "$($azureConsumptionEndDate)" +} +} +"@ + $funcAddToAllConsumptionData = $function:addToAllConsumptionData.ToString() + $batch.Group | ForEach-Object -Parallel { + $subIdToProcess = $_.subscriptionId + $subNameToProcess = $_.subscriptionName + $subscriptionQuotaIdToProcess = $_.subscriptionQuotaId + #region UsingVARs + $body = $using:body + $azureConsumptionStartDate = $using:azureConsumptionStartDate + $azureConsumptionEndDate = $using:azureConsumptionEndDate + $SubscriptionQuotaIdWhitelist = $using:SubscriptionQuotaIdWhitelist + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $allConsumptionData = $using:allConsumptionData + $htSubscriptionsMgPath = $using:htSubscriptionsMgPath + $htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI + $htConsumptionExceptionLog = $using:htConsumptionExceptionLog + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:addToAllConsumptionData = $using:funcAddToAllConsumptionData + #endregion UsingVARs + + $currentTask = " Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)) (whitelist))" + #test + write-host $currentTask + #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($subIdToProcess)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" + $method = 'POST' + $subConsumptionData = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + if ($subConsumptionData -eq 'Unauthorized' -or $subConsumptionData -eq 'OfferNotSupported' -or $subConsumptionData -eq 'InvalidQueryDefinition' -or $subConsumptionData -eq 'NonValidWebDirectAIRSOfferType' -or $subConsumptionData -eq 'NotFoundNotSupported' -or $subConsumptionData -eq 'IndirectCostDisabled') { + Write-Host " Failed ($subConsumptionData) - Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)) (whitelist))" + $hlper = $htAllSubscriptionsFromAPI.($subIdToProcess).subDetails + $hlper2 = $htSubscriptionsMgPath.($subIdToProcess) + $script:htConsumptionExceptionLog.Sub.($subIdToProcess) = @{} + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).Exception = $subConsumptionData + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionId = $subIdToProcess + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionName = $hlper.displayName + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).QuotaId = $hlper.subscriptionPolicies.quotaId + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgPath = $hlper2.ParentNameChainDelimited + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgParent = $hlper2.Parent + Continue + } + else { + Write-Host " $($subConsumptionData.Count) Consumption data entries ((scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess))))" + if ($subConsumptionData.Count -gt 0) { + addToAllConsumptionData -consumptiondataFromAPI $subConsumptionData + <# + foreach ($consumptionEntry in $subConsumptionData) { + if ($consumptionEntry.PreTaxCost -ne 0) { + $null = $script:allConsumptionData.Add($consumptionEntry) + } + } + #> + + } + } + } -ThrottleLimit $ThrottleLimit + #endregion subScopewhitelisted + } + else { + Write-Host " $($mgConsumptionData.Count) Consumption data entries" + if ($mgConsumptionData.Count -gt 0) { + addToAllConsumptionData -consumptiondataFromAPI $mgConsumptionData + <# + foreach ($consumptionEntry in $mgConsumptionData) { + if ($consumptionEntry.PreTaxCost -ne 0) { + $null = $script:allConsumptionData.Add($consumptionEntry) + } + } + #> + } + } + } + } + else { + $detailShowStopperResult = 'NoWhitelistSubscriptionsPresent' + Write-Host ' No Subscriptions matching whitelist present, skipping Consumption data processing' + #überprüfen + } + } + else { + + if ($subsToProcessInCustomDataCollectionCount -gt 0) { + #region mgScope + $currenttask = "Getting Consumption data (scope MG '$($ManagementGroupId)') for period $AzureConsumptionPeriod days ($azureConsumptionStartDate - $azureConsumptionEndDate)" + Write-Host "$currentTask" + #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($ManagementGroupId)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" + $method = 'POST' + $body = @" +{ + "type": "ActualCost", + "dataset": { + "granularity": "none", + "aggregation": { + "totalCost": { + "name": "PreTaxCost", + "function": "Sum" + } + }, + "grouping": [ + { + "type": "Dimension", + "name": "SubscriptionId" + }, + { + "type": "Dimension", + "name": "ResourceId" + }, + { + "type": "Dimension", + "name": "ConsumedService" + }, + { + "type": "Dimension", + "name": "MeterCategory" + }, + { + "type": "Dimension", + "name": "ChargeType" + } + ] + }, + "timeframe": "Custom", + "timeperiod": { + "from": "$($azureConsumptionStartDate)", + "to": "$($azureConsumptionEndDate)" + } +} +"@ + #$script:allConsumptionData = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + $allConsumptionDataAPIResult = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + #endregion mgScope + + #test + #$allConsumptionData = "OfferNotSupported" + + if ($allConsumptionDataAPIResult -eq 'AccountCostDisabled' -or $allConsumptionDataAPIResult -eq 'NoValidSubscriptions') { + $generalShowStopperResult = $true + if ($allConsumptionDataAPIResult -eq 'AccountCostDisabled') { + $detailShowStopperResult = $allConsumptionDataAPIResult + } + if ($allConsumptionDataAPIResult -eq 'NoValidSubscriptions') { + $detailShowStopperResult = $allConsumptionDataAPIResult + } + } + else { + if ($allConsumptionDataAPIResult -eq 'Unauthorized' -or $allConsumptionDataAPIResult -eq 'OfferNotSupported') { + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId) = @{} + $script:htConsumptionExceptionLog.Mg.($ManagementGroupId).Exception = $allConsumptionDataAPIResult + Write-Host " Switching to 'foreach Subscription' mode. Getting Consumption data using Management Group scope failed." + #region subScope + $body = @" +{ + "type": "ActualCost", + "dataset": { + "granularity": "none", + "aggregation": { + "totalCost": { + "name": "PreTaxCost", + "function": "Sum" + } + }, + "grouping": [ + { + "type": "Dimension", + "name": "SubscriptionId" + }, + { + "type": "Dimension", + "name": "ResourceId" + }, + { + "type": "Dimension", + "name": "ConsumedService" + }, + { + "type": "Dimension", + "name": "MeterCategory" + }, + { + "type": "Dimension", + "name": "ChargeType" + } + ] + }, + "timeframe": "Custom", + "timeperiod": { + "from": "$($azureConsumptionStartDate)", + "to": "$($azureConsumptionEndDate)" + } +} +"@ + + $funcAddToAllConsumptionData = $function:addToAllConsumptionData.ToString() + $subsToProcessInCustomDataCollection | ForEach-Object -Parallel { + $subIdToProcess = $_.subscriptionId + $subNameToProcess = $_.subscriptionName + $subscriptionQuotaIdToProcess = $_.subscriptionQuotaId + #region UsingVARs + $body = $using:body + $azureConsumptionStartDate = $using:azureConsumptionStartDate + $azureConsumptionEndDate = $using:azureConsumptionEndDate + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $htSubscriptionsMgPath = $using:htSubscriptionsMgPath + $htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI + $allConsumptionData = $using:allConsumptionData + $htConsumptionExceptionLog = $using:htConsumptionExceptionLog + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:addToAllConsumptionData = $using:funcAddToAllConsumptionData + #endregion UsingVARs + + $currentTask = " Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" + #test + write-host $currentTask + #https://docs.microsoft.com/en-us/rest/api/cost-management/query/usage + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/subscriptions/$($subIdToProcess)/providers/Microsoft.CostManagement/query?api-version=2019-11-01&`$top=5000" + $method = 'POST' + $subConsumptionData = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -body $body -currentTask $currentTask -listenOn 'ContentProperties' + if ($subConsumptionData -eq 'Unauthorized' -or $subConsumptionData -eq 'OfferNotSupported' -or $subConsumptionData -eq 'InvalidQueryDefinition' -or $subConsumptionData -eq 'NonValidWebDirectAIRSOfferType' -or $subConsumptionData -eq 'NotFoundNotSupported' -or $subConsumptionData -eq 'IndirectCostDisabled') { + Write-Host " Failed ($subConsumptionData) - Getting Consumption data (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" + $hlper = $htAllSubscriptionsFromAPI.($subIdToProcess).subDetails + $hlper2 = $htSubscriptionsMgPath.($subIdToProcess) + $script:htConsumptionExceptionLog.Sub.($subIdToProcess) = @{} + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).Exception = $subConsumptionData + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionId = $subIdToProcess + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).SubscriptionName = $hlper.displayName + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).QuotaId = $hlper.subscriptionPolicies.quotaId + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgPath = $hlper2.ParentNameChainDelimited + $script:htConsumptionExceptionLog.Sub.($subIdToProcess).mgParent = $hlper2.Parent + Continue + } + else { + Write-Host " $($subConsumptionData.Count) Consumption data entries (scope Sub $($subNameToProcess) '$($subIdToProcess)' ($($subscriptionQuotaIdToProcess)))" + if ($subConsumptionData.Count -gt 0) { + addToAllConsumptionData -consumptiondataFromAPI $subConsumptionData + <# + foreach ($consumptionEntry in $subConsumptionData) { + if ($consumptionEntry.PreTaxCost -ne 0) { + $null = $script:allConsumptionData.Add($consumptionEntry) + } + } + #> + } + } + } -ThrottleLimit $ThrottleLimit + #endregion subScope + } + else { + Write-Host " $($allConsumptionDataAPIResult.properties.rows.Count) Consumption data entries" + if ($allConsumptionDataAPIResult.properties.rows.Count -gt 0) { + addToAllConsumptionData -consumptiondataFromAPI $allConsumptionDataAPIResult + } + } + } + } + else { + $detailShowStopperResult = 'NoSubscriptionsPresent' + Write-Host ' No Subscriptions present, skipping Consumption data processing' + } + } + + if ($detailShowStopperResult -eq 'AccountCostDisabled' -or $detailShowStopperResult -eq 'NoValidSubscriptions' -or $detailShowStopperResult -eq 'NoWhitelistSubscriptionsPresent' -or $detailShowStopperResult -eq 'NoSubscriptionsPresent') { + if ($detailShowStopperResult -eq 'AccountCostDisabled') { + Write-Host ' Seems Access to cost data has been disabled for this Account - skipping CostManagement' + } + if ($detailShowStopperResult -eq 'NoValidSubscriptions') { + Write-Host ' Seems there are no valid Subscriptions present - skipping CostManagement' + } + if ($detailShowStopperResult -eq 'NoWhitelistSubscriptionsPresent') { + Write-Host " Seems there are no Subscriptions present that match the whitelist ($($SubscriptionQuotaIdWhitelist -join ', ')) - skipping CostManagement" + } + if ($detailShowStopperResult -eq 'NoSubscriptionsPresent') { + Write-Host ' Seems there are no Subscriptions present - skipping CostManagement' + } + Write-Host " Action: Setting switch parameter 'DoAzureConsumption' to false" + $azAPICallConf['htParameters'].DoAzureConsumption = $false + } + else { + Write-Host ' Checking returned Consumption data' + $script:allConsumptionDataCount = $allConsumptionData.Count + + if ($allConsumptionDataCount -gt 0) { + + $script:allConsumptionData = $allConsumptionData.where( { $_.PreTaxCost -ne 0 } ) + $script:allConsumptionDataCount = $allConsumptionData.Count + + if ($allConsumptionDataCount -gt 0) { + Write-Host " $($allConsumptionDataCount) relevant Consumption data entries" + + $script:consumptionData = $allConsumptionData + $script:consumptionDataGroupedByCurrency = $consumptionData | Group-Object -property Currency + + foreach ($currency in $consumptionDataGroupedByCurrency) { + + #subscriptions + $groupAllConsumptionDataPerCurrencyBySubscriptionId = $currency.group | Group-Object -Property SubscriptionId + foreach ($subscriptionId in $groupAllConsumptionDataPerCurrencyBySubscriptionId) { + + $subTotalCost = ($subscriptionId.Group.PreTaxCost | Measure-Object -Sum).Sum + $script:htAzureConsumptionSubscriptions.($subscriptionId.Name) = @{} + $script:htAzureConsumptionSubscriptions.($subscriptionId.Name).ConsumptionData = $subscriptionId.group + $script:htAzureConsumptionSubscriptions.($subscriptionId.Name).TotalCost = $subTotalCost + $script:htAzureConsumptionSubscriptions.($subscriptionId.Name).Currency = $currency.Name + $resourceTypes = $subscriptionId.Group.ConsumedService | Sort-Object -Unique + + foreach ($parentMg in $htSubscriptionsMgPath.($subscriptionId.Name).ParentNameChain) { + + if (-not $htManagementGroupsCost.($parentMg)) { + $script:htManagementGroupsCost.($parentMg) = @{} + $script:htManagementGroupsCost.($parentMg).currencies = $currency.Name + $script:htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" = $subTotalCost #[decimal]$subTotalCost + $script:htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" = ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count + $script:htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent = ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count + $script:htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" = 1 + $script:htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent = 1 + $script:htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" = $resourceTypes + $script:htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent = $resourceTypes + $script:htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" = $subscriptionId.group + $script:htManagementGroupsCost.($parentMg).consumptionDataSubscriptions = $subscriptionId.group + } + else { + $newMgTotalCost = $htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" + $subTotalCost #[decimal]$subTotalCost + $script:htManagementGroupsCost.($parentMg)."mgTotalCost_$($currency.Name)" = $newMgTotalCost #[decimal]$newMgTotalCost + + $currencies = [array]$htManagementGroupsCost.($parentMg).currencies + if ($currencies -notcontains $currency.Name) { + $currencies += $currency.Name + $script:htManagementGroupsCost.($parentMg).currencies = $currencies + } + + #currency based + $resourcesThatGeneratedCost = $htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" + ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count + $script:htManagementGroupsCost.($parentMg)."resourcesThatGeneratedCost_$($currency.Name)" = $resourcesThatGeneratedCost + + $subscriptionsThatGeneratedCost = $htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" + 1 + $script:htManagementGroupsCost.($parentMg)."subscriptionsThatGeneratedCost_$($currency.Name)" = $subscriptionsThatGeneratedCost + + $consumptionDataSubscriptions = $htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" += $subscriptionId.group + $script:htManagementGroupsCost.($parentMg)."consumptionDataSubscriptions_$($currency.Name)" = $consumptionDataSubscriptions + + $resourceTypesThatGeneratedCost = $htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" + foreach ($resourceType in $resourceTypes) { + if ($resourceTypesThatGeneratedCost -notcontains $resourceType) { + $resourceTypesThatGeneratedCost += $resourceType + } + } + $script:htManagementGroupsCost.($parentMg)."resourceTypesThatGeneratedCost_$($currency.Name)" = $resourceTypesThatGeneratedCost + + #currencyIndependent + $resourcesThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent + ($subscriptionId.Group.ResourceId | Sort-Object -Unique | Measure-Object).Count + $script:htManagementGroupsCost.($parentMg).resourcesThatGeneratedCostCurrencyIndependent = $resourcesThatGeneratedCostCurrencyIndependent + + $subscriptionsThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent + 1 + $script:htManagementGroupsCost.($parentMg).subscriptionsThatGeneratedCostCurrencyIndependent = $subscriptionsThatGeneratedCostCurrencyIndependent + + $consumptionDataSubscriptionsCurrencyIndependent = $htManagementGroupsCost.($parentMg).consumptionDataSubscriptions += $subscriptionId.group + $script:htManagementGroupsCost.($parentMg).consumptionDataSubscriptions = $consumptionDataSubscriptionsCurrencyIndependent + + $resourceTypesThatGeneratedCostCurrencyIndependent = $htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent + foreach ($resourceType in $resourceTypes) { + if ($resourceTypesThatGeneratedCostCurrencyIndependent -notcontains $resourceType) { + $resourceTypesThatGeneratedCostCurrencyIndependent += $resourceType + } + } + $script:htManagementGroupsCost.($parentMg).resourceTypesThatGeneratedCostCurrencyIndependent = $resourceTypesThatGeneratedCostCurrencyIndependent + } + } + } + + $totalCost = 0 + $script:tenantSummaryConsumptionDataGrouped = $currency.group | Group-Object -property ConsumedService, ChargeType, MeterCategory + $subsCount = ($tenantSummaryConsumptionDataGrouped.group.subscriptionId | Sort-Object -Unique | Measure-Object).Count + $consumedServiceCount = ($tenantSummaryConsumptionDataGrouped.group.consumedService | Sort-Object -Unique | Measure-Object).Count + $resourceCount = ($tenantSummaryConsumptionDataGrouped.group.ResourceId | Sort-Object -Unique | Measure-Object).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 = $script:arrayConsumptionData.Add([PSCustomObject]@{ + ConsumedService = ($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 + } + else { + $totalCost = [math]::Round($totalCost, 2).ToString('0.00') + } + $script:arrayTotalCostSummary += "$($totalCost) $($currency.Name) generated by $($resourceCount) Resources ($($consumedServiceCount) ResourceTypes) in $($subsCount) Subscriptions" + } + } + else { + Write-Host ' No relevant consumption data entries (0)' + } + } + + #region BuildConsumptionCSV + if (-not $NoAzureConsumptionReportExportToCSV) { + Write-Host " Exporting Consumption CSV $($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" + $startBuildConsumptionCSV = Get-Date + if ($CsvExportUseQuotesAsNeeded) { + $allConsumptionData | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" -Delimiter "$csvDelimiter" -NoTypeInformation -UseQuotes AsNeeded + } + else { + $allConsumptionData | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_Consumption.csv" -Delimiter "$csvDelimiter" -NoTypeInformation + } + $endBuildConsumptionCSV = Get-Date + Write-Host " Exporting Consumption CSV total duration: $((NEW-TIMESPAN -Start $startBuildConsumptionCSV -End $endBuildConsumptionCSV).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startBuildConsumptionCSV -End $endBuildConsumptionCSV).TotalSeconds) seconds)" + } + #endregion BuildConsumptionCSV + } + $endConsumptionData = Get-Date + Write-Host "Getting Consumption data duration: $((NEW-TIMESPAN -Start $startConsumptionData -End $endConsumptionData).TotalSeconds) seconds" +} \ No newline at end of file diff --git a/pwsh/dev/functions/getDefaultManagementGroup.ps1 b/pwsh/dev/functions/getDefaultManagementGroup.ps1 new file mode 100644 index 00000000..a3e4dd3d --- /dev/null +++ b/pwsh/dev/functions/getDefaultManagementGroup.ps1 @@ -0,0 +1,20 @@ +function getDefaultManagementGroup { + $currentTask = 'Get Default Management Group' + Write-Host $currentTask + #https://docs.microsoft.com/en-us/azure/governance/management-groups/how-to/protect-resource-hierarchy#setting---default-management-group + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/managementGroups/$($azAPICallConf['checkContext'].Tenant.Id)/settings?api-version=2020-02-01" + $method = 'GET' + $settingsMG = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask + + if (($settingsMG).count -gt 0) { + write-host " default ManagementGroup Id: $($settingsMG.properties.defaultManagementGroup)" + $script:defaultManagementGroupId = $settingsMG.properties.defaultManagementGroup + write-host " requireAuthorizationForGroupCreation: $($settingsMG.properties.requireAuthorizationForGroupCreation)" + $script:requireAuthorizationForGroupCreation = $settingsMG.properties.requireAuthorizationForGroupCreation + } + else { + write-host " default ManagementGroup: $(($azAPICallConf['checkContext']).Tenant.Id) (Tenant Root)" + $script:defaultManagementGroupId = ($azAPICallConf['checkContext']).Tenant.Id + $script:requireAuthorizationForGroupCreation = $false + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/getEntities.ps1 b/pwsh/dev/functions/getEntities.ps1 new file mode 100644 index 00000000..0b823bbc --- /dev/null +++ b/pwsh/dev/functions/getEntities.ps1 @@ -0,0 +1,89 @@ +function getEntities { + Write-Host 'Entities' + $startEntities = Get-Date + $currentTask = ' Getting Entities' + Write-Host $currentTask + #https://management.azure.com/providers/Microsoft.Management/getEntities?api-version=2020-02-01 + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Management/getEntities?api-version=2020-02-01" + $method = 'POST' + $script:arrayEntitiesFromAPI = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask + + Write-Host " $($arrayEntitiesFromAPI.Count) Entities returned" + + $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') { + $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__' + } + else { + $parent = $entity.properties.parent.Id -replace '.*/' + } + $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:htEntities.($entity.name) = @{} + $script:htEntities.($entity.name).ParentNameChain = $entity.properties.parentNameChain + $script:htEntities.($entity.name).Parent = $parent + 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 + } + + Write-Host " $(($htManagementGroupsMgPath.Keys).Count) Management Groups returned" + Write-Host " $(($htSubscriptionsMgPath.Keys).Count) Subscriptions returned" + + $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" +} \ No newline at end of file diff --git a/pwsh/dev/functions/getFileNaming.ps1 b/pwsh/dev/functions/getFileNaming.ps1 new file mode 100644 index 00000000..95e98d0b --- /dev/null +++ b/pwsh/dev/functions/getFileNaming.ps1 @@ -0,0 +1,24 @@ +function getFileNaming { + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions -eq $true) { + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $true) { + $script:fileName = "AzGovViz_HierarchyMapOnly_$($ManagementGroupId)" + } + elseif ($azAPICallConf['htParameters'].ManagementGroupsOnly -eq $true) { + $script:fileName = "AzGovViz_ManagementGroupsOnly_$($ManagementGroupId)" + } + else { + $script:fileName = "AzGovViz_$($ManagementGroupId)" + } + } + else { + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $true) { + $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)" + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/getGroupmembers.ps1 b/pwsh/dev/functions/getGroupmembers.ps1 new file mode 100644 index 00000000..e1c6d821 --- /dev/null +++ b/pwsh/dev/functions/getGroupmembers.ps1 @@ -0,0 +1,141 @@ + +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 + } + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/getMDfCSecureScoreMG.ps1 b/pwsh/dev/functions/getMDfCSecureScoreMG.ps1 new file mode 100644 index 00000000..3c4dedd4 --- /dev/null +++ b/pwsh/dev/functions/getMDfCSecureScoreMG.ps1 @@ -0,0 +1,55 @@ +function getMDfCSecureScoreMG { + $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)" + ] + } +"@ + + $start = Get-Date + $getMgAscSecureScore = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask -body $body -listenOn 'Content' + $end = Get-Date + Write-Host " Getting Microsoft Defender for Cloud Secure Score for Management Groups duration: $((NEW-TIMESPAN -Start $start -End $end).TotalSeconds) seconds" + if ($getMgAscSecureScore) { + if ($getMgAscSecureScore -eq 'capitulation') { + Write-Host ' Microsoft Defender for Cloud SecureScore for Management Groups will not be available' -ForegroundColor Yellow + } + else { + foreach ($entry in $getMgAscSecureScore.data) { + $script:htMgASCSecureScore.($entry.mgId) = @{} + if ($entry.secureScore -eq 404) { + $script:htMgASCSecureScore.($entry.mgId).SecureScore = 'n/a' + } + else { + $script:htMgASCSecureScore.($entry.mgId).SecureScore = $entry.secureScore + } + } + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/getResourceDiagnosticsCapability.ps1 b/pwsh/dev/functions/getResourceDiagnosticsCapability.ps1 new file mode 100644 index 00000000..2d307576 --- /dev/null +++ b/pwsh/dev/functions/getResourceDiagnosticsCapability.ps1 @@ -0,0 +1,133 @@ +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 to process" + $script:resourceTypesSummarizedArray = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + + $script:resourceTypesDiagnosticsArray = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList)) + $resourceTypesUnique.where( { $_.Name -like 'microsoft.*' }) | ForEach-Object -Parallel { + $resourceTypesUniqueGroup = $_ + $resourcetype = $resourceTypesUniqueGroup.Name + #region UsingVARs + #fromOtherFunctions + $azAPICallConf = $using:azAPICallConf + #Array&HTs + $ExcludedResourceTypesDiagnosticsCapable = $using:ExcludedResourceTypesDiagnosticsCapable + $resourceTypesDiagnosticsArray = $using:resourceTypesDiagnosticsArray + $htResourceTypesUniqueResource = $using:htResourceTypesUniqueResource + $resourceTypesSummarizedArray = $using:resourceTypesSummarizedArray + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #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 + $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 -method $method -currentTask $currentTask + if ($responseJSON -notlike 'meanwhile_deleted*') { + 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' meanwhile deleted" + } + } + until ($resourceAvailability -lt 0 -or $responseJSON -notlike 'meanwhile_deleted*') + + if ($resourceAvailability -lt 0 -and $responseJSON -like 'meanwhile_deleted*') { + Write-Host "tried for all available resourceIds ($($resourceCount)) for resourceType $resourceType, but seems all resources meanwhile have been deleted" + $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 + #[System.GC]::Collect() + } + 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)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/getSubscriptions.ps1 b/pwsh/dev/functions/getSubscriptions.ps1 new file mode 100644 index 00000000..8995c2d8 --- /dev/null +++ b/pwsh/dev/functions/getSubscriptions.ps1 @@ -0,0 +1,17 @@ +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 + + Write-Host " $($requestAllSubscriptionsAPI.Count) Subscriptions returned" + foreach ($subscription in $requestAllSubscriptionsAPI) { + $script:htAllSubscriptionsFromAPI.($subscription.subscriptionId) = @{} + $script:htAllSubscriptionsFromAPI.($subscription.subscriptionId).subDetails = $subscription + } + + $endGetSubscriptions = Get-Date + Write-Host "Getting all Subscriptions duration: $((NEW-TIMESPAN -Start $startGetSubscriptions -End $endGetSubscriptions).TotalSeconds) seconds" +} \ No newline at end of file diff --git a/pwsh/dev/functions/getTenantDetails.ps1 b/pwsh/dev/functions/getTenantDetails.ps1 new file mode 100644 index 00000000..f19b0a65 --- /dev/null +++ b/pwsh/dev/functions/getTenantDetails.ps1 @@ -0,0 +1,25 @@ +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' + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/handleCloudEnvironment.ps1 b/pwsh/dev/functions/handleCloudEnvironment.ps1 new file mode 100644 index 00000000..32aee825 --- /dev/null +++ b/pwsh/dev/functions/handleCloudEnvironment.ps1 @@ -0,0 +1,9 @@ +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 + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/html/htmlFunctions.ps1 b/pwsh/dev/functions/html/htmlFunctions.ps1 new file mode 100644 index 00000000..894fc8bf --- /dev/null +++ b/pwsh/dev/functions/html/htmlFunctions.ps1 @@ -0,0 +1,350 @@ +#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 += @" +
  • + +
    + +
    +
    +"@ + if ($mgPolicyAssignmentCount -gt 0 -or $mgPolicyPolicySetScopedCount -gt 0) { + if ($mgPolicyAssignmentCount -gt 0 -and $mgPolicyPolicySetScopedCount -gt 0) { + $script:html += @" +
    + $($mgPolicyAssignmentCount) +
    +
    + $($mgPolicyPolicySetScopedCount) +
    +"@ + } + else { + if ($mgPolicyAssignmentCount -gt 0) { + $script:html += @" +
    + $($mgPolicyAssignmentCount) +
    +"@ + } + if ($mgPolicyPolicySetScopedCount -gt 0) { + $script:html += @" +
    + $($mgPolicyPolicySetScopedCount) +
    +"@ + } + } + } + else { + $script:html += @' +
    +'@ + } + $script:html += @' +
    + +
    +'@ + if ($mgIdRoleAssignmentCount -gt 0) { + $script:html += @" +
    + $($mgIdRoleAssignmentCount) +
    +"@ + } + else { + $script:html += @' +
    +'@ + } + $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 += @" + +
    + + +"@ + if ($mgId -eq $defaultManagementGroupId) { + $script:html += @' + +'@ + } + $script:html += @" + + + +"@ + } + 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 += @" + + + + + +

    Highlight Management Group in HierarchyMap

    Default Management Group docs

    Management Group Name: $($mgName -replace '<', '<' -replace '>', '>')

    Management Group Id: $mgId

    Management Group Path: $mgPath

    + +
    +"@ + } + foreach ($subEntry in $subscriptions | Sort-Object -Property subscription, subscriptionId) { + #$subPath = $htSubscriptionsMgPath.($subEntry.subscriptionId).pathDelimited + if ($subscriptionLinkedCount -gt 1) { + if (-not $NoScopeInsights) { + $script:html += @" + +
    +"@ + } + } + #exactly 1 + else { + if (-not $NoScopeInsights) { + $script:html += @" + $($subEntry.subscription -replace '<', '<' -replace '>', '>') ($($subEntry.subscriptionId)) +"@ + } + } + if (-not $NoScopeInsights) { + $script:html += @" + + +"@ + } + + if (-not $azAPICallConf['htParameters'].ManagementGroupsOnly) { + processScopeInsightsMgOrSub -mgOrSub 'sub' -subscriptionId $subEntry.subscriptionId -subscriptionsMgId $mgChild + } + + if (-not $NoScopeInsights) { + $script:html += @' +

    Highlight Subscription in HierarchyMap

    +'@ + } + 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 \ No newline at end of file diff --git a/pwsh/dev/functions/namingValidation.ps1 b/pwsh/dev/functions/namingValidation.ps1 new file mode 100644 index 00000000..752b1da4 --- /dev/null +++ b/pwsh/dev/functions/namingValidation.ps1 @@ -0,0 +1,16 @@ +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 +} \ No newline at end of file diff --git a/pwsh/dev/functions/prepareData.ps1 b/pwsh/dev/functions/prepareData.ps1 new file mode 100644 index 00000000..68a276ea --- /dev/null +++ b/pwsh/dev/functions/prepareData.ps1 @@ -0,0 +1,32 @@ +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)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/processAADGroups.ps1 b/pwsh/dev/functions/processAADGroups.ps1 new file mode 100644 index 00000000..d07505fa --- /dev/null +++ b/pwsh/dev/functions/processAADGroups.ps1 @@ -0,0 +1,105 @@ +function processAADGroups { + Write-Host 'Resolving AAD Groups (for which a RBAC Role assignment exists)' + Write-Host " Users known as Guest count: $($htUserTypesGuest.Keys.Count) (before Resolving AAD Groups)" + $startAADGroupsResolveMembers = Get-Date + + $optimizedTableForAADGroupsQuery = ($roleAssignmentsUniqueById.where( { $_.RoleAssignmentIdentityObjectType -eq 'Group' } ) | Select-Object -Property RoleAssignmentIdentityObjectId, RoleAssignmentIdentityDisplayname) | Sort-Object -Property RoleAssignmentIdentityObjectId -Unique + $aadGroupsCount = ($optimizedTableForAADGroupsQuery).Count + + 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 with Role assignments (indicating progress in steps of $indicator)" + + $optimizedTableForAADGroupsQuery | ForEach-Object -Parallel { + $aadGroupIdWithRoleAssignment = $_ + #region UsingVARs + #fromOtherFunctions + $AADGroupMembersLimit = $using:AADGroupMembersLimit + $azAPICallConf = $using:azAPICallConf + #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 + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #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) + #[System.GC]::Collect() + } + else { + Write-Host " processing $($aadGroupsCount) AAD Groups with Role assignments" + } + + $arrayGroupRequestResourceNotFoundCount = ($arrayGroupRequestResourceNotFound).Count + if ($arrayGroupRequestResourceNotFoundCount -gt 0) { + Write-Host "$arrayGroupRequestResourceNotFoundCount Groups could not be checked for Memberships" + } + + Write-Host " Collected $($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)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/processApplications.ps1 b/pwsh/dev/functions/processApplications.ps1 new file mode 100644 index 00000000..3827b176 --- /dev/null +++ b/pwsh/dev/functions/processApplications.ps1 @@ -0,0 +1,133 @@ +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 + #Array&HTs + $arrayApplicationRequestResourceNotFound = $using:arrayApplicationRequestResourceNotFound + $htAppDetails = $using:htAppDetails + $htServicePrincipals = $using:htServicePrincipals + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #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)" + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/processDataCollection.ps1 b/pwsh/dev/functions/processDataCollection.ps1 new file mode 100644 index 00000000..6a8c6215 --- /dev/null +++ b/pwsh/dev/functions/processDataCollection.ps1 @@ -0,0 +1,1044 @@ +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 + + $mgBatch = ($allManagementGroupsFromEntitiesChildOfRequestedMg | Group-Object -Property { ($_.properties.parentNameChain).Count }) | Sort-Object -Property Name + 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 + #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 + $arrayAPICallTrackingCustomDataCollection = $using:arrayAPICallTrackingCustomDataCollection + $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 + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:addRowToTable = $using:funcAddRowToTable + $function:namingValidation = $using:funcNamingValidation + $function:resolveObjectIds = $using:funcResolveObjectIds + + #$function:dataCollectionFunctions = $using:funcdataCollectionFunctions + + $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 + + #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 + #[System.GC]::Collect() + } + + $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)" + + #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' + $subsExcludedStateCount = ($outOfScopeSubscriptions.where( { $_.outOfScopeReason -like 'State*' } )).Count + $subsExcludedWhitelistCount = ($outOfScopeSubscriptions.where( { $_.outOfScopeReason -like 'QuotaId*' } )).Count + if ($subsExcludedStateCount -gt 0) { + Write-Host " CustomDataCollection $($subsExcludedStateCount) Subscriptions excluded (State != enabled)" + } + if ($subsExcludedWhitelistCount -gt 0) { + Write-Host " CustomDataCollection $($subsExcludedWhitelistCount) Subscriptions excluded (not in quotaId whitelist: '$($SubscriptionQuotaIdWhitelist -join ', ')' OR is AAD_ quotaId)" + } + 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) { + #[System.GC]::Collect() + $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 + #Array&HTs + $newTable = $using:newTable + $resourcesAll = $using:resourcesAll + $resourcesIdsAll = $using:resourcesIdsAll + $resourceGroupsAll = $using:resourceGroupsAll + $customDataCollectionDuration = $using:customDataCollectionDuration + $htSubscriptionsMgPath = $using:htSubscriptionsMgPath + $htManagementGroupsMgPath = $using:htManagementGroupsMgPath + $htResourceProvidersAll = $using:htResourceProvidersAll + $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 + $arrayAPICallTrackingCustomDataCollection = $using:arrayAPICallTrackingCustomDataCollection + $arrayDiagnosticSettingsMgSub = $using:arrayDiagnosticSettingsMgSub + $htMgASCSecureScore = $using:htMgASCSecureScore + $htRoleAssignmentsFromAPIInheritancePrevention = $using:htRoleAssignmentsFromAPIInheritancePrevention + $htNamingValidation = $using:htNamingValidation + $htPrincipals = $using:htPrincipals + $htServicePrincipals = $using:htServicePrincipals + $htUserTypesGuest = $using:htUserTypesGuest + $arrayDefenderPlans = $using:arrayDefenderPlans + $arrayDefenderPlansSubscriptionNotRegistered = $using:arrayDefenderPlansSubscriptionNotRegistered + $arrayUserAssignedIdentities4Resources = $using:arrayUserAssignedIdentities4Resources + $htSubscriptionsRoleAssignmentLimit = $using:htSubscriptionsRoleAssignmentLimit + #Functions + #AzAPICall + # $function:AzAPICall = $using:AzAPICallFunctions.funcAzAPICall + # $function:createBearerToken = $using:AzAPICallFunctions.funcCreateBearerToken + # $function:GetJWTDetails = $using:AzAPICallFunctions.funcGetJWTDetails + # $function:Logging = $using:AzAPICallFunctions.funcLogging + if ($azAPICallConf['htParameters'].onAzureDevOpsOrGitHubActions) { + Import-Module ".\pwsh\AzAPICallModule\AzAPICall\$($azAPICallConf['htParameters'].azAPICallModuleVersion)\AzAPICall.psd1" -Force -ErrorAction Stop + } + else { + Import-Module -Name AzAPICall -RequiredVersion $azAPICallConf['htParameters'].azAPICallModuleVersion -Force -ErrorAction Stop + } + #other + $function:addRowToTable = $using:funcAddRowToTable + $function:namingValidation = $using:funcNamingValidation + $function:resolveObjectIds = $using:funcResolveObjectIds + $function:dataCollectionMGSecureScore = $using:funcDataCollectionMGSecureScore + $function:dataCollectionDefenderPlans = $using:funcDataCollectionDefenderPlans + $function:dataCollectionDiagnosticsSub = $using:funcDataCollectionDiagnosticsSub + $function:dataCollectionResources = $using:funcDataCollectionResources + $function:dataCollectionResourceGroups = $using:funcDataCollectionResourceGroups + $function:dataCollectionResourceProviders = $using:funcDataCollectionResourceProviders + $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 + #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 + $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 + } + } + + #$rndom = Get-Random -Minimum 10 -Maximum 750 + #start-sleep -Millisecond $rndom + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $false) { + $currentSubscription = $htAllSubscriptionsFromAPI.($childMgSubId).subDetails + $subscriptionQuotaId = $currentSubscription.subscriptionPolicies.quotaId + $subscriptionState = $currentSubscription.state + + $targetMgOrSub = 'Sub' + $baseParameters = @{ + scopeId = $childMgSubId + scopeDisplayName = $childMgSubDisplayName + } + + if (-not $azAPICallConf['htParameters'].ManagementGroupsOnly) { + #mgSecureScore + $mgAscSecureScoreResult = DataCollectionMGSecureScore -Id $childMgId + + #defenderPlans + $dataCollectionDefenderPlansParameters = @{ + ChildMgMgPath = $childMgMgPath + } + DataCollectionDefenderPlans @baseParameters @dataCollectionDefenderPlansParameters + + #diagnostics + $dataCollectionDiagnosticsSubParameters = @{ + ChildMgMgPath = $childMgMgPath + ChildMgId = $childMgId + } + DataCollectionDiagnosticsSub @baseParameters @dataCollectionDiagnosticsSubParameters + + + if ($azAPICallConf['htParameters'].NoResources -eq $false) { + #resources + $dataCollectionResourcesParameters = @{ + ChildMgMgPath = $childMgMgPath + } + DataCollectionResources @baseParameters @dataCollectionResourcesParameters + } + + #resourceGroups + DataCollectionResourceGroups @baseParameters + + #resourceProviders + DataCollectionResourceProviders @baseParameters + + #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 + } + } + + 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)" + } + #[System.GC]::Collect() + + $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)" + + #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))" + + #APITracking + $APICallTrackingCount = ($arrayAPICallTrackingCustomDataCollection).Count + $APICallTrackingRetriesCount = ($arrayAPICallTrackingCustomDataCollection.where( { $_.TryCounter -gt 1 } )).Count + $APICallTrackingRestartDueToDuplicateNextlinkCounterCount = ($arrayAPICallTrackingCustomDataCollection.where( { $_.RestartDueToDuplicateNextlinkCounter -gt 0 } )).Count + Write-Host "Collecting custom data APICalls (Management) total count: $APICallTrackingCount ($APICallTrackingRetriesCount retries; $APICallTrackingRestartDueToDuplicateNextlinkCounterCount nextLinkReset)" + + 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) { + #DataCollection Export of All Resources + 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 + } + } + } + + 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' + } + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/processDefinitionInsights.ps1 b/pwsh/dev/functions/processDefinitionInsights.ps1 new file mode 100644 index 00000000..250ec4ff --- /dev/null +++ b/pwsh/dev/functions/processDefinitionInsights.ps1 @@ -0,0 +1,996 @@ +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' + $tfCount = $tenantAllPoliciesCount + $htmlTableId = 'definitionInsights_Policy' + [void]$htmlDefinitionInsights.AppendLine( @" + +
    + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + + + + + +
    + + +
    + + + + + +
    + + +
    + +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + +"@) + + $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" + } + + $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 '-' + @" + + + + + + + + + + + + + + + + + +"@ + } + [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) + $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $htmlDefinitionInsights = [System.Text.StringBuilder]::new() + [void]$htmlDefinitionInsights.AppendLine( @" + +
    JSONPolicyTypeCategoryDeprecatedPreviewScope Mg/SubScope Name/IdeffectDefaultValuehasAssignmentsAssignments CountAssignmentsUsedInPolicySetPolicySetsCountPolicySetsRoles
    + +
    + + +
    + +
    + + + +
    $($policy.Type)$($policy.Category -replace '<', '<' -replace '>', '>')$($policy.Deprecated)$($policy.Preview)$($policy.ScopeMgSub)$($scopeDetails -replace '<', '<' -replace '>', '>')$($policy.effectDefaultValue)$hasAssignments$assignmentsCount$assignmentsDetailed$usedInPolicySet$usedInPolicySetCount$usedInPolicySets$($roleDefinitionIds -replace '<', '<' -replace '>', '>')
    +
    + +
    +"@) + $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' + $tfCount = $tenantAllPolicySetsCount + $htmlTableId = 'definitionInsights_PolicySet' + [void]$htmlDefinitionInsights.AppendLine( @" + +
    + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    + + + + + + + + + + + + + + + + + +"@) + $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 '-' + @" + + + + + + + + + + + + +"@ + } + [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) + $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $htmlDefinitionInsights = [System.Text.StringBuilder]::new() + [void]$htmlDefinitionInsights.AppendLine( @" + +
    JSONPolicySet TypeCategoryDeprecatedPreviewScope Mg/SubScope Name/IdhasAssignmentsAssignments CountAssignments
    + +
    + + +
    + +
    + + + +
    $($policySet.Type)$($policySet.Category -replace '<', '<' -replace '>', '>')$($policySet.Deprecated)$($policySet.Preview)$($policySet.ScopeMgSub)$($scopeDetails -replace '<', '<' -replace '>', '>')$hasAssignments$assignmentsCount$assignmentsDetailed
    +
    + +
    +"@) + $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' + $tfCount = $tenantAllRolesCount + $htmlTableId = 'definitionInsights_Roles' + [void]$htmlDefinitionInsights.AppendLine( @" + +
    + +
    +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    +
    + + +
    + + + + + + + + + + + + + + +"@) + $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 '-' + @" + + + + + + + + + +"@ + } + + #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 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation + $arrayRoleDefinitionsForCSVExport = $null + } + #endregion exportCSV + + [void]$htmlDefinitionInsights.AppendLine($htmlDefinitionInsightshlp) + $htmlDefinitionInsights | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $htmlDefinitionInsights = [System.Text.StringBuilder]::new() + [void]$htmlDefinitionInsights.AppendLine( @" + +
    JSONRole TypeDatacanDoRoleAssignmentshasAssignmentsAssignments CountAssignments
    + +
    + + +
    + +
    + + + +
    $($roleType)$($roleManageData)$($role.RoleCanDoRoleAssignments)$hasAssignments$assignmentsCount$assignmentsDetailed
    +
    + +
    +"@) + $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 + + $script:html += $htmlDefinitionInsights + $htmlDefinitionInsights = $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)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/processDiagramMermaid.ps1 b/pwsh/dev/functions/processDiagramMermaid.ps1 new file mode 100644 index 00000000..5305a9dc --- /dev/null +++ b/pwsh/dev/functions/processDiagramMermaid.ps1 @@ -0,0 +1,90 @@ +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 +"@ + } + } + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/processHierarchyMapOnly.ps1 b/pwsh/dev/functions/processHierarchyMapOnly.ps1 new file mode 100644 index 00000000..ecb1014e --- /dev/null +++ b/pwsh/dev/functions/processHierarchyMapOnly.ps1 @@ -0,0 +1,24 @@ +function processHierarchyMapOnly { + foreach ($entity in $arrayEntitiesFromAPI) { + if ($entity.properties.parentNameChain -contains $ManagementGroupID -or $entity.Name -eq $ManagementGroupId) { + if ($entity.type -eq '/subscriptions') { + addRowToTable ` + -level (($htEntities.($entity.name).ParentNameChain).Count - 1) ` + -mgName $htEntities.(($entity.properties.parent.Id) -replace '.*/').displayName ` + -mgId (($entity.properties.parent.Id) -replace '.*/') ` + -mgParentId $htEntities.(($entity.properties.parent.Id) -replace '.*/').Parent ` + -mgParentName $htEntities.(($entity.properties.parent.Id) -replace '.*/').ParentDisplayName ` + -Subscription $htEntities.($entity.name).DisplayName ` + -SubscriptionId $htEntities.($entity.name).Id + } + if ($entity.type -eq 'Microsoft.Management/managementGroups') { + addRowToTable ` + -level ($htEntities.($entity.name).ParentNameChain).Count ` + -mgName $entity.properties.displayname ` + -mgId $entity.Name ` + -mgParentId $htEntities.($entity.name).Parent ` + -mgParentName $htEntities.($entity.name).ParentDisplayName + } + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/processManagedIdentities.ps1 b/pwsh/dev/functions/processManagedIdentities.ps1 new file mode 100644 index 00000000..a77a77ad --- /dev/null +++ b/pwsh/dev/functions/processManagedIdentities.ps1 @@ -0,0 +1,26 @@ +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)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/processScopeInsightsMgOrSub.ps1 b/pwsh/dev/functions/processScopeInsightsMgOrSub.ps1 new file mode 100644 index 00000000..2a2559df --- /dev/null +++ b/pwsh/dev/functions/processScopeInsightsMgOrSub.ps1 @@ -0,0 +1,3155 @@ +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 + + $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') { + [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

    + +"@) + + #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(@" + +
    +"@) + + 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 + + + + + + + + + +"@) + + 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(@" + + + + +"@) + } + [void]$htmlScopeInsights.AppendLine(@" + + +
    PlanTier
    $($thisDefenderPlan)$($plan.defenderPlanTier)
    + +
    +"@) + } + else { + $subscriptionNotregisteredMDfC = $arrayDefenderPlansSubscriptionNotRegistered.where( { $_.subscriptionId -eq $subscriptionId } ) + if ($subscriptionNotregisteredMDfC.Count -gt 0) { + [void]$htmlScopeInsights.AppendLine(@' +

    Microsoft Defender for Cloud plans - Subscription not registered (ResourceProvider: Microsoft.Security) docs

    +'@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + +"@) + foreach ($logCategory in $diagnosticSettingsSubCategories) { + [void]$htmlScopeInsights.AppendLine(@" + +"@) + } + [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) { + @" + + + + +"@ + foreach ($logCategory in $diagnosticSettingsSubCategories) { + if (($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticCategoriesHt.($logCategory)) { + @" + +"@ + } + else { + @' + +'@ + } + } + @' + +'@ + } + } + + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsSub) + [void]$htmlScopeInsights.AppendLine(@" + +
    Diagnostic settingTargetTarget Id$logCategory
    $(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticSettingName)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticTargetType)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticTargetId)$(($htDiagnosticSettingsMgSub).sub.($subscriptionId).$entry.$diagset.DiagnosticCategoriesHt.($logCategory))n/a
    + +
    +"@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + +"@) + $htmlScopeInsightsTags = $null + $htmlScopeInsightsTags = foreach ($tag in (($htSubscriptionTags).($subscriptionId)).keys | Sort-Object) { + @" + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTags) + [void]$htmlScopeInsights.AppendLine(@" + +
    Tag NameTag Value
    $tag$($htSubscriptionTags.$subscriptionId[$tag])
    + +
    +"@) + } + 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(@" + +
    +   Resource naming and tagging decision guide docs
    +   Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlScopeInsightsTagsUsage = $null + $htmlScopeInsightsTagsUsage = foreach ($tagEntry in $arrayTagListSubscription | Sort-Object Scope, TagName -CaseSensitive) { + @" + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTagsUsage) + [void]$htmlScopeInsights.AppendLine(@" + +
    ScopeTagNameCount
    $($tagEntry.Scope)$($tagEntry.TagName)$($tagEntry.TagCount)
    + +
    +"@) + } + 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.consumedService | Sort-Object -Unique | Measure-Object).Count + $resourceCount = ($consumptionData.ResourceId | Sort-Object -Unique | Measure-Object).Count + $subConsumptionDataGrouped = $consumptionData | Group-Object -property ConsumedService, 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]@{ + ConsumedService = ($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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + + +"@) + $htmlScopeInsightsConsumptionSub = $null + $htmlScopeInsightsConsumptionSub = foreach ($consumptionLine in $arrayConsumptionData) { + @" + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsConsumptionSub) + [void]$htmlScopeInsights.AppendLine(@" + +
    ChargeTypeResourceTypeCategoryResourceCountCost ($($AzureConsumptionPeriod)d)Currency
    $($consumptionLine.ConsumedServiceChargeType)$($consumptionLine.ConsumedService)$($consumptionLine.ConsumedServiceCategory)$($consumptionLine.ConsumedServiceInstanceCount)$($consumptionLine.ConsumedServiceCost)$($currency)
    +
    + +"@) + } + 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'].NoResourceProvidersDetailed -eq $false) { + if (($htResourceProvidersAll).($subscriptionId)) { + $tfCount = ($htResourceProvidersAll).($subscriptionId).Providers.Count + $htmlTableId = "ScopeInsights_ResourceProvider_$($subscriptionId -replace '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma + + + + + + + + +"@) + $htmlScopeInsightsResourceProvidersDetailed = $null + $htmlScopeInsightsResourceProvidersDetailed = foreach ($provider in ($htResourceProvidersAll).($subscriptionId).Providers) { + @" + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResourceProvidersDetailed) + [void]$htmlScopeInsights.AppendLine(@" + +
    ProviderState
    $($provider.namespace)$($provider.registrationState)
    +
    + +"@) + } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $(($htResourceProvidersAll.Keys).count) Resource Providers

    +"@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + } + #endregion ScopeInsightsResourceProvidersDetailed + + #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(@" + +
    +   Considerations before applying locks docs + + + + + + + + + + + + + + + + +
    Lock scopeLock typepresence
    SubscriptionCannotDelete$($subscriptionLocksCannotDeleteCount)
    SubscriptionReadOnly$($subscriptionLocksReadOnlyCount)
    ResourceGroupCannotDelete$($resourceGroupsLocksCannotDeleteCount)
    ResourceGroupReadOnly$($resourceGroupsLocksReadOnlyCount)
    ResourceCannotDelete$($resourcesLocksCannotDeleteCount)
    ResourceReadOnly$($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 '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma + + + + + + +"@) + foreach ($logCategory in $diagnosticSettingsMgCategories) { + [void]$htmlScopeInsights.AppendLine(@" + +"@) + } + [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) { + @" + + + + +"@ + foreach ($logCategory in $diagnosticSettingsMgCategories) { + if (($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticCategoriesHt.($logCategory)) { + @" + +"@ + } + else { + @' + +'@ + } + } + @' + +'@ + } + } + + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsMg) + [void]$htmlScopeInsights.AppendLine(@" + +
    Diagnostic settingTargetTarget Id$logCategory
    $(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticSettingName)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticTargetType)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticTargetId)$(($htDiagnosticSettingsMgSub).mg.($mgChild).$entry.$diagset.DiagnosticCategoriesHt.($logCategory))n/a
    + +
    +"@) + } + 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 ConsumedService, ChargeType, MeterCategory + $subsCount = ($tenantSummaryConsumptionDataGrouped.group.subscriptionId | Sort-Object -Unique).Count + $consumedServiceCount = ($tenantSummaryConsumptionDataGrouped.group.consumedService | 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]@{ + ConsumedService = ($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 '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV +semicolon | +comma + + + + + + + + + + + + + +"@) + $htmlScopeInsightsConsumptionMg = $null + $htmlScopeInsightsConsumptionMg = foreach ($consumptionLine in $arrayConsumptionData) { + @" + + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsConsumptionMg) + [void]$htmlScopeInsights.AppendLine(@" + +
    ChargeTypeResourceTypeCategoryResourceCountCost ($($AzureConsumptionPeriod)d)CurrencySubscriptions
    $($consumptionLine.ConsumedServiceChargeType)$($consumptionLine.ConsumedService)$($consumptionLine.ConsumedServiceCategory)$($consumptionLine.ConsumedServiceInstanceCount)$($consumptionLine.ConsumedServiceCost)$($consumptionLine.ConsumedServiceCurrency)$($consumptionLine.ConsumedServiceSubscriptions)
    +
    + +"@) + } + 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 '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlScopeInsightsResources = $null + $htmlScopeInsightsResources = foreach ($resourceAllChildSubscriptionResourceTypePerLocation in $resourcesAllChildSubscriptionsArray | Sort-Object @{Expression = { $_.ResourceType } }, @{Expression = { $_.location } }) { + @" + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResources) + [void]$htmlScopeInsights.AppendLine(@" + +
    ResourceTypeLocationCount
    $($resourceAllChildSubscriptionResourceTypePerLocation.ResourceType)$($resourceAllChildSubscriptionResourceTypePerLocation.location)$($resourceAllChildSubscriptionResourceTypePerLocation.ResourceCount)
    + +
    +"@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlScopeInsightsResources = $null + $htmlScopeInsightsResources = foreach ($resourceSubscriptionResourceTypePerLocation in $resourcesSubscription | Sort-Object @{Expression = { $_.type } }, @{Expression = { $_.location } }, @{Expression = { $_.count_ } }) { + @" + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsResources) + [void]$htmlScopeInsights.AppendLine(@" + +
    ResourceTypeLocationCount
    $($resourceSubscriptionResourceTypePerLocation.type)$($resourceSubscriptionResourceTypePerLocation.location)$($resourceSubscriptionResourceTypePerLocation.count_)
    + +
    +"@) + } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $resourcesSubscriptionResourceTypeCount ResourceTypes

    +"@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + } + #endregion ScopeInsightsResources + } + + #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 '-','_')" + $randomFunctionName = "func_$htmlTableId" + [void]$htmlScopeInsights.AppendLine(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + + +"@) + $htmlScopeInsightsDiagnosticsCapable = $null + $htmlScopeInsightsDiagnosticsCapable = foreach ($resourceSubscriptionResourceType in $resourceTypesSummarizedArray | Sort-Object @{Expression = { $_.ResourceType } }) { + @" + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsCapable) + [void]$htmlScopeInsights.AppendLine(@" + +
    ResourceTypeResource CountDiagnostics capableMetricsLogsLogCategories
    $($resourceSubscriptionResourceType.ResourceType)$($resourceSubscriptionResourceType.ResourceCount)$($resourceSubscriptionResourceType.DiagnosticsCapable)$($resourceSubscriptionResourceType.Metrics)$($resourceSubscriptionResourceType.Logs)$($resourceSubscriptionResourceType.LogCategories)
    + +
    +"@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + + +"@) + $htmlScopeInsightsDiagnosticsCapable = $null + $htmlScopeInsightsDiagnosticsCapable = foreach ($resourceSubscriptionResourceType in $resourceTypesSummarizedArray | Sort-Object @{Expression = { $_.ResourceType } }) { + @" + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsDiagnosticsCapable) + [void]$htmlScopeInsights.AppendLine(@" + +
    ResourceTypeResource CountDiagnostics capableMetricsLogsLogCategories
    $($resourceSubscriptionResourceType.ResourceType)$($resourceSubscriptionResourceType.ResourceCount)$($resourceSubscriptionResourceType.DiagnosticsCapable)$($resourceSubscriptionResourceType.Metrics)$($resourceSubscriptionResourceType.Logs)$($resourceSubscriptionResourceType.LogCategories)
    + +
    +"@) + } + 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(@" + +
    +   Managed identity 'user-assigned' vs 'system-assigned' docs
    +   Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + + + +"@) + $htmlScopeInsightsTags = $null + $htmlScopeInsightsTags = foreach ($miResEntry in $arrayUserAssignedIdentities4ResourcesSubscription | Sort-Object -Property miResourceId, resourceId) { + @" + + + + + + + + + + + + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsTags) + [void]$htmlScopeInsights.AppendLine(@" + +
    MI NameMI MgPathMI Subscription NameMI Subscription IdMI ResourceGroupMI ResourceIdMI AAD SP objectIdMI AAD SP applicationIdMI count Res assignments +Res NameRes TypeRes MgPathRes Subscription NameRes Subscription IdRes ResourceGroupRes IdRes count assigned MIs +
    $($miResEntry.miResourceName)$($miResEntry.miMgPath)$($miResEntry.miSubscriptionName)$($miResEntry.miSubscriptionId)$($miResEntry.miResourceGroupName)$($miResEntry.miResourceId)$($miResEntry.miPrincipalId)$($miResEntry.miClientId)$($htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount)$($miResEntry.resourceName)$($miResEntry.resourceType)$($miResEntry.resourceMgPath)$($miResEntry.resourceSubscriptionName)$($miResEntry.resourceSubscriptionId)$($miResEntry.resourceResourceGroupName)$($miResEntry.resourceId)$($htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount)
    + +
    +"@) + } + else { + [void]$htmlScopeInsights.AppendLine(@' +

    No UserAssigned Managed Identities assigned to Resources / vice versa - at all

    +'@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsUserAssignedIdentities4Resources + } + } + + #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(@" + +
    +   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 + + + + + + + + + + + + + + +"@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + + [void]$htmlScopeInsights.AppendLine(@' + + + + + +'@) + } + + [void]$htmlScopeInsights.AppendLine(@" + + + + + + + + + + + + +"@) + $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 + } + @" + + + + + + + + + + + + +"@ + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + @" + + + + + +"@ + } + + @" + + + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPolicyAssignments) + [void]$htmlScopeInsights.AppendLine(@" + +
    InheritanceScopeExcludedExemption appliesPolicy DisplayNamePolicyIdTypeCategoryEffectParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignmentIdAssignedByCreatedOnCreatedByUpdatedOnUpdatedBy
    $($policyAssignment.Inheritance)$($policyAssignment.ExcludedScope)$($policyAssignment.ExemptionScope)$($policyName)$($policyAssignment.PolicyId)$($policyAssignment.PolicyType)$($policyAssignment.PolicyCategory -replace '<', '<' -replace '>', '>')$($policyAssignment.Effect)$($policyAssignment.PolicyAssignmentParameters)$($policyAssignment.PolicyAssignmentEnforcementMode)$($policyAssignment.PolicyAssignmentNonComplianceMessages)$($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)
    +
    + +"@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + + +"@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + + [void]$htmlScopeInsights.AppendLine(@' + + + + + +'@) + } + + [void]$htmlScopeInsights.AppendLine(@" + + + + + + + + + + + + +"@) + $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 + } + @" + + + + + + + + + + +"@ + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + @" + + + + + +"@ + } + @" + + + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsPolicySetAssignments) + [void]$htmlScopeInsights.AppendLine(@" + +
    InheritanceScopeExcludedPolicySet DisplayNamePolicySetIdTypeCategoryParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignmentIdAssignedByCreatedOnCreatedByUpdatedOnUpdatedBy
    $($policyAssignment.Inheritance)$($policyAssignment.ExcludedScope)$($policyName)$($policyAssignment.PolicyId)$($policyAssignment.PolicyType)$($policyAssignment.PolicyCategory -replace '<', '<' -replace '>', '>')$($policyAssignment.PolicyAssignmentParameters)$($policyAssignment.PolicyAssignmentEnforcementMode)$($policyAssignment.PolicyAssignmentNonComplianceMessages)$($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)
    +
    + +"@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + + + +"@) + $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) + } + @" + + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsScopedPolicies) + [void]$htmlScopeInsights.AppendLine(@" + +
    Policy DisplayNamePolicyIdCategoryPolicy effectRole definitionsUnique assignmentsUsed in PolicySets
    $($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicyUsedInPolicySets)
    +
    + +"@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + +"@) + $htmlScopeInsightsScopedPolicySets = $null + $htmlScopeInsightsScopedPolicySets = foreach ($custompolicySet in $scopePolicySets | Sort-Object @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { + @" + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsScopedPolicySets) + [void]$htmlScopeInsights.AppendLine(@" + +
    PolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies Used
    $($custompolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')$($custompolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')$($custompolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')$($custompolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')$($custompolicySet.PoliciesUsed)
    +
    + +"@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + + + +"@) + $htmlScopeInsightsBlueprintAssignments = $null + $htmlScopeInsightsBlueprintAssignments = foreach ($blueprintAssigned in $blueprintsAssigned) { + @" + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsBlueprintAssignments) + [void]$htmlScopeInsights.AppendLine(@" + +
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdBlueprint VersionBlueprint AssignmentId
    $($blueprintAssigned.BlueprintName -replace '<', '<' -replace '>', '>')$($blueprintAssigned.BlueprintDisplayName -replace '<', '<' -replace '>', '>')$($blueprintAssigned.BlueprintDescription -replace '<', '<' -replace '>', '>')$($blueprintAssigned.BlueprintId -replace '<', '<' -replace '>', '>')$($blueprintAssigned.BlueprintAssignmentVersion -replace '<', '<' -replace '>', '>')$($blueprintAssigned.BlueprintAssignmentId -replace '<', '<' -replace '>', '>')
    +
    + +"@) + } + 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(@" + +
    +   Download CSV semicolon | comma + + + + + + + + + + +"@) + $htmlScopeInsightsBlueprintsScoped = $null + $htmlScopeInsightsBlueprintsScoped = foreach ($blueprintScoped in $blueprintsScoped) { + @" + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsBlueprintsScoped) + [void]$htmlScopeInsights.AppendLine(@" + +
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintId
    $($blueprintScoped.BlueprintName -replace '<', '<' -replace '>', '>')$($blueprintScoped.BlueprintDisplayName -replace '<', '<' -replace '>', '>')$($blueprintScoped.BlueprintDescription -replace '<', '<' -replace '>', '>')$($blueprintScoped.BlueprintId -replace '<', '<' -replace '>', '>')
    +
    + +"@) + } + else { + [void]$htmlScopeInsights.AppendLine(@" +

    $blueprintsScopedCount Blueprints scoped

    +"@) + } + [void]$htmlScopeInsights.AppendLine(@' + + +'@) + #endregion ScopeInsightsBlueprintsScoped + + #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 -eq 'User') { + $rolesAssignedUser++ + } + if ($roleAssignment.ObjectType -eq 'Group') { + $rolesAssignedGroup++ + } + if ($roleAssignment.ObjectType -eq 'ServicePrincipal') { + $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(@" + +
    +   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 + + + + + + + + + + + + + + + + + + + + + + + +"@) + $htmlScopeInsightsRoleAssignments = $null + $htmlScopeInsightsRoleAssignments = foreach ($roleAssignment in ($rolesAssigned | Sort-Object -Property Level, MgName, MgId, SubscriptionName, SubscriptionId, Scope, Role, RoleId, ObjectId, RoleAssignmentId)) { + @" + + + + + + + + + + + + + + + + + + + +"@ + } + [void]$htmlScopeInsights.AppendLine($htmlScopeInsightsRoleAssignments) + [void]$htmlScopeInsights.AppendLine(@" + +
    ScopeRoleRoleIdRole TypeDataCan do Role assignmentIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsRole AssignmentIdRelated Policy Assignment $noteOrNotCreatedOnCreatedBy
    $($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)
    +
    + +"@) + } + 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 + #[System.GC]::Collect() + } + } + +} \ No newline at end of file diff --git a/pwsh/dev/functions/processTenantSummary.ps1 b/pwsh/dev/functions/processTenantSummary.ps1 new file mode 100644 index 00000000..b37dd90f --- /dev/null +++ b/pwsh/dev/functions/processTenantSummary.ps1 @@ -0,0 +1,11926 @@ +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" + #if ($cnter % 5000 -eq 0) { + #[System.GC]::Collect() + #} + } + $scope = $null + + if ($rbac.RoleAssignmentPIM -eq 'true') { + $pim = $true + $pimAssignmentType = $rbac.RoleAssignmentPIMAssignmentType + $pimSlotStart = $($rbac.RoleAssignmentPIMSlotStart) + $pimSlotEnd = $($rbac.RoleAssignmentPIMSlotEnd) + } + else { + $pim = $false + $pimAssignmentType = '' + $pimSlotStart = '' + $pimSlotEnd = '' + } + + if ($rbac.RoleAssignmentId -like '/providers/Microsoft.Management/managementGroups/*') { + $tenOrMgOrSubOrRGOrRes = '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' + $tenOrMgOrSubOrRGOrRes = 'Sub' + } + + if ($rbac.RoleAssignmentId -like '/subscriptions/*/resourcegroups/*') { + $scope = 'thisScope Sub RG' + $tenOrMgOrSubOrRGOrRes = 'RG' + } + + if ($rbac.RoleAssignmentId -like '/subscriptions/*/resourcegroups/*/providers/*/providers/*') { + $scope = 'thisScope Sub RG Res' + $tenOrMgOrSubOrRGOrRes = 'Res' + } + + if ($rbac.RoleAssignmentId -like '/providers/Microsoft.Authorization/roleAssignments/*') { + $scope = 'inherited Tenant' + $tenOrMgOrSubOrRGOrRes = '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 + RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName + RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG + RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes + 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 + 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 + TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes + 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 + RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName + RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG + RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes + 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 + 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 + TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes + 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 + RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName + RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG + RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes + 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 + 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 = "AzGovViz:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))" + ObjectSignInName = "AzGovViz:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))" + ObjectId = "AzGovViz:TooManyMembers ($($htAADGroupsDetails.($rbac.RoleAssignmentIdentityObjectId).MembersAllCount))" + ObjectType = 'unresolved' + TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes + 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 + RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName + RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG + RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes + 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 + 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 + TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes + 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 + RoleAssignmentScopeName = $rbac.RoleAssignmentScopeName + RoleAssignmentScopeRG = $rbac.RoleAssignmentScopeRG + RoleAssignmentScopeRes = $rbac.RoleAssignmentScopeRes + 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 + 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 + TenOrMgOrSubOrRGOrRes = $tenOrMgOrSubOrRGOrRes + RbacRelatedPolicyAssignment = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignment + RbacRelatedPolicyAssignmentClear = $hlpRoleAssignmentRelatedPolicyAssignments.relatedPolicyAssignmentClear + RoleSecurityCustomRoleOwner = $rbac.RoleSecurityCustomRoleOwner + RoleSecurityOwnerAssignmentSP = $rbac.RoleSecurityOwnerAssignmentSP + RoleCanDoRoleAssignments = $rbac.RoleCanDoRoleAssignments + }) + } + } + #endregion createRBACAll + + 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 ($htIdentitiesWithRoleAssignmentsUnique.($createdBy)) { + $createdBy = $htIdentitiesWithRoleAssignmentsUnique.($createdBy).details + $rbac.CreatedBy = $createdBy + } + else { + if (-not $htNonResolvedIdentities.($rbac.createdBy)) { + $htNonResolvedIdentities.($rbac.createdBy) = @{} + } + } + } + #endregion enrichrbacAll + + $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 -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 AzGovViz - 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*' })) { + if ($htResolvedIdentities.($rbac.CreatedBy)) { + $rbac.CreatedBy = $htResolvedIdentities.($rbac.CreatedBy).custObjectType + } + else { + if ([string]::IsNullOrEmpty($rbac.CreatedBy)) { + $rbac.CreatedBy = 'IsNullOrEmpty' + } + else { + $rbac.CreatedBy = "$($rbac.CreatedBy)" + } + } + } + } + } + + $endUnResolvedIdentitiesCreatedBy = Get-Date + Write-Host " UnresolvedIdentities (createdBy) duration: $((NEW-TIMESPAN -Start $startUnResolvedIdentitiesCreatedBy -End $endUnResolvedIdentitiesCreatedBy).TotalMinutes) minutes ($((NEW-TIMESPAN -Start $startUnResolvedIdentitiesCreatedBy -End $endUnResolvedIdentitiesCreatedBy).TotalSeconds) seconds)" + + $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)" + } + else { + $effect = "Fixed: $($tenantPolicy.effectFixedValue)" + } + + 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.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 + PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId + 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 + #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.PolicyDefinitionId -replace '.*/' + PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId + 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 + }) + } + else { + $null = $script:tenantPoliciesDetailed.Add([PSCustomObject]@{ + Type = 'BuiltIn' + ScopeMGLevel = $null + Scope = $null + ScopeId = $null + PolicyDisplayName = $tenantPolicy.DisplayName + PolicyDefinitionName = $tenantPolicy.PolicyDefinitionId -replace '.*/' + PolicyDefinitionId = $tenantPolicy.PolicyDefinitionId + 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 + }) + } + } + + 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 -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation + } + + if ($getMgParentName -eq 'Tenant Root') { + + if ($tenantCustomPoliciesCount -gt 0) { + $tfCount = $tenantCustomPoliciesCount + $htmlTableId = 'TenantSummary_customPolicies' + $randomFunctionName = "func_$htmlTableId" + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + +"@) + $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) + } + @" + + + + + + + + + + + + + + + +"@ + } + + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYcustompolicies) + $htmlTenantSummary | Add-Content -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName).html" -Encoding utf8 -Force + $htmlTenantSummary = [System.Text.StringBuilder]::new() + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeScope IdPolicy DisplayNamePolicyIdCategoryEffectRole definitionsUnique assignmentsUsed in PolicySetsCreatedOnCreatedByUpdatedOnUpdatedBy
    $($customPolicy.Scope)$($customPolicy.ScopeId)$($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicyUsedInPolicySets)$($customPolicy.CreatedOn)$($customPolicy.CreatedBy)$($customPolicy.UpdatedOn)$($customPolicy.UpdatedBy)
    +
    + +"@) + } + 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' + $randomFunctionName = "func_$htmlTableId" + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + +"@) + $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) + } + @" + + + + + + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYcustompolicies) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeScope IdPolicy DisplayNamePolicyIdCategoryPolicy EffectRole definitionsUnique assignmentsUsed in PolicySetsCreatedOnCreatedByUpdatedOnUpdatedBy
    $($customPolicy.Scope)$($customPolicy.ScopeId)$($customPolicy.PolicyDisplayName -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyCategory -replace '<', '<' -replace '>', '>')$($customPolicy.PolicyEffect)$($customPolicy.RoleDefinitions)$($customPolicy.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicyUsedInPolicySets)$($customPolicy.CreatedOn)$($customPolicy.CreatedBy)$($customPolicy.UpdatedOn)$($customPolicy.UpdatedBy)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + +"@) + $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = $null + $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = foreach ($customPolicyOrphaned in $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { + @" + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustomPoliciesOrphandedTenantRoot) + [void]$htmlTenantSummary.AppendLine(@" + +
    Policy DisplayNamePolicyId
    $($customPolicyOrphaned.DisplayName)$($customPolicyOrphaned.PolicyDefinitionId)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + +"@) + $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = $null + $htmlSUMMARYCustomPoliciesOrphandedTenantRoot = foreach ($customPolicyOrphaned in $arrayCustomPoliciesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { + @" + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustomPoliciesOrphandedTenantRoot) + [void]$htmlTenantSummary.AppendLine(@" + +
    Policy DisplayNamePolicyId
    $($customPolicyOrphaned.DisplayName)$($customPolicyOrphaned.PolicyDefinitionId)
    +
    + +"@) + } + 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]@() + foreach ($policyPolicySet in $tenantPolicySet.PolicySetPolicyIds) { + $hlpPolicyDef = ($htCacheDefinitionsPolicy).($policyPolicySet) + + if ($hlpPolicyDef.Type -eq 'Builtin') { + $null = $policySetPoliciesArray.Add("$($hlpPolicyDef.LinkToAzAdvertizer) ($policyPolicySet)") + } + else { + 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 + } + + $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.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.ToString('yyyy-MM-dd HH:mm:ss') + } + 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.ToString('yyyy-MM-dd HH:mm:ss') + } + 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 + 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) + }) + + $null = $script:tenantPolicySetsDetailed.Add([PSCustomObject]@{ + Type = 'Custom' + ScopeMGLevel = $tenantPolicySet.ScopeMGLevel + Scope = $tenantPolicySet.ScopeMgSub + ScopeId = $tenantPolicySet.ScopeId + PolicySetDisplayName = $tenantPolicySet.DisplayName + PolicySetDefinitionName = $tenantPolicySet.PolicyDefinitionId -replace '.*/' + PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId + PolicySetCategory = $tenantPolicySet.Category + UniqueAssignmentsCount = $policySetUniqueAssignmentsCount + UniqueAssignments = $policySetUniqueAssignments + PoliciesUsedCount = $policySetPoliciesCount + PoliciesUsed = $policySetPoliciesArrayClean + PoliciesUsed4JSON = $policySetPoliciesArrayIdOnly + CreatedOn = $createdOn + CreatedBy = $createdBy + CreatedByJson = $createdByJson + UpdatedOn = $updatedOn + UpdatedBy = $updatedBy + UpdatedByJson = $updatedByJson + #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings) + Json = $tenantPolicySet.Json + }) + + } + else { + $null = $script:tenantPolicySetsDetailed.Add([PSCustomObject]@{ + Type = 'BuiltIn' + ScopeMGLevel = $null + Scope = $null + ScopeId = $null + PolicySetDisplayName = $tenantPolicySet.DisplayName + PolicySetDefinitionName = $tenantPolicySet.PolicyDefinitionId -replace '.*/' + PolicySetDefinitionId = $tenantPolicySet.PolicyDefinitionId + PolicySetCategory = $tenantPolicySet.Category + UniqueAssignmentsCount = $policySetUniqueAssignmentsCount + UniqueAssignments = $policySetUniqueAssignments + PoliciesUsedCount = $policySetPoliciesCount + PoliciesUsed = $policySetPoliciesArrayClean + PoliciesUsed4JSON = $policySetPoliciesArrayIdOnly + CreatedOn = '' + CreatedBy = '' + CreatedByJson = $null + UpdatedOn = '' + UpdatedBy = '' + UpdatedByJson = $null + #Json = [string]($tenantPolicySet.Json | ConvertTo-Json -Depth 99 -EnumsAsStrings) + Json = $tenantPolicySet.Json + }) + } + } + + if (-not $NoCsvExport) { + $csvFilename = "$($filename)_PolicySetDefinitions" + Write-Host " Exporting PolicySetDefinitions CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'" + $tenantPolicySetsDetailed | Select-Object -ExcludeProperty UniqueAssignments, PoliciesUsed, CreatedByJson, UpdatedByJson, Json | Sort-Object -Property Type, Scope, PolicySetDefinitionId | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + +"@) + $htmlSUMMARYtenanttotalcustompolicySets = $null + $htmlSUMMARYtenanttotalcustompolicySets = foreach ($customPolicySet in $customPolicySetsDetailed | Sort-Object @{Expression = { $_.Scope } }, @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { + @" + + + + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustompolicySets) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeScopeIdPolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies used in PolicySetCreatedOnCreatedByUpdatedOnUpdatedBy
    $($customPolicySet.Scope)$($customPolicySet.ScopeId)$($customPolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')$($customPolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')$($customPolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicySet.PoliciesUsed)$($customPolicySet.CreatedOn)$($customPolicySet.CreatedBy)$($customPolicySet.UpdatedOn)$($customPolicySet.UpdatedBy)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + +"@) + $htmlSUMMARYtenanttotalcustompolicySets = $null + $htmlSUMMARYtenanttotalcustompolicySets = foreach ($customPolicySet in $customPolicySetsDetailed | Sort-Object @{Expression = { $_.Scope } }, @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { + @" + + + + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustompolicySets) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeScope IdPolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies used in PolicySetCreatedOnCreatedByUpdatedOnUpdatedBy
    $($customPolicySet.Scope)$($customPolicySet.ScopeId)$($customPolicySet.PolicySetDisplayName -replace '<', '<' -replace '>', '>')$($customPolicySet.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')$($customPolicySet.PolicySetCategory -replace '<', '<' -replace '>', '>')$($customPolicySet.UniqueAssignments -replace '<', '<' -replace '>', '>')$($customPolicySet.PoliciesUsed)$($customPolicySet.CreatedOn)$($customPolicySet.CreatedBy)$($customPolicySet.UpdatedOn)$($customPolicySet.UpdatedBy)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + +"@) + $htmlSUMMARYCustompolicySetOrphandedTenantRoot = $null + $htmlSUMMARYCustompolicySetOrphandedTenantRoot = foreach ($custompolicySetOrphaned in $arraycustompolicySetSetsOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { + @" + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustompolicySetOrphandedTenantRoot) + [void]$htmlTenantSummary.AppendLine(@" + +
    PolicySet DisplayNamePolicySetId
    $($custompolicySetOrphaned.DisplayName)$($custompolicySetOrphaned.PolicyDefinitionId)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + +"@) + $htmlSUMMARYCustompolicySetOrphandedTenantRoot = $null + $htmlSUMMARYCustompolicySetOrphandedTenantRoot = foreach ($custompolicySetOrphaned in $arraycustompolicySetsOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.PolicyDefinitionId } }, @{Expression = { $_.DisplayName } }) { + @" + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYCustompolicySetOrphandedTenantRoot) + [void]$htmlTenantSummary.AppendLine(@" + +
    PolicySet DisplayNamePolicySetId
    $($custompolicySetOrphaned.DisplayName)$($custompolicySetOrphaned.policyDefinitionId)
    +
    + +"@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $(($arraycustompolicySetsOrphanedFinalIncludingResourceGroups).count) Orphaned Custom PolicySet definitions ($scopeNamingSummary)

    +"@) + } + } + #endregion SUMMARYCustompolicySetOrphandedTenantRoot + + $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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $htmlSUMMARYPolicySetsDeprecatedPolicy = $null + $htmlSUMMARYPolicySetsDeprecatedPolicy = foreach ($policySetDeprecated in $policySetsDeprecated | Sort-Object @{Expression = { $_.PolicySetDisplayName } }, @{Expression = { $_.PolicySetDefinitionId } }) { + + if ($policySetDeprecated.DeprecatedProperty -eq $true) { + $deprecatedProperty = 'true' + } + else { + $deprecatedProperty = 'false' + } + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicySetsDeprecatedPolicy) + [void]$htmlTenantSummary.AppendLine(@" + +
    PolicySet DisplayNamePolicySetIdPolicy DisplayNamePolicyIdDeprecated Property
    $($policySetDeprecated.PolicySetDisplayName -replace '<', '<' -replace '>', '>')$($policySetDeprecated.PolicySetDefinitionId -replace '<', '<' -replace '>', '>')$($policySetDeprecated.PolicyDisplayName -replace '<', '<' -replace '>', '>')$($policySetDeprecated.PolicyId -replace '<', '<' -replace '>', '>')$deprecatedProperty
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + +"@) + $htmlSUMMARYPolicyAssignmentsDeprecatedPolicy = $null + $htmlSUMMARYPolicyAssignmentsDeprecatedPolicy = foreach ($policyAssignmentDeprecated in $policyAssignmentsDeprecated | Sort-Object @{Expression = { $_.PolicyAssignmentDisplayName } }, @{Expression = { $_.PolicyAssignmentId } }) { + @" + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyAssignmentsDeprecatedPolicy) + [void]$htmlTenantSummary.AppendLine(@" + +
    Policy Assignment DisplayNamePolicy AssignmentIdPolicy/PolicySetPolicySet DisplayNamePolicySetIdPolicy DisplayNamePolicyIdDeprecated Property
    $($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)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + +"@) + + $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 = '' + } + + if (-not $NoCsvExport) { + $null = $exemptionData4CSVExport.Add([PSCustomObject]@{ + Scope = $exemptionScope + ManagementGroupId = $mgId + ManagementGroupName = $mgName + SubscriptionId = $subId + SubscriptionName = $subName + ResourceGroup = $rgName + ResourceName_ResourceType = $resName + DisplayName = $exemption.properties.DisplayName + Category = $exemption.properties.exemptionCategory + ExpiresOn_UTC = $exemptionExpiresOn + Id = $exemption.Id + PolicyAssignmentId = $exemption.properties.policyAssignmentId + }) + } + + @" + + + + + + + + + + + + + + +"@ + } + + 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(@" + +
    ScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameResourceGroupResourceName / ResourceTypeDisplayNameCategoryExpiresOn (UTC)IdPolicy AssignmentId
    $($exemptionScope)$($mgId)$($mgName -replace '<', '<' -replace '>', '>')$($subId)$($subName)$($rgName)$($resName)$($exemption.properties.DisplayName -replace '<', '<' -replace '>', '>')$($exemption.properties.exemptionCategory -replace '<', '<' -replace '>', '>')$($exemptionExpiresOn)$($exemption.Id)$($exemption.properties.policyAssignmentId -replace '<', '<' -replace '>', '>')
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + +"@) + + $htmlSUMMARYPolicyassignmentsOrphaned = $null + $htmlSUMMARYPolicyassignmentsOrphaned = foreach ($orphanedPolicyAssignment in $policyAssignmentsOrphaned | Sort-Object -Property PolicyAssignmentId) { + @" + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYPolicyassignmentsOrphaned) + [void]$htmlTenantSummary.AppendLine(@" + +
    Policy AssignmentIdPolicy/Set definition
    $($orphanedPolicyAssignment.policyAssignmentId -replace '<', '<' -replace '>', '>')$($orphanedPolicyAssignment.PolicyDefinitionId -replace '<', '<' -replace '>', '>')
    +
    + +"@) + } + 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" + #if ($cnter % 5000 -eq 0) { + #[System.GC]::Collect() + #} + } + + #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 + 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 + 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 -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 AzGovViz - 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(@" + +
    + 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 -DoNotIncludeResourceGroupsAnsResourcesOnRBAC
    + Check the parameters documentation AzGovViz docs +
    +"@) + } + else { + + $htmlTableId = 'TenantSummary_policyAssignmentsAll' + $noteOrNot = '' + + [void]$htmlTenantSummary.AppendLine(@" + +
    + 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 + + + + + + + + + + + + + + + + + + + + + +"@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + + + + + +'@) + } + + [void]$htmlTenantSummary.AppendLine(@" + + + + + + + + + + + + + +"@) + + $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 + } + @" + + + + + + + + + + + + + + + + + + + +"@ + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + @" + + + + + +"@ + } + + @" + + + + + + + + + + + +"@ + } + + $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" + #[System.GC]::Collect() + + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameInheritanceScopeExcludedExemption appliesPolicy/Set DisplayNamePolicy/Set DescriptionPolicy/SetIdPolicy/SetTypeCategoryEffectParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotManaged IdentityAssignment DisplayNameAssignment DescriptionAssignmentIdAssignedByCreatedOnCreatedByUpdatedOnUpdatedBy
    $($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 -replace '<', '<' -replace '>', '>')$($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)
    +
    + +"@) + } + } + 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 + + [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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + +"@) + $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' + } + } + @" + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtenanttotalcustomroles) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdAssignable ScopesDataCreatedOnCreatedByUpdatedOnUpdatedBy
    $($cachedTenantCustomRole.Name -replace '<', '<' -replace '>', '>')$($cachedTenantCustomRole.Id)$(($cachedTenantCustomRole.AssignableScopes).count) ($($cachedTenantCustomRole.AssignableScopes -join "$CsvDelimiterOpposite "))$($roleManageData)$createdOnFormated$createdBy$updatedOnFormated$updatedByRemoveNoiseOrNot
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYOrphanedCustomRoles = $null + $htmlSUMMARYOrphanedCustomRoles = foreach ($customRoleOrphaned in $arrayCustomRolesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.Name } }) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedCustomRoles) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdAssignable Scopes
    $($customRoleOrphaned.Name -replace '<', '<' -replace '>', '>')$($customRoleOrphaned.Id)$(($customRoleOrphaned.AssignableScopes).count) ($($customRoleOrphaned.AssignableScopes -join "$CsvDelimiterOpposite "))
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYOrphanedCustomRoles = $null + $htmlSUMMARYOrphanedCustomRoles = foreach ($inScopeCustomRole in $arrayCustomRolesOrphanedFinalIncludingResourceGroups | Sort-Object @{Expression = { $_.Name } }) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedCustomRoles) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdRole Assignable Scopes
    $($inScopeCustomRole.Name -replace '<', '<' -replace '>', '>')$($inScopeCustomRole.Id)$(($inScopeCustomRole.AssignableScopes).count) ($($inScopeCustomRole.AssignableScopes -join "$CsvDelimiterOpposite "))
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + $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 += @" + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOrphanedRoleAssignments) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role AssignmentIdRole NameRoleIdImpacted Mg/Sub
    $($roleAssignmentOrphanedUnique.RoleAssignmentId)$($roleAssignmentOrphanedUnique.RoleDefinitionName -replace '<', '<' -replace '>', '>')$($roleAssignmentOrphanedUnique.RoleDefinitionId)Mg: $(($impactedMgs).count); Sub: $(($impactedSubs).count)
    +
    + +"@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $(($roleAssignmentsOrphanedUnique).count) Orphaned Role assignments ($scopeNamingSummary)

    +"@) + } + #endregion SUMMARYOrphanedRoleAssignments + + #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 + } + else { + $rbacAllCount = $rbacAll.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(@" + +
    + 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 -DoNotIncludeResourceGroupsAnsResourcesOnRBAC
    + Check the parameters documentation AzGovViz docs +
    +"@) + } + else { + $htmlTableId = 'TenantSummary_roleAssignmentsAll' + $noteOrNot = '' + [void]$htmlTenantSummary.AppendLine(@" + +
    + 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(@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"@) + $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() + #[System.GC]::Collect() + } + } + + if ($roleAssignment.RoleType -eq 'Custom') { + $roleName = ($roleAssignment.Role -replace '<', '<' -replace '>', '>') + } + else { + $roleName = $roleAssignment.Role + } + + [void]$htmlSummaryRoleAssignmentsAll.AppendFormat( + @' + + + + + + + + + + + + + + + + + + + + + + + + + + + + +'@, $roleAssignment.TenOrMgOrSubOrRGOrRes, + $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(@" + +
    ScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameAssignment ScopeRoleRole IdRole TypeDataCan do Role assignmentIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsPIMPIM assignment typePIM startPIM endRole AssignmentIdRelated Policy Assignment $noteOrNotCreatedOnCreatedBy
    {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}
    +
    + +"@) + + } + } + 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 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + $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 += @" + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityCustomRoles) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdRole assignmentsAssignable Scopes
    $($customRole.Name -replace '<', '<' -replace '>', '>')$($customRole.Id)$($customRoleRoleAssignmentsOutput)$(($customRole.AssignableScopes).count) ($($customRole.AssignableScopes -join "$CsvDelimiterOpposite "))
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $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 += @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityRolesCanDoRoleAssignments) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdTypeRole assignmentsAssignable Scopes
    $($role.Name -replace '<', '<' -replace '>', '>')$($role.Id)$($roleType)$($roleAssignments)$($roleAssignableScopes)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $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 + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityOwnerAssignmentSP) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdRole AssignmentServicePrincipal (ObjId)Impacted Mg/Sub
    $($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)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + +"@) + $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) }) + @" + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityOwnerAssignmentNotGroup) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdImpacted Mg/Sub
    $($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)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + +"@) + $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) }) + @" + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityUserAccessAdministratorAssignmentNotGroup) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdImpacted Mg/Sub
    $($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)
    +
    + +"@) + } + 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 priviledged Guest User)' + $highPriviledgedGuestUserRoleAssignments = $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 + $highPriviledgedGuestUserRoleAssignmentsCount = ($highPriviledgedGuestUserRoleAssignments).Count + if ($highPriviledgedGuestUserRoleAssignmentsCount -gt 0) { + $tfCount = $highPriviledgedGuestUserRoleAssignmentsCount + $htmlTableId = 'TenantSummary_SecurityGuestUserHighPriviledgesAssignments' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + +"@) + $htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments = $null + $htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments = foreach ($highPriviledgedGuestUserRoleAssignment in ($highPriviledgedGuestUserRoleAssignments)) { + if ($highPriviledgedGuestUserRoleAssignment.AssignmentType -eq 'indirect') { + $assignmentInfo = "indirect / AAD Group Membership '$($highPriviledgedGuestUserRoleAssignment.AssignmentInheritFrom)'" + } + else { + $assignmentInfo = 'direct' + } + @" + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSecurityGuestUserHighPriviledgesAssignments) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdRole AssignmentObj TypeObj DisplayNameObj SignInNameObjIdAssignment direct/indirect
    $($highPriviledgedGuestUserRoleAssignment.Role <#-replace "<", "<" -replace ">", ">"#>)$($highPriviledgedGuestUserRoleAssignment.RoleId)$($highPriviledgedGuestUserRoleAssignment.RoleAssignmentId)$($highPriviledgedGuestUserRoleAssignment.ObjectType)$($highPriviledgedGuestUserRoleAssignment.ObjectDisplayName)$($highPriviledgedGuestUserRoleAssignment.ObjectSignInName)$($highPriviledgedGuestUserRoleAssignment.ObjectId)$assignmentInfo
    +
    + +"@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $($highPriviledgedGuestUserRoleAssignmentsCount) Guest Users with high permissions ($scopeNamingSummary)

    +"@) + } + $endSUMMARYSecurityGuestUserHighPriviledgesAssignments = Get-Date + Write-Host " TenantSummary RoleAssignments security (high priviledged 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + $htmlSUMMARYBlueprintDefinitions = $null + $htmlSUMMARYBlueprintDefinitions = foreach ($blueprintDefinition in $blueprintDefinitions | Sort-Object -Property BlueprintName, BlueprintDisplayName) { + @" + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintDefinitions) + [void]$htmlTenantSummary.AppendLine(@" + +
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintId
    $($blueprintDefinition.BlueprintName -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintDisplayName -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintDescription -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintId -replace '<', '<' -replace '>', '>')
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + +"@) + $htmlSUMMARYBlueprintAssignments = $null + $htmlSUMMARYBlueprintAssignments = foreach ($blueprintAssignment in $blueprintAssignments | Sort-Object -Property level, BlueprintAssignmentId) { + @" + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintAssignments) + [void]$htmlTenantSummary.AppendLine(@" + +
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintIdBlueprint VersionBlueprint AssignmentId
    $($blueprintAssignment.BlueprintName -replace '<', '<' -replace '>', '>')$($blueprintAssignment.BlueprintDisplayName -replace '<', '<' -replace '>', '>')$($blueprintAssignment.BlueprintDescription -replace '<', '<' -replace '>', '>')$($blueprintAssignment.BlueprintId -replace '<', '<' -replace '>', '>')$($blueprintAssignment.BlueprintAssignmentVersion)$($blueprintAssignment.BlueprintAssignmentId -replace '<', '<' -replace '>', '>')
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + $htmlSUMMARYBlueprintsOrphaned = $null + $htmlSUMMARYBlueprintsOrphaned = foreach ($blueprintDefinition in $blueprintDefinitionsOrphanedArray | Sort-Object -Property BlueprintId) { + @" + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYBlueprintsOrphaned) + [void]$htmlTenantSummary.AppendLine(@" + +
    Blueprint NameBlueprint DisplayNameBlueprint DescriptionBlueprintId
    $($blueprintDefinition.BlueprintName -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintDisplayName -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintDescription -replace '<', '<' -replace '>', '>')$($blueprintDefinition.BlueprintId -replace '<', '<' -replace '>', '>')
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + +'@) + } + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + [void]$htmlTenantSummary.AppendLine(@" + +"@) + } + [void]$htmlTenantSummary.AppendLine(@' + + + + +'@) + $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' + } + } + + @" + + + + + + + + +"@ + if ($azAPICallConf['htParameters'].NoMDfCSecureScore -eq $false) { + @" + +"@ + } + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + @" + +"@ + } + @" + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYManagementGroups) + [void]$htmlTenantSummary.AppendLine(@" + +
    LevelManagementGroupManagementGroup IdMg children (total)Mg children (direct)Sub children (total)Sub children (direct)MG MDfC ScoreCost ($($AzureConsumptionPeriod)d)Path
    $($summaryManagementGroup.level)$($summaryManagementGroup.mgName -replace '<', '<' -replace '>', '>')$($summaryManagementGroup.mgId)$($mgAllChildMgsCountTotal)$($mgAllChildMgsCountDirect)$($mgAllChildSubscriptionsCountTotal)$($mgAllChildSubscriptionsCountDirect)$($mgSecureScore)$($arrayTotalCostSummaryMgSummary -join ', ')$($pathhlper)
    +
    + + +"@) + } + 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 tenantSummarySubscriptions + [void]$htmlTenantSummary.AppendLine(@' + +
    +'@) + + #region SUMMARYSubs + Write-Host ' processing TenantSummary Subscriptions' + $summarySubscriptions = $optimizedTableForPathQueryMgAndSub | Sort-Object -Property Subscription + $summarySubscriptionsCount = ($summarySubscriptions).Count + if ($summarySubscriptionsCount -gt 0) { + $tfCount = $summarySubscriptionsCount + $htmlTableId = 'TenantSummary_subs' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Supported Microsoft Azure offers docs
    + Understand Microsoft Defender for Cloud Secure Score Video , Blog , docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + [void]$htmlTenantSummary.AppendLine(@" + + +"@) + } + [void]$htmlTenantSummary.AppendLine(@' + + + + +'@) + $htmlSUMMARYSubs = $null + $htmlSUMMARYSubs = foreach ($summarySubscription in $summarySubscriptions) { + $subPath = $htSubscriptionsMgPath.($summarySubscription.subscriptionId).pathDelimited + $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 ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) { + @" + + +"@ + } + @" + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubs) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdQuotaIdRole assignment limitTagsSub MDfC ScoreCost ($($AzureConsumptionPeriod)d)CurrencyPath
    $($summarySubscription.subscription -replace '<', '<' -replace '>', '>')$($summarySubscription.subscriptionId)$($summarySubscription.SubscriptionQuotaId)$($htSubscriptionsRoleAssignmentLimit.($summarySubscription.subscriptionId))$(($subscriptionTagsArray | Sort-Object) -join "$CsvDelimiterOpposite ")$($summarySubscription.SubscriptionASCSecureScore)$totalCost$currency $subPath
    +
    + + +"@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $($summarySubscriptionsCount) Subscriptions

    +"@) + } + #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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + $htmlSUMMARYOutOfScopeSubscriptions = $null + $htmlSUMMARYOutOfScopeSubscriptions = foreach ($outOfScopeSubscription in $outOfScopeSubscriptions) { + @" + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYOutOfScopeSubscriptions) + [void]$htmlTenantSummary.AppendLine(@" + +
    Subscription NameSubscriptionIdout-of-scope reasonManagement Group
    $($outOfScopeSubscription.SubscriptionName)$($outOfScopeSubscription.SubscriptionId)$($outOfScopeSubscription.outOfScopeReason) $($outOfScopeSubscription.ManagementGroupName -replace '<', '<' -replace '>', '>') ($($outOfScopeSubscription.ManagementGroupId))
    +
    + +"@) + } + 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(@" + +
    + Resource naming and tagging decision guide docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYtagsUsage = $null + $htmlSUMMARYtagsUsage = foreach ($tagEntry in $arrayTagList | Sort-Object -Property Scope, TagName -CaseSensitive) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYtagsUsage) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeTagNameCount
    $($tagEntry.Scope)$($tagEntry.TagName)$($tagEntry.TagCount)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + +"@) + $htmlSUMMARYResources = $null + $htmlSUMMARYResources = foreach ($resourceAllSummarized in $resourcesAllGroupedByType) { + $type = $resourceAllSummarized.Name + $script:htDailySummary."ResourceType_$($resourceAllSummarized.Name)" = ($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum + @" + + + + +"@ + + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResources) + [void]$htmlTenantSummary.AppendLine(@" + +
    ResourceTypeResource Count
    $($type)$(($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum)
    +
    + +"@) + } + 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 + } + + if ($azAPICallConf['htParameters'].NoResources -eq $false) { + #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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYResources = $null + $htmlSUMMARYResources = foreach ($resourceAllSummarized in $resourcesAllGroupedByTypeLocation) { + $typeLocation = $resourceAllSummarized.Name.Split(', ') + $type = $typeLocation[0] + $location = $typeLocation[1] + @" + + + + + +"@ + + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResources) + [void]$htmlTenantSummary.AppendLine(@" + +
    ResourceTypeLocationResource Count
    $($type)$($location)$(($resourceAllSummarized.group.count_ | Measure-Object -Sum).Sum)
    +
    + +"@) + } + 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 SUMMARYSubResourceProviders + $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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $htmlSUMMARYSubResourceProviders = $null + $htmlSUMMARYSubResourceProviders = foreach ($provider in ($providerSummary | Sort-Object -Property Provider)) { + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubResourceProviders) + [void]$htmlTenantSummary.AppendLine(@" + +
    ProviderRegisteredRegisteringNotRegisteredUnregistering
    $($provider.Provider)$($provider.Registered)$($provider.Registering)$($provider.NotRegistered)$($provider.Unregistering)
    +
    + +"@) + } + 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'].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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + + } + 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 + }) + } + + @" + + + + + + + +"@ + } + } + + #region exportCSV + if (-not $NoCsvExport) { + $csvFilename = "$($filename)_ResourceProviders" + Write-Host " Exporting ResourceProviders CSV '$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv'" + $arrayResourceProvidersDetailedForCSVExport | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($csvFilename).csv" -Delimiter $csvDelimiter -Encoding utf8 -NoTypeInformation + $arrayResourceProvidersDetailedForCSVExport = $null + } + #endregion exportCSV + + if ($tfCount -lt $HtmlTableRowsLimit) { + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubResourceProvidersDetailed) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdSubscription MG path +ProviderState
    $($subscriptionResProvDetails.DisplayName)$($subscriptionResProv)$($subscriptionResProvDetails.pathDelimited)$($provider.namespace)$($provider.registrationState)
    +
    + +"@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" + +
    + 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 AzGovViz 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 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(@" + +
    + Considerations before applying locks docs + + + + + + + + + + + + + + + + +
    Lock scopeLock typepresence
    SubscriptionCannotDelete$($subscriptionLocksCannotDeleteCount) of $totalSubCount Subscriptions
    SubscriptionReadOnly$($subscriptionLocksReadOnlyCount) of $totalSubCount Subscriptions
    ResourceGroupCannotDelete$($resourceGroupsLocksCannotDeleteCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourceGroupsLocksCannotDeleteCount | Measure-Object -Sum).Sum))
    ResourceGroupReadOnly$($resourceGroupsLocksReadOnlyCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourceGroupsLocksReadOnlyCount | Measure-Object -Sum).Sum))
    ResourceCannotDelete$($resourcesLocksCannotDeleteCount) of $totalSubCount Subscriptions (total: $(($htResourceLocks.Values.ResourcesLocksCannotDeleteCount | Measure-Object -Sum).Sum))
    ResourceReadOnly$($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 + + #SUMMARYSubDefenderPlansSubscriptionNotRegistered + if ($arrayDefenderPlansSubscriptionNotRegistered.Count -gt 0) { + #region SUMMARYSubDefenderPlansSubscriptionNotRegistered + Write-Host ' processing TenantSummary Subscriptions Microsoft Defender for Cloud plans SubscriptionNotRegistered' + + $tfCount = $defenderPlansGroupedByPlanCount + $startDefenderPlans = Get-Date + + $htmlTableId = 'TenantSummary_DefenderPlansSubscriptionNotRegistered' + + [void]$htmlTenantSummary.AppendLine(@" + +
    + Register Resource Provider 'Microsoft.Security' docs
    + Microsoft Defender for Cloud's enhanced security features docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + + foreach ($subscription in $arrayDefenderPlansSubscriptionNotRegistered | Sort-Object -Property subscriptionName) { + [void]$htmlTenantSummary.AppendLine(@" + + + + + +"@) + } + + [void]$htmlTenantSummary.AppendLine(@" + +
    Subscription NameSubscription IdSubscription MG path
    $($subscription.subscriptionName)$($subscription.subscriptionId)$($subscription.subscriptionMgPath)
    + +
    +"@) + + $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 SUMMARYSubDefenderPlansSubscriptionNotRegistered + } + + #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(@" + +
    +"@) + + 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 + + + + + + + + +"@) + + 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(@" + + + + +"@) + } + + [void]$htmlTenantSummary.AppendLine(@" + +
    Plan/TierSubscription Count
    $($thisDefenderPlan)$($defenderCapabilityAndTier.Count)
    + +
    +"@) + } + 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(@" + +
    +"@) + + 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 + + + + + + +"@) + + foreach ($defenderCapability in $defenderCapabilities) { + if (($defenderPlanDeprecatedContainerRegistry -and $defenderCapability -eq 'ContainerRegistry') -or ($defenderPlanDeprecatedKubernetesService -and $defenderCapability -eq 'KubernetesService')) { + $thisDefenderCapability = " $($defenderCapability)" + } + else { + $thisDefenderCapability = $defenderCapability + } + [void]$htmlTenantSummary.AppendLine(@" + +"@) + + } + + [void]$htmlTenantSummary.AppendLine(@' + + + +'@) + + foreach ($sub in $defenderPlansGroupedBySub) { + $nameSplit = $sub.Name.split(', ') + [void]$htmlTenantSummary.AppendLine(@" + + + + + +"@) + + foreach ($plan in $sub.Group | Sort-Object -Property defenderPlan) { + [void]$htmlTenantSummary.AppendLine(@" + +"@) + } + [void]$htmlTenantSummary.AppendLine(@' + +'@) + } + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdSubscription MG path$($thisDefenderCapability)
    $($nameSplit[0])$($nameSplit[1])$($nameSplit[2])$($plan.defenderPlanTier)
    + +
    +"@) + } + 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(@" + +
    + Managed identity 'user-assigned' vs 'system-assigned' docs
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + + +"@) + + [void]$htmlTenantSummary.AppendLine(@' + + + +'@) + + $userAssignedIdentities4Resources4CSVExport = [System.Collections.ArrayList]@() + foreach ($miResEntry in $arrayUserAssignedIdentities4Resources | Sort-Object -Property miResourceId, resourceId) { + [void]$htmlTenantSummary.AppendLine(@" + + + + + + + + + + + + + + + + + + + +"@) + + 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 + 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, resourceId | Export-Csv -Path "$($outputPath)$($DirectorySeparatorChar)$($fileName)_UserAssignedIdentities4Resources.csv" -Delimiter "$csvDelimiter" -NoTypeInformation + } + + [void]$htmlTenantSummary.AppendLine(@" + +
    MI NameMI MgPathMI Subscription NameMI Subscription IdMI ResourceGroupMI ResourceIdMI AAD SP objectIdMI AAD SP applicationIdMI count Res assignmentsRes NameRes TypeRes MgPathRes Subscription NameRes Subscription IdRes ResourceGroupRes IdRes count assigned MIs
    $($miResEntry.miResourceName)$($miResEntry.miMgPath)$($miResEntry.miSubscriptionName)$($miResEntry.miSubscriptionId)$($miResEntry.miResourceGroupName)$($miResEntry.miResourceId)$($miResEntry.miPrincipalId)$($miResEntry.miClientId)$($htUserAssignedIdentitiesAssignedResources.($miResEntry.miPrincipalId).ResourcesCount)$($miResEntry.resourceName)$($miResEntry.resourceType)$($miResEntry.resourceMgPath)$($miResEntry.resourceSubscriptionName)$($miResEntry.resourceSubscriptionId)$($miResEntry.resourceResourceGroupName)$($miResEntry.resourceId)$($htResourcesAssignedUserAssignedIdentities.(($miResEntry.resourceId).tolower()).UserAssignedIdentitiesCount)
    + +
    +"@) + } + 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 + } + + [void]$htmlTenantSummary.AppendLine(@' +
    +'@) + #endregion tenantSummarySubscriptions + + 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(@" + +
    + Management Group Diagnostic Settings - Create Or Update - REST API docs
    + Download CSV semicolon | comma + + + + + + + + + + +"@) + + foreach ($logCategory in $diagnosticSettingsMgCategories) { + [void]$htmlTenantSummary.AppendLine(@" + +"@) + } + + [void]$htmlTenantSummary.AppendLine(@' + + + +'@) + $htmlSUMMARYDiagnosticsManagementGroups = $null + $htmlSUMMARYDiagnosticsManagementGroups = foreach ($entry in $diagnosticSettingsMg | Sort-Object -Property ScopeMgPath, DiagnosticsInheritedFrom, DiagnosticSettingName, DiagnosticTargetType, DiagnosticTargetId) { + + @" + + + + + + + + +"@ + foreach ($logCategory in $diagnosticSettingsMgCategories) { + if ($entry.DiagnosticCategoriesHt.($logCategory)) { + @" + +"@ + } + else { + @' + +'@ + } + } + @' + +'@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYDiagnosticsManagementGroups) + [void]$htmlTenantSummary.AppendLine(@" + +
    Management Group NameManagement Group IdDiagnostic settingInheritanceInherited fromTargetTargetId$logCategory
    $($entry.ScopeName -replace '<', '<' -replace '>', '>')$($entry.ScopeId)$($entry.DiagnosticSettingName)$($entry.DiagnosticsInheritedOrnot)$($entry.DiagnosticsInheritedFrom)$($entry.DiagnosticTargetType)$($entry.DiagnosticTargetId)$($entry.DiagnosticCategoriesHt.($logCategory))n/a
    +
    + +"@) + } + 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(@" + +
    + Management Group Diagnostic Settings - Create Or Update - REST API docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYNoDiagnosticsManagementGroups = $null + $htmlSUMMARYNoDiagnosticsManagementGroups = foreach ($entry in $arrayMgsWithoutDiagnostics | Sort-Object -Property ScopeMgPath) { + + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNoDiagnosticsManagementGroups) + [void]$htmlTenantSummary.AppendLine(@" + +
    Management Group NameManagement Group IdManagement Group path
    $($entry.ScopeName -replace '<', '<' -replace '>', '>')$($entry.ScopeId)$($entry.ScopeMgPath)
    +
    + +"@) + } + 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(@" + +
    + Create diagnostic setting docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + + foreach ($logCategory in $diagnosticSettingsSubCategories) { + [void]$htmlTenantSummary.AppendLine(@" + +"@) + } + + [void]$htmlTenantSummary.AppendLine(@' + + + +'@) + $htmlSUMMARYDiagnosticsSubscriptions = $null + $htmlSUMMARYDiagnosticsSubscriptions = foreach ($entry in $diagnosticSettingsSub | Sort-Object -Property ScopeName, DiagnosticTargetType, DiagnosticSettingName) { + + @" + + + + + + + +"@ + foreach ($logCategory in $diagnosticSettingsSubCategories) { + if ($entry.DiagnosticCategoriesHt.($logCategory)) { + @" + +"@ + } + else { + @' + +'@ + } + } + @' + +'@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYDiagnosticsSubscriptions) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdPathDiagnostic settingTargetTargetId$logCategory
    $($entry.ScopeName -replace '<', '<' -replace '>', '>')$($entry.ScopeId) $($entry.ScopeMgPath)$($entry.DiagnosticSettingName)$($entry.DiagnosticTargetType)$($entry.DiagnosticTargetId)$($entry.DiagnosticCategoriesHt.($logCategory))n/a
    +
    + +"@) + } + else { + + [void]$htmlTenantSummary.AppendLine(@' +

    No Subscriptions configured for Diagnostic settings docs

    +'@) + } + + #hasNoDiag + if ($diagnosticSettingsSubNoDiagCount -gt 0) { + $tfCount = $diagnosticSettingsSubNoDiagCount + $htmlTableId = 'TenantSummary_NoDiagnosticsSubscriptions' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Create diagnostic setting docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYNoDiagnosticsSubscriptions = $null + $htmlSUMMARYNoDiagnosticsSubscriptions = foreach ($entry in $diagnosticSettingsSubNoDiag | Sort-Object -Property ScopeMgPath) { + + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNoDiagnosticsSubscriptions) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscription IdSubscription Mg path
    $($entry.ScopeName -replace '<', '<' -replace '>', '>')$($entry.ScopeId)$($entry.ScopeMgPath)
    +
    + +"@) + } + 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(@" + +
    + 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 + + + + + + + + + + + + +"@) + $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 + } + } + @" + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYResourcesDiagnosticsCapable) + [void]$htmlTenantSummary.AppendLine(@" + +
    ResourceTypeResource CountDiagnostics capableMetricsLogsLogCategories
    $($resourceType.ResourceType)$($resourceType.ResourceCount)$diagnosticsCapable$($resourceType.Metrics)$($resourceType.Logs)$($resourceType.LogCategories -join "$CsvDelimiterOpposite ")
    +
    + +"@) + } + 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 = 'AzGovViz 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 + 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 | Measure-Object).count + + if ($diagnosticsPolicyAnalysisCount -gt 0) { + $tfCount = $diagnosticsPolicyAnalysisCount + + $htmlTableId = 'TenantSummary_DiagnosticsLifecycle' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Create Custom Policies for Azure ResourceTypes that support Diagnostics Logs and Metrics Create-AzDiagPolicy
    + Supported categories for Azure Resource Logs docs + + + + + + + + + + + + + + + + + + + +"@) + + foreach ($diagnosticsFinding in $diagnosticsPolicyAnalysis | Sort-Object -property @{Expression = { $_.Priority } }, @{Expression = { $_.Recommendation } }, @{Expression = { $_.ResourceType } }, @{Expression = { $_.PolicyName } }, @{Expression = { $_.PolicyId } }) { + [void]$htmlTenantSummary.AppendLine(@" + + + + + + + + + + + + + + + +"@) + } + [void]$htmlTenantSummary.AppendLine(@" + +
    PriorityRecommendationResourceTypeResource CountDiagnostics capable (logs)Policy IdPolicy DisplayNameRole definitionsTargetLog Categories not covered by PolicyPolicy assignmentsPolicy used in PolicySetPolicySet assignments
    + $($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) +
    +
    + +"@) + } + 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(@" + +
    + Azure Policy Limits docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYMgsapproachingLimitsPolicyAssignments = $null + $htmlSUMMARYMgsapproachingLimitsPolicyAssignments = foreach ($mgApproachingLimitPolicyAssignments in $mgsApproachingLimitPolicyAssignments) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicyAssignments) + [void]$htmlTenantSummary.AppendLine(@" + +
    Management Group NameManagement Group IdLimit
    $($mgApproachingLimitPolicyAssignments.MgName -replace '<', '<' -replace '>', '>')$($mgApproachingLimitPolicyAssignments.MgId)$(($mgApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount/$LimitPOLICYPolicyAssignmentsManagementGroup).tostring('P')) ($($mgApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount)/$($LimitPOLICYPolicyAssignmentsManagementGroup)) ($($mgApproachingLimitPolicyAssignments.PolicyAssignmentAtScopeCount) Policy assignments, $($mgApproachingLimitPolicyAssignments.PolicySetAssignmentAtScopeCount) PolicySet assignments)
    +
    + +"@) + } + 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(@" + +
    + Azure Policy Limits docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYMgsapproachingLimitsPolicyScope = $null + $htmlSUMMARYMgsapproachingLimitsPolicyScope = foreach ($mgApproachingLimitPolicyScope in $mgsApproachingLimitPolicyScope) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicyScope) + [void]$htmlTenantSummary.AppendLine(@" + +
    Management Group NameManagement Group IdLimit
    $($mgApproachingLimitPolicyScope.MgName -replace '<', '<' -replace '>', '>')$($mgApproachingLimitPolicyScope.MgId)$(($mgApproachingLimitPolicyScope.PolicyDefinitionsScopedCount/$LimitPOLICYPolicyDefinitionsScopedManagementGroup).tostring('P')) $($mgApproachingLimitPolicyScope.PolicyDefinitionsScopedCount)/$($LimitPOLICYPolicyDefinitionsScopedManagementGroup)
    +
    + +"@) + } + 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(@" + +
    + Azure Policy Limits docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYMgsapproachingLimitsPolicySetScope = $null + $htmlSUMMARYMgsapproachingLimitsPolicySetScope = foreach ($mgApproachingLimitPolicySetScope in $mgsApproachingLimitPolicySetScope) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsPolicySetScope) + [void]$htmlTenantSummary.AppendLine(@" + +
    Management Group NameManagement Group IdLimit
    $($mgApproachingLimitPolicySetScope.MgName -replace '<', '<' -replace '>', '>')$($mgApproachingLimitPolicySetScope.MgId)$(($mgApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount/$LimitPOLICYPolicySetDefinitionsScopedManagementGroup).tostring('P')) ($($mgApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount)/$($LimitPOLICYPolicySetDefinitionsScopedManagementGroup))
    +
    + +"@) + } + 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(@" + +
    + Azure RBAC Limits docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYMgsapproachingLimitsRoleAssignment = $null + $htmlSUMMARYMgsapproachingLimitsRoleAssignment = foreach ($mgApproachingRoleAssignmentLimit in $mgsApproachingRoleAssignmentLimit) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYMgsapproachingLimitsRoleAssignment) + [void]$htmlTenantSummary.AppendLine(@" + +
    Management Group NameManagement Group IdLimit
    $($mgApproachingRoleAssignmentLimit.MgName -replace '<', '<' -replace '>', '>')$($mgApproachingRoleAssignmentLimit.MgId)$(($mgApproachingRoleAssignmentLimit.RoleAssignmentsCount/$LimitRBACRoleAssignmentsManagementGroup).tostring('P')) ($($mgApproachingRoleAssignmentLimit.RoleAssignmentsCount)/$($LimitRBACRoleAssignmentsManagementGroup))
    +
    + +"@) + } + 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(@" + +
    + Azure Subscription Resource Group Limit docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYSubsapproachingLimitsResourceGroups = $null + $htmlSUMMARYSubsapproachingLimitsResourceGroups = foreach ($subscriptionApproachingLimitFromResourceGroupsAll in $subscriptionsApproachingLimitFromResourceGroupsAll) { + $subscriptionData = $htSubDetails.($subscriptionApproachingLimitFromResourceGroupsAll.subscriptionId).details + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsResourceGroups) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdLimit
    $($subscriptionData.subscription -replace '<', '<' -replace '>', '>')$($subscriptionData.subscriptionId)$(($subscriptionApproachingLimitFromResourceGroupsAll.count_/$LimitResourceGroups).tostring('P')) ($($subscriptionApproachingLimitFromResourceGroupsAll.count_)/$($LimitResourceGroups))
    +
    + +"@) + } + 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(@" + +
    + Azure Subscription Tag Limit docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYSubsapproachingLimitsSubscriptionTags = $null + $htmlSUMMARYSubsapproachingLimitsSubscriptionTags = foreach ($subscriptionApproachingLimitTags in $subscriptionsApproachingLimitTags) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsSubscriptionTags) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdLimit
    $($subscriptionApproachingLimitTags.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingLimitTags.subscriptionId)$(($subscriptionApproachingLimitTags.SubscriptionTagsCount/$LimitTagsSubscription).tostring('P')) ($($subscriptionApproachingLimitTags.SubscriptionTagsCount)/$($LimitTagsSubscription))
    +
    + +"@) + } + 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(@" + +
    + Azure Policy Limits docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYSubsapproachingLimitsPolicyAssignments = $null + $htmlSUMMARYSubsapproachingLimitsPolicyAssignments = foreach ($subscriptionApproachingLimitPolicyAssignments in $subscriptionsApproachingLimitPolicyAssignments) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicyAssignments) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdLimit
    $($subscriptionApproachingLimitPolicyAssignments.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingLimitPolicyAssignments.subscriptionId)$(($subscriptionApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount/$subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentLimit).tostring('P')) ($($subscriptionApproachingLimitPolicyAssignments.PolicyAndPolicySetAssignmentAtScopeCount)/$($subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentLimit)) ($($subscriptionApproachingLimitPolicyAssignments.PolicyAssignmentAtScopeCount) Policy assignments, $($subscriptionApproachingLimitPolicyAssignments.PolicySetAssignmentAtScopeCount) PolicySet assignments)
    +
    + +"@) + } + 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(@" + +
    + Azure Policy Limits docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYSubsapproachingLimitsPolicyScope = $null + $htmlSUMMARYSubsapproachingLimitsPolicyScope = foreach ($subscriptionApproachingLimitPolicyScope in $subscriptionsApproachingLimitPolicyScope) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicyScope) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdLimit
    $($subscriptionApproachingLimitPolicyScope.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingLimitPolicyScope.subscriptionId)$(($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedCount/$subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedLimit).tostring('P')) ($($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedCount)/$($subscriptionApproachingLimitPolicyScope.PolicyDefinitionsScopedLimit))
    +
    + +"@) + } + 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(@" + +
    + Azure Policy Limits docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYSubsapproachingLimitsPolicySetScope = $null + $htmlSUMMARYSubsapproachingLimitsPolicySetScope = foreach ($subscriptionApproachingLimitPolicySetScope in $subscriptionsApproachingLimitPolicySetScope) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsPolicySetScope) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdLimit
    $($subscriptionApproachingLimitPolicySetScope.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingLimitPolicySetScope.subscriptionId)$(($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount/$subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedLimit).tostring('P')) ($($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedCount)/$($subscriptionApproachingLimitPolicySetScope.PolicySetDefinitionsScopedLimit))
    +
    + +"@) + } + 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(@" + +
    + Azure RBAC Limits docs
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $htmlSUMMARYSubsapproachingLimitsRoleAssignment = $null + $htmlSUMMARYSubsapproachingLimitsRoleAssignment = foreach ($subscriptionApproachingRoleAssignmentLimit in $subscriptionsApproachingRoleAssignmentLimit) { + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYSubsapproachingLimitsRoleAssignment) + [void]$htmlTenantSummary.AppendLine(@" + +
    SubscriptionSubscriptionIdLimit
    $($subscriptionApproachingRoleAssignmentLimit.subscription -replace '<', '<' -replace '>', '>')$($subscriptionApproachingRoleAssignmentLimit.subscriptionId)$(($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsCount/$subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsLimit).tostring('P')) ($($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsCount)/$($subscriptionApproachingRoleAssignmentLimit.RoleAssignmentsLimit))
    +
    + +"@) + } + 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 POC 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(@" + +
    + + + + + + + +"@) + $htmlSUMMARYAADSPNotFound = $null + $htmlSUMMARYAADSPNotFound = foreach ($serviceprincipal in $arrayServicePrincipalRequestResourceNotFound | Sort-Object) { + + @" + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPNotFound) + [void]$htmlTenantSummary.AppendLine(@" + +
    Service Principal Object Id
    $($serviceprincipal)
    +
    + +"@) + } + 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(@" + +
    + + + + + + + +"@) + $htmlSUMMARYAADAppNotFound = $null + $htmlSUMMARYAADAppNotFound = foreach ($app in $arrayApplicationRequestResourceNotFound | Sort-Object) { + + @" + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADAppNotFound) + [void]$htmlTenantSummary.AppendLine(@" + +
    Application (Client) Id
    $($app)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + +"@) + $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' + } + } + + @" + + + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPManagedIdentities) + [void]$htmlTenantSummary.AppendLine(@" + +
    ApplicationIdDisplayNameSP ObjectIdTypeUsageUsage infoPolicy assignment detailsRole assignmentsAssigned to resources$($abbr)Orphaned$($abbrOrphanedSPMI) +
    $($serviceprincipalMIDetailed.appId)$($serviceprincipalMIDetailed.displayName)$($serviceprincipalMI)$miType$miResourceType$($serviceprincipalMIDetailed.alternativeNames -join ', ')$($policyAssignmentMoreInfo)$($miRoleAssignments)$userMiAssignedToResourcesCount$orphanedMI
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + + + +"@) + $htmlSUMMARYAADSPCredExpiry = $null + $htmlSUMMARYAADSPCredExpiry = foreach ($serviceprincipalApp in $servicePrincipalsOfTypeApplication | Sort-Object) { + @" + + + + + + +"@ + if ($htAppDetails.$serviceprincipalApp.appPasswordCredentialsCount) { + @" + + + + + +"@ + } + else { + @' + + + + + +'@ + } + + if ($htAppDetails.$serviceprincipalApp.appKeyCredentialsCount) { + @" + + + + + +"@ + } + else { + @' + + + + + +'@ + } + + @' + +'@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPCredExpiry) + [void]$htmlTenantSummary.AppendLine(@" + +
    ApplicationIdDisplayNameNotesSP ObjectIdApp ObjectIdSecretsSecrets expiredSecrets expiry
    <$($AADServicePrincipalExpiryWarningDays)d
    Secrets expiry
    >$($AADServicePrincipalExpiryWarningDays)d & <2y
    Secrets expiry
    >2y
    CertsCerts expiredCerts expiry
    <$($AADServicePrincipalExpiryWarningDays)d
    Certs expiry
    >$($AADServicePrincipalExpiryWarningDays)d & <2y
    Certs expiry
    >2y
    $($htAppDetails.$serviceprincipalApp.spGraphDetails.appId)$($htAppDetails.$serviceprincipalApp.spGraphDetails.displayName)$($htAppDetails.$serviceprincipalApp.spGraphDetails.notes)$($htAppDetails.$serviceprincipalApp.spGraphDetails.Id)$($htAppDetails.$serviceprincipalApp.appGraphDetails.Id)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiredCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsGracePeriodExpiryCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiryOKCount)$($htAppDetails.$serviceprincipalApp.appPasswordCredentialsExpiryOKMoreThan2YearsCount)00000$($htAppDetails.$serviceprincipalApp.appKeyCredentialsCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiredCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsGracePeriodExpiryCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiryOKCount)$($htAppDetails.$serviceprincipalApp.appKeyCredentialsExpiryOKMoreThan2YearsCount)00000
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $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 ', '))" + } + + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYAADSPExternal) + [void]$htmlTenantSummary.AppendLine(@" + +
    ApplicationIdDisplayNameSP ObjectIdOrganizationIdRole assignments$($abbr)
    $($htServicePrincipals.($serviceprincipalApp).appId)$($htServicePrincipals.($serviceprincipalApp).displayName)$($htServicePrincipals.($serviceprincipalApp).id)$($htServicePrincipals.($serviceprincipalApp).appOwnerOrganizationId)$roleAssignments4ExternalApp
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + +"@) + $htmlSUMMARYConsumption = $null + $htmlSUMMARYConsumption = foreach ($consumptionLine in $arrayConsumptionData) { + @" + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYConsumption) + [void]$htmlTenantSummary.AppendLine(@" + +
    ChargeTypeResourceTypeCategoryResourceCountCost ($($AzureConsumptionPeriod)d)CurrencySubscriptions
    $($consumptionLine.ConsumedServiceChargeType)$($consumptionLine.ConsumedService)$($consumptionLine.ConsumedServiceCategory)$($consumptionLine.ConsumedServiceInstanceCount)$($consumptionLine.ConsumedServiceCost)$($consumptionLine.ConsumedServiceCurrency)$($consumptionLine.ConsumedServiceSubscriptions)
    +
    + +"@) + } + 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( { $_.TenOrMgOrSubOrRGOrRes -eq 'MG' -or $_.TenOrMgOrSubOrRGOrRes -eq 'Ten' }) + $roleAssignmentsCreatedMgCount = $roleAssignmentsCreatedMg.Count + #rbac assignments createdSub + $roleAssignmentsCreatedSub = $roleAssignmentsCreatedUnique.where( { $_.TenOrMgOrSubOrRGOrRes -eq 'Sub' }) + $roleAssignmentsCreatedSubCount = $roleAssignmentsCreatedSub.Count + if (-not $azAPICallConf['htParameters'].DoNotIncludeResourceGroupsAndResourcesOnRBAC) { + $roleAssignmentsCreatedSubRg = $roleAssignmentsCreatedUnique.where( { $_.TenOrMgOrSubOrRGOrRes -eq 'RG' }) + $roleAssignmentsCreatedSubRgCount = $roleAssignmentsCreatedSubRg.Count + $roleAssignmentsCreatedSubRgRes = $roleAssignmentsCreatedUnique.where( { $_.TenOrMgOrSubOrRGOrRes -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(@" + +
    +"@) + + #region ChangeTrackingCustomPolicy + if ($customPolicyCreatedOrUpdatedCount -gt 0) { + $tfCount = $customPolicyCreatedOrUpdatedCount + $htmlTableId = 'TenantSummary_ChangeTrackingCustomPolicy' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + + +"@) + $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) + } + + @" + + + + + + + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomPolicy) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeScope IdPolicy DisplayNamePolicyIdCategoryEffectRole definitionsUnique assignmentsUsed in PolicySetsCreated/UpdatedCreatedOnCreatedByUpdatedOnUpdatedBy
    $($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)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + +"@) + $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' + } + + @" + + + + + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomPolicySet) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeScopeIdPolicySet DisplayNamePolicySetIdCategoryUnique assignmentsPolicies used in PolicySetCreated/UpdatedCreatedOnCreatedByUpdatedOnUpdatedBy
    $($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)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + + + +"@) + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + [void]$htmlTenantSummary.AppendLine(@' + + + + + +'@) + } + + [void]$htmlTenantSummary.AppendLine(@" + + + + + + + + + + + + + +"@) + $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 + } + + @" + + + + + + + + + + + + + + + + + + + +"@ + + if ($azAPICallConf['htParameters'].NoPolicyComplianceStates -eq $false) { + @" + + + + + +"@ + } + + @" + + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingPolicyAssignments) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeManagement Group IdManagement Group NameSubscriptionIdSubscription NameInheritanceScopeExcludedExemption appliesPolicy/Set DisplayNamePolicy/Set DescriptionPolicy/SetIdPolicy/SetTypeCategoryEffectParametersEnforcementNonCompliance MessagePolicies NonCmplntPolicies CompliantResources NonCmplntResources CompliantResources ConflictingRole/Assignment $noteOrNotAssignment DisplayNameAssignment DescriptionAssignmentIdCreated/UpdatedAssignedByCreatedOnCreatedByUpdatedOnUpdatedBy
    $($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)$($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)
    +
    + +"@) + } + else { + [void]$htmlTenantSummary.AppendLine(@" +

    $policyAssignmentsCreatedOrUpdatedCount Created/Updated Policy assignments

    +"@) + } + #endregion ChangeTrackingPolicyAssignments + + [void]$htmlTenantSummary.AppendLine(@' +
    +'@) + + #endregion ctpolicy + + #region ctrbac + [void]$htmlTenantSummary.AppendLine(@" + +
    +"@) + + #region ChangeTrackingCustomRoles + if ($customRoleDefinitionsCreatedOrUpdatedCount -gt 0) { + $tfCount = $customRoleDefinitionsCreatedOrUpdatedCount + $htmlTableId = 'TenantSummary_ChangeTrackingCustomRoles' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + +"@) + $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 + } + } + + @" + + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingCustomRoles) + [void]$htmlTenantSummary.AppendLine(@" + +
    Role NameRoleIdAssignable ScopesDataCreated/UpdatedCreatedOnCreatedByUpdatedOnUpdatedBy
    $($entry.Name -replace '<', '<' -replace '>', '>')$($entry.Id)$(($entry.AssignableScopes | Measure-Object).count) ($($entry.AssignableScopes -join "$CsvDelimiterOpposite "))$($roleManageData)$createdOnUpdatedOn$createdOnFormated$createdBy$updatedOnFormated$updatedByRemoveNoiseOrNot
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + + + + + + + + + +"@) + $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( + @' + + + + + + + + + + + + + + + + + + +'@, $entry.TenOrMgOrSubOrRGOrRes, + $roleName, + $entry.RoleId, + $entry.RoleType, + $entry.RoleDataRelated, + $entry.ObjectDisplayName, + $entry.ObjectSignInName, + $entry.ObjectId, + $entry.ObjectType, + $entry.AssignmentType, + $entry.AssignmentInheritFrom, + $entry.GroupMembersCount, + $entry.RoleAssignmentId, + ($entry.RbacRelatedPolicyAssignment -replace '<', '<' -replace '>', '>'), + $entry.CreatedOn, + $entry.CreatedBy + ) + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingRoleAssignments) + [void]$htmlTenantSummary.AppendLine(@" + +
    ScopeRoleRole IdRole TypeDataIdentity DisplaynameIdentity SignInNameIdentity ObjectIdIdentity TypeApplicabilityApplies through membership Group DetailsRole AssignmentIdRelated Policy Assignment $noteOrNotCreatedOnCreatedBy
    {0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}
    +
    + +"@) + } + 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(@" + +
    +"@) + + #region ChangeTrackingResources + if ($resourcesCreatedOrChangedCount -gt 0) { + $tfCount = $resourcesCreatedOrChangedGroupedCount + $htmlTableId = 'TenantSummary_ChangeTrackingResources' + [void]$htmlTenantSummary.AppendLine(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + + + + +"@) + $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 + + @" + + + + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYChangeTrackingResources) + [void]$htmlTenantSummary.AppendLine(@" + +
    ResourceTypeResource CountCreated&ChangedCreated&Changed SubsCreatedCreated SubsChangedChanged Subs
    $($entry.Name)$($entry.Count)$($createdAndChangedCount)$($createdAndChangedInSubscriptionsCount)$($createdCount)$($createdInSubscriptionsCount)$($changedCount)$($changedInSubscriptionsCount)
    +
    + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $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 = '' + } + + + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicy) + [void]$htmlTenantSummary.AppendLine(@" + +
    IdNameName Invalid charsDisplayNameDisplayName Invalid chars
    $($id)$($name)$($nameInvalidChars)$($displayName)$($displayNameInvalidChars)
    +
    + + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $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 = '' + } + + + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicySet) + [void]$htmlTenantSummary.AppendLine(@" + +
    IdNameName Invalid charsDisplayNameDisplayName Invalid chars
    $($id)$($name)$($nameInvalidChars)$($displayName)$($displayNameInvalidChars)
    +
    + + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + + + +"@) + $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 = '' + } + + + @" + + + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingPolicyAssignment) + [void]$htmlTenantSummary.AppendLine(@" + +
    IdNameName Invalid charsDisplayNameDisplayName Invalid chars
    $($id)$($name)$($nameInvalidChars)$($displayName)$($displayNameInvalidChars)
    +
    + + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $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 = '' + } + + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingManagementGroup) + [void]$htmlTenantSummary.AppendLine(@" + +
    IdNameName Invalid chars
    $($id)$($name)$($nameInvalidChars)
    +
    + + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $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 = '' + } + + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingSubscription) + [void]$htmlTenantSummary.AppendLine(@" + +
    IdDisplayNameDisplayName Invalid chars
    $($key)$($displayName)$($displayNameInvalidChars)
    +
    + + +"@) + } + 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(@" + +
    + Download CSV semicolon | comma + + + + + + + + + +"@) + $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 = '' + } + + @" + + + + + +"@ + } + [void]$htmlTenantSummary.AppendLine($htmlSUMMARYNamingRole) + [void]$htmlTenantSummary.AppendLine(@" + +
    IdNameName Invalid chars
    $($key)$($roleName)$($roleNameInvalidChars)
    +
    + + +"@) + } + 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 + +} \ No newline at end of file diff --git a/pwsh/dev/functions/removeInvalidFileNameChars.ps1 b/pwsh/dev/functions/removeInvalidFileNameChars.ps1 new file mode 100644 index 00000000..7712dca5 --- /dev/null +++ b/pwsh/dev/functions/removeInvalidFileNameChars.ps1 @@ -0,0 +1,19 @@ +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 '"', '_') +} \ No newline at end of file diff --git a/pwsh/dev/functions/resolveObjectIds.ps1 b/pwsh/dev/functions/resolveObjectIds.ps1 new file mode 100644 index 00000000..bc09cd4d --- /dev/null +++ b/pwsh/dev/functions/resolveObjectIds.ps1 @@ -0,0 +1,151 @@ +function ResolveObjectIds($objectIds) { + + $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 -join '","') + $currentTask = " Resolving ObjectIds - Batch #$batchCnt/$($ObjectBatchCount) ($(($batch.Group).Count)" + $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" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/runInfo.ps1 b/pwsh/dev/functions/runInfo.ps1 new file mode 100644 index 00000000..db9ade5c --- /dev/null +++ b/pwsh/dev/functions/runInfo.ps1 @@ -0,0 +1,330 @@ +function runInfo { + #region RunInfo + Write-Host 'Run Info:' + if ($azAPICallConf['htParameters'].HierarchyMapOnly -eq $true) { + 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['accountId']) (App/ClientId) ($($azAPICallConf['htParameters'].accountType)) " + } + elseif ($azAPICallConf['htParameters'].accountType -eq 'ManagedService') { + $script:paramsUsed += "ExecutedBy: $($azAPICallConf['accountId']) (Id) ($($azAPICallConf['htParameters'].accountType)) " + } + elseif ($azAPICallConf['htParameters'].accountType -eq 'ClientAssertion') { + $script:paramsUsed += "ExecutedBy: $($azAPICallConf['accountId']) (App/ClientId) ($($azAPICallConf['htParameters'].accountType)) " + } + else { + $script:paramsUsed += "ExecutedBy: $($azAPICallConf['accountId']) ($($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. AzGovViz 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 - AzGovViz: 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 - AzGovViz: check the last console output for details' + } + elseif ($AzureConsumptionPeriod -eq 0) { + Write-Host 'parameter -AzureConsumptionPeriod must be gt 0' + Throw 'Error - AzGovViz: 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'].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) " + } + } + #endregion RunInfo +} \ No newline at end of file diff --git a/pwsh/dev/functions/selectMg.ps1 b/pwsh/dev/functions/selectMg.ps1 new file mode 100644 index 00000000..61f9a4bc --- /dev/null +++ b/pwsh/dev/functions/selectMg.ps1 @@ -0,0 +1,21 @@ +function selectMg() { + Write-Host 'Please select a Management Group from the list below:' + $MgtGroupArray | Select-Object '#', Name, 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 + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/setBaseVariablesMG.ps1 b/pwsh/dev/functions/setBaseVariablesMG.ps1 new file mode 100644 index 00000000..952a5cdd --- /dev/null +++ b/pwsh/dev/functions/setBaseVariablesMG.ps1 @@ -0,0 +1,15 @@ +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" + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/setOutput.ps1 b/pwsh/dev/functions/setOutput.ps1 new file mode 100644 index 00000000..c0bd1a3b --- /dev/null +++ b/pwsh/dev/functions/setOutput.ps1 @@ -0,0 +1,28 @@ +function setOutput { + #outputPath + 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 +} \ No newline at end of file diff --git a/pwsh/dev/functions/setTranscript.ps1 b/pwsh/dev/functions/setTranscript.ps1 new file mode 100644 index 00000000..2544390e --- /dev/null +++ b/pwsh/dev/functions/setTranscript.ps1 @@ -0,0 +1,52 @@ +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)" +} \ No newline at end of file diff --git a/pwsh/dev/functions/showMemoryUsage.ps1 b/pwsh/dev/functions/showMemoryUsage.ps1 new file mode 100644 index 00000000..2e692e8d --- /dev/null +++ b/pwsh/dev/functions/showMemoryUsage.ps1 @@ -0,0 +1,40 @@ +function showMemoryUsage { + if ($ShowMemoryUsage) { + function makeDouble { + [CmdletBinding()] + Param + ( + [Parameter(Mandatory = $true)]$MemoryUsed + ) + + try { + $memoryUsedDouble = [double]($memoryUsed -replace ',', '.') + } + catch { + $memoryUsedDouble = [string]$MemoryUsed + } + return $memoryUsedDouble + } + + if ($IsLinux) { + $memoryUsed = 100 - (free | grep Mem | awk '{print $4/$2 * 100.0}') + $memoryUsed = makeDouble $memoryUsed + } + if ($IsWindows) { + $memoryUsed = (Get-CimInstance win32_operatingsystem | ForEach-Object { '{0:N2}' -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory) * 100) / $_.TotalVisibleMemorySize) }) + $memoryUsed = makeDouble $memoryUsed + } + + if ($memoryUsed -is [double]) { + if ($memoryUsed -gt 90) { + Write-Host "Memory utilization HIGH: $([math]::Round($memoryUsed))%" -ForegroundColor Magenta + } + else { + Write-Host "Memory utilization: $([math]::Round($memoryUsed))%" + } + } + else { + Write-Host "Memory utilization: $($memoryUsed)%" + } + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/stats.ps1 b/pwsh/dev/functions/stats.ps1 new file mode 100644 index 00000000..c4e93c0f --- /dev/null +++ b/pwsh/dev/functions/stats.ps1 @@ -0,0 +1,165 @@ +function stats { + #region Stats + if (-not $StatsOptOut) { + + 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)) + $identifier = "$(([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": "$($identifier)", + "platform": "$($azAPICallConf['htParameters'].CodeRunPlatform)", + "productVersion": "$($ProductVersion)", + "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": "$($azAPICallConf['htParameters'].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)", + "statsParametersNoResources": "$($azAPICallConf['htParameters'].NoResources)", + "statsParametersPolicyAtScopeOnly": "$($azAPICallConf['htParameters'].PolicyAtScopeOnly)", + "statsParametersRBACAtScopeOnly": "$($azAPICallConf['htParameters'].RBACAtScopeOnly)", + "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) + } + else { + #noStats + $identifier = (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": "$($identifier)", + "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 +} \ No newline at end of file diff --git a/pwsh/dev/functions/testPowerShellVersion.ps1 b/pwsh/dev/functions/testPowerShellVersion.ps1 new file mode 100644 index 00000000..3608e73d --- /dev/null +++ b/pwsh/dev/functions/testPowerShellVersion.ps1 @@ -0,0 +1,49 @@ +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' + } +} \ No newline at end of file diff --git a/pwsh/dev/functions/validateAccess.ps1 b/pwsh/dev/functions/validateAccess.ps1 new file mode 100644 index 00000000..1a6e44e2 --- /dev/null +++ b/pwsh/dev/functions/validateAccess.ps1 @@ -0,0 +1,146 @@ +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 -noPaging + + 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 -noPaging + + 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 -noPaging + + if ($res -eq 'failed') { + $permissionCheckResults += "MSGraph API 'ServicePrincipals Read' permission - check FAILED" + $permissionsCheckFailed = $true + } + else { + $permissionCheckResults += "MSGraph API 'ServicePrincipals' Read permission - check PASSED" + } + } + #endregion validationAccess + + #ManagementGroup helper + #region managementGroupHelper + #thx @Jim Britt https://github.com/JimGBritt/AzurePolicy/tree/master/AzureMonitor/Scripts Create-AzDiagPolicy.ps1 + 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 -noPaging + + if ($getAzManagementGroups -eq 'failed') { + $permissionCheckResults += "'Reader' permissions on Management Group - check FAILED" + $permissionsCheckFailed = $true + } + else { + $permissionCheckResults += "'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 - AzGovViz: check the last console output for details' + } + + if ($getAzManagementGroups.Count -eq 0) { + Write-Host 'Management Groups count returned null' + Throw 'Error - AzGovViz: 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 - AzGovViz: check the last console output for details' + } + + selectMg + + if ($($MgtGroupArray[$SelectedMG - 1].Name)) { + $script:ManagementGroupId = $($MgtGroupArray[$SelectedMG - 1].Name) + $script:ManagementGroupName = $($MgtGroupArray[$SelectedMG - 1].DisplayName) + } + else { + Write-Host 's.th. unexpected happened' -ForegroundColor Red + return + } + Write-Host "Selected Management Group: $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 -noPaging + + if ($selectedManagementGroupId -eq 'failed') { + $permissionCheckResults += "'Reader' permissions on Management Group '$($ManagementGroupId)' - check FAILED" + $permissionsCheckFailed = $true + } + else { + $permissionCheckResults += "'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: https://$($GithubRepository)#required-permissions-in-azure" + Throw 'Error - AzGovViz: check the last console output for details' + } + + } + #endregion managementGroupHelper +} \ No newline at end of file diff --git a/pwsh/prerequisites.ps1 b/pwsh/prerequisites.ps1 index 40203183..bcfa8eec 100644 --- a/pwsh/prerequisites.ps1 +++ b/pwsh/prerequisites.ps1 @@ -1,59 +1,58 @@ -#v6_major_20220116_2 #This script should be run in Azure DevOps Pipelines and GitHub Actions only if ($env:SYSTEM_TEAMPROJECTID -and $env:BUILD_REPOSITORY_ID) { - $checkCodeRunPlatform = "AzureDevOps" + $codeRunPlatform = 'AzureDevOps' } elseif ($env:GITHUB_ACTIONS) { - $checkCodeRunPlatform = "GitHubActions" + $codeRunPlatform = 'GitHubActions' } else { - $checkCodeRunPlatform = "not 'AzureDevOps', not 'GitHubActions'" + $codeRunPlatform = "not 'AzureDevOps', not 'GitHubActions'" } -Write-Host "CodeRunPlatform:" $checkCodeRunPlatform +Write-Host 'CodeRunPlatform:' $codeRunPlatform -if ($checkCodeRunPlatform -eq "GitHubActions") { +if ($codeRunPlatform -eq 'GitHubActions') { $repoUri = "https://github.com/$($env:GITHUB_REPOSITORY)" Write-Host "Testing if repository '$($repoUri)' is accessible from the public" - try{ + try { $res = Invoke-WebRequest -uri $repoUri $statusCode = $res.StatusCode } - catch{ + catch { $statusCode = $_.Exception.Response.StatusCode.value__ } - finally{ - if ($statusCode -eq 404){ - Write-Host "Test returned statusCode: '$statusCode' - '$($repoUri)' seems not accessible from the public - proceed" + finally { + if ($statusCode -eq 404) { + Write-Host "Test returned statusCode: '$statusCode' - '$($repoUri)' seems not accessible from the public - proceed" } - elseif ($statusCode -eq 200){ + elseif ($statusCode -eq 200) { Write-Host "Test returned statusCode: '$statusCode' - '$($repoUri)' is accessible from the public!" - Write-Host "Assuming and insisting that you do not want to publish your tenant insights to the public - throw" - throw + Write-Host 'Assuming and insisting that you do not want to publish your tenant insights to the public - throw' + throw } - else{ - Write-Host "Test returned statusCode: '$statusCode' - skipping this test" + else { + Write-Host "Test returned statusCode: '$statusCode' - skipping this test" } } - Write-Host "outputpath is '$($env:outputpath)'" - if (-not (Test-Path -Path wiki)) { + Write-Host "outputpath is '$($env:Outputpath)'" + if (-not (Test-Path -Path ".\$($env:Outputpath)")) { #Assuming this is the initial run #Create the outputpath dir - Write-Host "Creating directory '$($env:outputpath)'" - New-Item -ItemType Directory -Force -Path $($env:outputpath) + Write-Host "Creating directory '$($env:Outputpath)'" + New-Item -ItemType Directory -Force -Path $($env:Outputpath) Get-ChildItem } - else{ - Write-Host "outputpath dir '$($env:outputpath)' already exists" + else { + Write-Host "outputpath dir '$($env:Outputpath)' already exists" } } -if ($checkCodeRunPlatform -eq "AzureDevOps") { +if ($codeRunPlatform -eq 'AzureDevOps') { Write-Host "outputpath is '$($env:WIKIDIR)'" if (-not (Test-Path -Path "$($env:SYSTEM_DEFAULTWORKINGDIRECTORY)/$($env:WIKIDIR)")) { #Assuming this is the initial run @@ -63,7 +62,7 @@ if ($checkCodeRunPlatform -eq "AzureDevOps") { New-Item -ItemType Directory -Force -Path "$($env:SYSTEM_DEFAULTWORKINGDIRECTORY)/$($env:WIKIDIR)" #Repository permission check - Write-Host "Repository access check" + Write-Host 'Repository access check' #createHeader $pat = $env:SYSTEM_ACCESSTOKEN #$(System.AccessToken) @@ -75,7 +74,7 @@ if ($checkCodeRunPlatform -eq "AzureDevOps") { #region listRepos $uri = "$($collectionUri)/$($project)/_apis/git/repositories?api-version=5.1" - $repos = Invoke-RestMethod -Uri $uri -Method "get" -Headers $header -ContentType "application/json" + $repos = Invoke-RestMethod -Uri $uri -Method 'get' -Headers $header -ContentType 'application/json' $htRepos = @{} foreach ($repo in $repos.value) { $htRepos.($repo.id) = @{} @@ -84,41 +83,52 @@ if ($checkCodeRunPlatform -eq "AzureDevOps") { #endregion listRepos #region gettingSubjectDescriptor - $organization = $collectionUri.Substring(0, $collectionUri.Length - 1) -replace ".*/" + $organization = $collectionUri.Substring(0, $collectionUri.Length - 1) -replace '.*/' $buildServiceAccountId = $env:SYSTEM_TEAMPROJECTID #$(System.TeamProjectId) #either 'Project Collection Build Service ($($organization))' OR '$($project) Build Service ($($organization))' $buildAccount = "Project Collection Build Service ($($organization))" Write-Host "Checking: $buildAccount" $uri = "https://vssps.dev.azure.com/$($organization)/_apis/identities?searchFilter=General&filterValue=$($buildAccount)&queryMembership=None&api-version=6.0" - $res = Invoke-RestMethod -Uri $uri -Method "GET" -Headers $header -ContentType "application/json" - $providerDisplayName = $res.value.providerDisplayName - $providerDisplayNameProjectCollectionBuildService = $providerDisplayName - $subjectDescriptor = $res.value.subjectDescriptor - - if ($providerDisplayName -ne $buildServiceAccountId) { - $buildAccount = "$($project) Build Service ($($organization))" - Write-Host "Checking: $buildAccount" - $uri = "https://vssps.dev.azure.com/$($organization)/_apis/identities?searchFilter=General&filterValue=$($buildAccount)&queryMembership=None&api-version=6.0" - $res = Invoke-RestMethod -Uri $uri -Method "GET" -Headers $header -ContentType "application/json" + try { + $res = Invoke-RestMethod -Uri $uri -Method 'GET' -Headers $header -ContentType 'application/json' + } + catch { + Write-Host "Checking: $buildAccount failed:" + $_ + Write-Host "Checking: $buildAccount failed; skipping check" + $skipCheck = $true + } + + if (-not $skipCheck) { $providerDisplayName = $res.value.providerDisplayName - $providerDisplayNameProjectBuildService = $providerDisplayName + $providerDisplayNameProjectCollectionBuildService = $providerDisplayName $subjectDescriptor = $res.value.subjectDescriptor - } - #endregion gettingSubjectDescriptor + + if ($providerDisplayName -ne $buildServiceAccountId) { + $buildAccount = "$($project) Build Service ($($organization))" + Write-Host "Checking: $buildAccount" + $uri = "https://vssps.dev.azure.com/$($organization)/_apis/identities?searchFilter=General&filterValue=$($buildAccount)&queryMembership=None&api-version=6.0" + $res = Invoke-RestMethod -Uri $uri -Method 'GET' -Headers $header -ContentType 'application/json' + $providerDisplayName = $res.value.providerDisplayName + $providerDisplayNameProjectBuildService = $providerDisplayName + $subjectDescriptor = $res.value.subjectDescriptor + } - if ($providerDisplayName -ne $buildServiceAccountId) { - Write-Host "Neighter 'Project Collection Build Service ($($organization)) $($providerDisplayNameProjectCollectionBuildService)' nore '$($project) Build Service ($($organization))' $providerDisplayNameProjectBuildService matching Id: $($buildServiceAccountId)" - } - else { - Write-Host "subjectDescriptor for '$($buildAccount)':" $subjectDescriptor + #endregion gettingSubjectDescriptor + + if ($providerDisplayName -ne $buildServiceAccountId) { + Write-Host "Neighter 'Project Collection Build Service ($($organization)) $($providerDisplayNameProjectCollectionBuildService)' nore '$($project) Build Service ($($organization))' $providerDisplayNameProjectBuildService matching Id: $($buildServiceAccountId)" + } + else { + Write-Host "subjectDescriptor for '$($buildAccount)':" $subjectDescriptor - #region gettingPermissions - $repositoryId = $env:BUILD_REPOSITORY_ID #$(Build.Repository.ID) - $permissionSetId = "2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87" #Git Repositories + #region gettingPermissions + $repositoryId = $env:BUILD_REPOSITORY_ID #$(Build.Repository.ID) + $permissionSetId = '2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87' #Git Repositories - $uri = "$($collectionUri)/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1" - $body = @" + $uri = "$($collectionUri)/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1" + $body = @" { "contributionIds": [ "ms.vss-admin-web.security-view-permissions-data-provider" @@ -133,54 +143,55 @@ if ($checkCodeRunPlatform -eq "AzureDevOps") { } "@ - $permissions = Invoke-RestMethod -Uri $uri -Method "post" -Body $body -Headers $header -ContentType "application/json" - $htPermissionsRef = @{} - $htPermissionsRef."1" = "allow" - $htPermissionsRef."2" = "deny" - $htPermissionsRef."3" = "allow(inherited)" - Write-Host "'$($buildAccount)' ($buildServiceAccountId) permissions for Repository: $($htRepos.($repositoryId).name) ($repositoryId)" - $contributePermissionState = ($permissions.dataProviders."ms.vss-admin-web.security-view-permissions-data-provider".subjectPermissions).where( { $_.displayName -eq "Contribute" } ).effectivePermissionValue - if ($contributePermissionState -eq 1 -or $contributePermissionState -eq 3) { - Write-Host " Contribute: $($htPermissionsRef."$contributePermissionState") ($($contributePermissionState))" - } - else { - if ($contributePermissionState) { + $permissions = Invoke-RestMethod -Uri $uri -Method 'post' -Body $body -Headers $header -ContentType 'application/json' + $htPermissionsRef = @{} + $htPermissionsRef.'1' = 'allow' + $htPermissionsRef.'2' = 'deny' + $htPermissionsRef.'3' = 'allow(inherited)' + Write-Host "'$($buildAccount)' ($buildServiceAccountId) permissions for Repository: $($htRepos.($repositoryId).name) ($repositoryId)" + $contributePermissionState = ($permissions.dataProviders.'ms.vss-admin-web.security-view-permissions-data-provider'.subjectPermissions).where( { $_.displayName -eq 'Contribute' } ).effectivePermissionValue + if ($contributePermissionState -eq 1 -or $contributePermissionState -eq 3) { Write-Host " Contribute: $($htPermissionsRef."$contributePermissionState") ($($contributePermissionState))" } else { - Write-Host " Contribute: not set" - } - $testResult = "FAILED" - } - Write-Host "All permissions:" - foreach ($permission in $permissions.dataProviders."ms.vss-admin-web.security-view-permissions-data-provider".subjectPermissions) { - if ($permission.effectivePermissionValue) { - Write-Host " $($permission.displayName): $($htPermissionsRef."$($permission.effectivePermissionValue)") ($($permission.effectivePermissionValue))" + if ($contributePermissionState) { + Write-Host " Contribute: $($htPermissionsRef."$contributePermissionState") ($($contributePermissionState))" + } + else { + Write-Host ' Contribute: not set' + } + $testResult = 'FAILED' } - else { - Write-Host " $($permission.displayName): not set" + Write-Host 'All permissions:' + foreach ($permission in $permissions.dataProviders.'ms.vss-admin-web.security-view-permissions-data-provider'.subjectPermissions) { + if ($permission.effectivePermissionValue) { + Write-Host " $($permission.displayName): $($htPermissionsRef."$($permission.effectivePermissionValue)") ($($permission.effectivePermissionValue))" + } + else { + Write-Host " $($permission.displayName): not set" + } } - } - if ($testResult -eq "FAILED") { - Write-Host "" - Write-Host "- - - - - - - - - - - - - - - - -" - Write-Host "Repository permission test failed" - Write-Host "You must grant the Account '$($buildAccount)' ($buildServiceAccountId) with 'Contribute' permissions on the Repository '$($htRepos.($repositoryId).name)'' ($repositoryId)" - Write-Host "Instructions: https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/blob/master/setup.md#grant-permissions-on-azgovviz-azdo-repository" - Write-Error "Error" - } - else { - $timestamp = get-date -format "yyyy-MM-dd_HH:mm:ss" - $fileContent = @" + if ($testResult -eq 'FAILED') { + Write-Host '' + Write-Host '- - - - - - - - - - - - - - - - -' + Write-Host 'Repository permission test failed' + Write-Host "You must grant the Account '$($buildAccount)' ($buildServiceAccountId) with 'Contribute' permissions on the Repository '$($htRepos.($repositoryId).name)'' ($repositoryId)" + Write-Host 'Instructions: https://github.com/JulianHayward/Azure-MG-Sub-Governance-Reporting/blob/master/setup.md#grant-permissions-on-azgovviz-azdo-repository' + Write-Error 'Error' + } + else { + $timestamp = get-date -format 'yyyy-MM-dd_HH:mm:ss' + $fileContent = @" Repository access check result: Date: $($timestamp) Build Account: '$($buildAccount)' ($buildServiceAccountId) Permission 'Contribute' for Repository '$($htRepos.($repositoryId).name) ($repositoryId)': $($contributePermissionState) "@ - $fileContent | Set-Content -Path "$($env:SYSTEM_DEFAULTWORKINGDIRECTORY)/AzGovViz_RepositoryPermissionCheck.log" -Encoding utf8 -Force + $fileContent | Set-Content -Path "$($env:SYSTEM_DEFAULTWORKINGDIRECTORY)/AzGovViz_RepositoryPermissionCheck.log" -Encoding utf8 -Force + } + #endregion gettingPermissions } - #endregion gettingPermissions } } else {