├── .gitattributes ├── LICENSE ├── Query 3 – Get Defender for Cloud SubAssessments.txt ├── README.md ├── Powershell-Query_Search-AzGraph.txt ├── Query 2 – Get Defender for Cloud Recommendations – with links for more information (SubAssessments).txt ├── Query 1 – Get Defender for Cloud recommendations – including SubAssessments (for example vulnerability recommendations).txt ├── Content.csv └── Azure-Recommendations-Get-In-Control.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Morten Knudsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Query 3 – Get Defender for Cloud SubAssessments.txt: -------------------------------------------------------------------------------- 1 | // Query #3 – Get Defender for Cloud SubAssessments 2 | SecurityResources 3 | | where type == 'microsoft.security/assessments/subassessments' 4 | | extend AssessmentKey = extract('.*assessments/(.+?)/.*',1, id) 5 | | project AssessmentKey, subassessmentKey=name, id, parse_json(properties), resourceGroup, subscriptionId, tenantId 6 | | extend SubAssessDescription = properties.description, 7 | SubAssessDisplayName = properties.displayName, 8 | SubAssessResourceId = properties.resourceDetails.id, 9 | SubAssessResourceSource = properties.resourceDetails.source, 10 | SubAssessCategory = properties.category, 11 | SubAssessSeverity = properties.status.severity, 12 | SubAssessCode = properties.status.code, 13 | SubAssessTimeGenerated = properties.timeGenerated, 14 | SubAssessRemediation = properties.remediation, 15 | SubAssessImpact = properties.impact, 16 | SubAssessVulnId = properties.id, 17 | SubAssessMoreInfo = properties.additionalData, 18 | SubAssessMoreInfoAssessedResourceType = properties.additionalData.assessedResourceType, 19 | SubAssessMoreInfoData = properties.additionalData.data 20 | | join kind=leftouter (resourcecontainers | where type=='microsoft.resources/subscriptions' | project SubName=name, subscriptionId) on subscriptionId -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure-Recommendations-Get-In-Control 2 | Automate Reporting of Defender for Cloud recommendations & Role Assignments with 35 different views 3 | 4 | .SYNOPSIS 5 | 6 | In this script, I will demonstrate, how you can extract security recommendations from Microsoft 7 | Defender for Cloud using Azure Resource Graph - delivering a horizontal cross-subscriptions, 8 | workload overview. Data will automatically be exported into a Excel spreadsheet delivering 19 Excel 9 | tables and 16 pivot tables. 10 | 11 | Information can be used to detect deviations from best practice / desired state - covering 12 | * Getting-in-control with workloads in tenant/management group (storage, network, app services, 13 | containers, etc.) where we are not in control according to security best practice / desired state 14 | * Getting-in-control with subscriptions, where environment are not configured according to security 15 | best practice / desired state 16 | * Get-in-control with role assignments in tenant / management group / subscription / resource group. 17 | * Get detailed information about role assignments on user / service-principal-level, based on direct 18 | assignment and inheritance 19 | * Get detailed insight about users / service-principal-level, based on group membership - both direct 20 | and inheritance. 21 | 22 | .NOTES 23 | VERSION: 2301 24 | 25 | .COPYRIGHT 26 | @mortenknudsendk on Twitter 27 | Blog: https://mortenknudsen.net 28 | 29 | .LICENSE 30 | Licensed under the MIT license. 31 | 32 | .WARRANTY 33 | Use at your own risk, no warranty given! 34 | -------------------------------------------------------------------------------- /Powershell-Query_Search-AzGraph.txt: -------------------------------------------------------------------------------- 1 | $MDC_Recommendations_SubAssessments = @() 2 | $pageSize = 1000 3 | $iteration = 0 4 | $searchParams = @{ 5 | Query = "SecurityResources ` 6 | | where type == 'microsoft.security/assessments/subassessments' 7 | | extend AssessmentKey = extract('.*assessments/(.+?)/.*',1, id) 8 | | project AssessmentKey, subassessmentKey=name, id, parse_json(properties), resourceGroup, subscriptionId, tenantId 9 | | extend SubAssessDescription = properties.description, 10 | SubAssessDisplayName = properties.displayName, 11 | SubAssessResourceId = properties.resourceDetails.id, 12 | SubAssessResourceSource = properties.resourceDetails.source, 13 | SubAssessCategory = properties.category, 14 | SubAssessSeverity = properties.status.severity, 15 | SubAssessCode = properties.status.code, 16 | SubAssessTimeGenerated = properties.timeGenerated, 17 | SubAssessRemediation = properties.remediation, 18 | SubAssessImpact = properties.impact, 19 | SubAssessVulnId = properties.id, 20 | SubAssessMoreInfo = properties.additionalData, 21 | SubAssessMoreInfoAssessedResourceType = properties.additionalData.assessedResourceType, 22 | SubAssessMoreInfoData = properties.additionalData.data ` 23 | | join kind=leftouter (resourcecontainers | where type=='microsoft.resources/subscriptions' | project SubName=name, subscriptionId) on subscriptionId " 24 | First = $pageSize 25 | } 26 | 27 | $results = do { 28 | $iteration += 1 29 | Write-Verbose "Iteration #$iteration" -Verbose 30 | $pageResults = Search-AzGraph @searchParams -ManagementGroup $Global:ManagementGroupScope 31 | $searchParams.Skip += $pageResults.Count 32 | $MDC_Recommendations_SubAssessments += $pageResults 33 | } while ($pageResults.Count -eq $pageSize) -------------------------------------------------------------------------------- /Query 2 – Get Defender for Cloud Recommendations – with links for more information (SubAssessments).txt: -------------------------------------------------------------------------------- 1 | // Query #2 – Get Defender for Cloud Recommendations – with links for more information (SubAssessments) 2 | SecurityResources 3 | | where type == 'microsoft.security/assessments' 4 | | mvexpand Category=properties.metadata.categories 5 | | extend AssessmentId=id, 6 | AssessmentKey=name, 7 | ResourceId=properties.resourceDetails.Id, 8 | ResourceIdsplit = split(properties.resourceDetails.Id,'/'), 9 | RecommendationId=name, 10 | RecommendationName=properties.displayName, 11 | Source=properties.resourceDetails.Source, 12 | RecommendationState=properties.status.code, 13 | ActionDescription=properties.metadata.description, 14 | AssessmentType=properties.metadata.assessmentType, 15 | RemediationDescription=properties.metadata.remediationDescription, 16 | PolicyDefinitionId=properties.metadata.policyDefinitionId, 17 | ImplementationEffort=properties.metadata.implementationEffort, 18 | RecommendationSeverity=properties.metadata.severity, 19 | Threats=properties.metadata.threats, 20 | UserImpact=properties.metadata.userImpact, 21 | AzPortalLink=properties.links.azurePortal, 22 | MoreInfo=properties 23 | | extend ResourceSubId = tostring(ResourceIdsplit[(2)]), 24 | ResourceRgName = tostring(ResourceIdsplit[(4)]), 25 | ResourceType = tostring(ResourceIdsplit[(6)]), 26 | ResourceName = tostring(ResourceIdsplit[(8)]), 27 | FirstEvaluationDate = MoreInfo.status.firstEvaluationDate, 28 | StatusChangeDate = MoreInfo.status.statusChangeDate, 29 | Status = MoreInfo.status.code 30 | | join kind=leftouter (resourcecontainers | where type=='microsoft.resources/subscriptions' | project SubName=name, subscriptionId) on subscriptionId 31 | | where AssessmentType == 'BuiltIn' 32 | | project-away kind,managedBy,sku,plan,tags,identity,zones,location,ResourceIdsplit,id,name,type,resourceGroup,subscriptionId, extendedLocation,subscriptionId1 33 | | project SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,TenantId=tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink, AssessmentKey 34 | | where RecommendationState == 'Unhealthy' -------------------------------------------------------------------------------- /Query 1 – Get Defender for Cloud recommendations – including SubAssessments (for example vulnerability recommendations).txt: -------------------------------------------------------------------------------- 1 | // Query #1 – Get Defender for Cloud recommendations – including SubAssessments (for example vulnerability recommendations) 2 | SecurityResources 3 | | where type == 'microsoft.security/assessments' 4 | | mvexpand Category=properties.metadata.categories 5 | | extend AssessmentId=id, 6 | AssessmentKey=name, 7 | ResourceId=properties.resourceDetails.Id, 8 | ResourceIdsplit = split(properties.resourceDetails.Id,'/'), 9 | RecommendationId=name, 10 | RecommendationName=properties.displayName, 11 | Source=properties.resourceDetails.Source, 12 | RecommendationState=properties.status.code, 13 | ActionDescription=properties.metadata.description, 14 | AssessmentType=properties.metadata.assessmentType, 15 | RemediationDescription=properties.metadata.remediationDescription, 16 | PolicyDefinitionId=properties.metadata.policyDefinitionId, 17 | ImplementationEffort=properties.metadata.implementationEffort, 18 | RecommendationSeverity=properties.metadata.severity, 19 | Threats=properties.metadata.threats, 20 | UserImpact=properties.metadata.userImpact, 21 | AzPortalLink=properties.links.azurePortal, 22 | MoreInfo=properties 23 | | extend ResourceSubId = tostring(ResourceIdsplit[(2)]), 24 | ResourceRgName = tostring(ResourceIdsplit[(4)]), 25 | ResourceType = tostring(ResourceIdsplit[(6)]), 26 | ResourceName = tostring(ResourceIdsplit[(8)]), 27 | FirstEvaluationDate = MoreInfo.status.firstEvaluationDate, 28 | StatusChangeDate = MoreInfo.status.statusChangeDate, 29 | Status = MoreInfo.status.code 30 | | join kind=leftouter (resourcecontainers | where type=='microsoft.resources/subscriptions' | project SubName=name, subscriptionId) on subscriptionId 31 | | where AssessmentType == 'BuiltIn' 32 | | project-away kind,managedBy,sku,plan,tags,identity,zones,location,ResourceIdsplit,id,name,type,resourceGroup,subscriptionId, extendedLocation,subscriptionId1 33 | | project SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,TenantId=tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink, AssessmentKey 34 | | where RecommendationState == 'Unhealthy' 35 | | join kind=leftouter ( 36 | securityresources 37 | | where type == 'microsoft.security/assessments/subassessments' 38 | | extend AssessmentKey = extract('.*assessments/(.+?)/.*',1, id) 39 | | project AssessmentKey, subassessmentKey=name, id, parse_json(properties), resourceGroup, subscriptionId, tenantId 40 | | extend SubAssessmentDescription = properties.description, 41 | SubAssessmentDisplayName = properties.displayName, 42 | SubAssessmentResourceId = properties.resourceDetails.id, 43 | SubAssessmentResourceSource = properties.resourceDetails.source, 44 | SubAssessmentCategory = properties.category, 45 | SubAssessmentSeverity = properties.status.severity, 46 | SubAssessmentCode = properties.status.code, 47 | SubAssessmentTimeGenerated = properties.timeGenerated, 48 | SubAssessmentRemediation = properties.remediation, 49 | SubAssessmentImpact = properties.impact, 50 | SubAssessmentVulnId = properties.id, 51 | SubAssessmentMoreInfo = properties.additionalData, 52 | SubAssessmentMoreInfoAssessedResourceType = properties.additionalData.assessedResourceType, 53 | SubAssessmentMoreInfoData = properties.additionalData.data 54 | ) on AssessmentKey -------------------------------------------------------------------------------- /Content.csv: -------------------------------------------------------------------------------- 1 | TableName;Purpose;Source 2 | Unhealthy_All;Show All Unhealthy recommendations;"Defender for Cloud 3 | Extracted via Azure Resource Graph" 4 | Unhealthy_High;Show all Unhealthy recommendations with High priority;Filtered view of Unhealthy_All 5 | Unhealthy_Medium;Show all Unhealthy recommendations with medium priority;Filtered view of Unhealthy_All 6 | Unhealthy_Low;Show all Unhealthy recommendations with Low priority;Filtered view of Unhealthy_All 7 | Unhealthy_All_SubLevel;Show all Unhealthy recommendations where the target is on subcription-level;Filtered view of Unhealthy_All 8 | Unhealthy_All_ResourceLevel;Show all Unhealthy recommendations where the target is on resource-level;Filtered view of Unhealthy_All 9 | Unhealthy_Category_Networking;Show all Unhealthy recommendations related to Networking category;Filtered view of Unhealthy_All 10 | Unhealthy_Category_AppServices;Show all Unhealthy recommendations related to AppServices category;Filtered view of Unhealthy_All 11 | Unhealthy_Category_Compute;Show all Unhealthy recommendations related to Compute category;Filtered view of Unhealthy_All 12 | Unhealthy_Category_Container;Show all Unhealthy recommendations related to Container category;Filtered view of Unhealthy_All 13 | Unhealthy_Category_Data;Show all Unhealthy recommendations related to Data category;Filtered view of Unhealthy_All 14 | Unhealthy_Category_IoT;Show all Unhealthy recommendations related to IoT category;Filtered view of Unhealthy_All 15 | Unhealthy_Category_Id_Access;Show all Unhealthy recommendations related to IdentityAndAccess category;Filtered view of Unhealthy_All 16 | SubAssess_All;Show all detailed information (SubAssessments);"Defender for Cloud 17 | Extracted via Azure Resource Graph" 18 | SubAssess_Identity;Show all identity detailed information (SubAssessments);Filtered view of SubAssess_All 19 | SubAssess_Other;Show all other, non-identity detailed information (SubAssessments);Filtered view of SubAssess_All 20 | RBAC_RoleAssignments;Show all Azure RBAC Role Assignments;Azure Role Assignsment 21 | RBAC_Direct_Sublevel;Show all Azure RBAC Role Assignments directly on Sub-level (not part of group);Filtered of RBAC_RoleAssignments 22 | RBAC_Direct_Mglevel;Show all Azure RBAC Role Assignments directly on Mg-level (not part of group);Filtered of RBAC_RoleAssignments 23 | PT_CATEGORY_SUBLEVEL;"Prioritize recommendations based on Category and RecommendationSeverity 24 | Sort-order: 25 | (1) Category (Storage, Network, Identity, etc.) 26 | (2) RecommendationSeverity (High, Medium, Low) 27 | (3) RecommendationName 28 | (4) SubName (suscription name)";"Table 29 | Unhealthy_All_SubLevel" 30 | PT_CATEGORY_RESOURCELEVEL;"Prioritize recommendations based on ResourceType and RecommendationSeverity 31 | Sort-order: 32 | (1) Category (Storage, Network, Identity, etc.) 33 | (2) RecommendationSeverity (High, Medium, Low) 34 | (3) RecommendationName 35 | (4) SubName (suscription name) 36 | (5) ResourceRgName 37 | (6) ResourceName";"Table 38 | Unhealthy_All_ResourceLevel" 39 | PT_CATEGORY_NETWORKING;"Prioritize networking recommendations based on RecommendationSeverity 40 | Sort-order: 41 | (1) RecommendationSeverity (High, Medium, Low) 42 | (2) RecommendationName 43 | (3) SubName (suscription name) 44 | (4) ResourceRgName 45 | (5) ResourceName";"Table 46 | Unhealthy_Category_Networking" 47 | PT_CATEGORY_APPSERVICES;"Prioritize AppServices recommendations based on RecommendationSeverity 48 | Sort-order: 49 | (1) RecommendationSeverity (High, Medium, Low) 50 | (2) RecommendationName 51 | (3) SubName (suscription name) 52 | (4) ResourceRgName 53 | (5) ResourceName";"Table 54 | Unhealthy_Category_AppServices" 55 | PT_CATEGORY_COMPUTE;"Prioritize Compute recommendations based on RecommendationSeverity 56 | Sort-order: 57 | (1) RecommendationSeverity (High, Medium, Low) 58 | (2) RecommendationName 59 | (3) SubName (suscription name) 60 | (4) ResourceRgName 61 | (5) ResourceName";"Table 62 | Unhealthy_Category_Compute" 63 | PT_CATEGORY_CONTAINER;"Prioritize Container recommendations based on RecommendationSeverity 64 | Sort-order: 65 | (1) RecommendationSeverity (High, Medium, Low) 66 | (2) RecommendationName 67 | (3) SubName (suscription name) 68 | (4) ResourceRgName 69 | (5) ResourceName";"Table 70 | Unhealthy_Category_Container" 71 | PT_CATEGORY_DATA;"Prioritize Data recommendations based on RecommendationSeverity 72 | Sort-order: 73 | (1) RecommendationSeverity (High, Medium, Low) 74 | (2) RecommendationName 75 | (3) SubName (suscription name) 76 | (4) ResourceRgName 77 | (5) ResourceName";"Table 78 | Unhealthy_Category_Data" 79 | PT_CATEGORY_ID_ACCESS;"Prioritize Identity and Access recommendations based on RecommendationSeverity 80 | Sort-order: 81 | (1) RecommendationSeverity (High, Medium, Low) 82 | (2) RecommendationName 83 | (3) SubName (suscription name) 84 | (4) ResourceRgName 85 | (5) ResourceName";"Table 86 | Unhealthy_Category_IdentityAndAccess" 87 | PT_RESOURCETYPE;"Prioritize recommendations based on ResourceType and RecommendationSeverity 88 | Sort-order: 89 | (1) ResourceType (SQL, Keyvault, Storage, Network, Identity, etc.) 90 | (2) RecommendationSeverity (High, Medium, Low) 91 | (3) RecommendationName 92 | (4) SubName (suscription name) 93 | (5) ResourceRgName 94 | (6) ResourceName";"Table 95 | Unhealthy_All_ResourceLevel" 96 | PT_CATEGORY_RESOURCELEVEL;"Prioritize recommendations based on Category and RecommendationSeverity 97 | Sort-order: 98 | (1) Category (Storage, Network, Identity, etc.) 99 | (2) RecommendationSeverity (High, Medium, Low) 100 | (3) RecommendationName 101 | (4) SubName (suscription name) 102 | (5) ResourceRgName 103 | (6) ResourceName";"Table 104 | Unhealthy_All_ResourceLevel" 105 | PT_SUBASSESS_IDENTITY;"Prioritize recommendations based on SubAssessmentSeverity (identity-related) 106 | Sort-order: 107 | (1) SubAssessSeverity (High, Medium, Low) 108 | (2) SubAssessDisplayName (recommendation) 109 | (3) SubAssessResDisplayName (resource) 110 | (4) SubAssessResUserPrincipalName (resource) 111 | (5) SubName (suscription name)";"Table 112 | SubAssess_Identity" 113 | PT_SUBASSESS_OTHER;"Prioritize recommendations based on SubAssessmentSeverity (non-identity related) 114 | Sort-order: 115 | (1) SubAssessSeverity (High, Medium, Low) 116 | (2) SubAssessDisplayName (recommendation) 117 | (3) SubAssessResDisplayName (resource) 118 | (4) SubAssessResUserPrincipalName (resource) 119 | (5) SubName (suscription name)";"Table 120 | SubAssess_Other" 121 | PT_RBAC_ROLEDEF;"Detail RBAC permissions, sorted by RoleDefinitionName and Scope_Delegation 122 | Sort-order: 123 | (1) SubscriptionName 124 | (2) RoleDefinitionName (Contributor, Owner, Cost Management Reader, etc.) 125 | (3) Scope_Delegation (Direct_SUB, Direct_RG, Inheritance_MG) 126 | (4) Scope (management group name or /) 127 | (5) RBAC_Delegation_Type (Group_inheritance, Direct) 128 | (6) RBAC_GroupName 129 | (7) ObjectType 130 | (8) DisplayName 131 | (9) UserPrincipalName";"Table 132 | RBAC_RoleAssignments" 133 | PT_RBAC_SCOPE_DELEGATION;"Detail RBAC permissions, sorted by Scope_Delegation and RoleDefinitionName 134 | Sort-order: 135 | (1) SubscriptionName 136 | (2) Scope_Delegation (Direct_SUB, Direct_RG, Inheritance_MG) 137 | (3) RoleDefinitionName (Contributor, Owner, Cost Management Reader, etc.) 138 | (4) Scope (management group name or /) 139 | (5) RBAC_Delegation_Type (Group_inheritance, Direct) 140 | (6) RBAC_GroupName 141 | (7) ObjectType 142 | (8) DisplayName 143 | (9) UserPrincipalName";"Table 144 | RBAC_RoleAssignments" 145 | PT_RBAC_MG;"Detect direct RBAC permissions on Mg-level (not done by group) 146 | Sort-order: 147 | (1) SubscriptionName 148 | (2) RoleDefinitionName (Contributor, Owner, Cost Management Reader, etc.) 149 | (3) Scope_Delegation (Direct_SUB, Direct_RG, Inheritance_MG) 150 | (4) Scope (management group name or /) 151 | (5) ObjectType 152 | (6) DisplayName 153 | (7) UserPrincipalName";"Table 154 | RBAC_Direct_Mglevel" 155 | PT_RBAC_SUB;"Detect direct RBAC permissions on Sub-level (not done by group) 156 | Sort-order: 157 | (1) SubscriptionName 158 | (2) RoleDefinitionName (Contributor, Owner, Cost Management Reader, etc.) 159 | (3) Scope_Delegation (Direct_SUB, Direct_RG, Inheritance_MG) 160 | (4) Scope (management group name or /) 161 | (5) ObjectType 162 | (6) DisplayName 163 | (7) UserPrincipalName";"Table 164 | RBAC_Direct_Sublevel" 165 | -------------------------------------------------------------------------------- /Azure-Recommendations-Get-In-Control.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 5.0 2 | <# 3 | .SYNOPSIS 4 | 5 | In this script, I will demonstrate, how you can extract security recommendations from Microsoft Defender for Cloud 6 | using Azure Resource Graph - delivering a horizontal cross-subscriptions, workload overview. 7 | Data will automatically be exported into a Excel spreadsheet delivering 19 Excel tables and 16 pivot tables. 8 | 9 | Information can be used to detect deviations from best practice / desired state - covering 10 | * Getting-in-control with workloads in tenant/management group (storage, network, app services, containers, etc.) 11 | where we are not in control according to security best practice / desired state 12 | * Getting-in-control with subscriptions, where environment are not configured according to security best practice / desired state 13 | * Get-in-control with role assignments in tenant / management group / subscription / resource group. 14 | * Get detailed information about role assignments on user / service-principal-level, based on direct assignment and inheritance 15 | * Get detailed insight about users / service-principal-level, based on group membership - both direct and inheritance. 16 | 17 | .NOTES 18 | VERSION: 2301 19 | 20 | .COPYRIGHT 21 | @mortenknudsendk on Twitter 22 | Blog: https://mortenknudsen.net 23 | 24 | .LICENSE 25 | Licensed under the MIT license. 26 | 27 | .WARRANTY 28 | Use at your own risk, no warranty given! 29 | #> 30 | 31 | 32 | #------------------------------------------------------------------------------------------------------------ 33 | # Functions 34 | #------------------------------------------------------------------------------------------------------------ 35 | Function AZ_Find_Subscriptions_in_Tenant_With_Subscription_Exclusions 36 | { 37 | Write-Output "" 38 | Write-Output "Finding all subscriptions in scope .... please Wait !" 39 | 40 | $global:Query_Exclude = @() 41 | $Subscriptions_in_Scope = @() 42 | $pageSize = 1000 43 | $iteration = 0 44 | 45 | ForEach ($Sub in $global:Exclude_Subscriptions) 46 | { 47 | $global:Query_Exclude += "| where (subscriptionId !~ `"$Sub`")" 48 | } 49 | 50 | $searchParams = @{ 51 | Query = "ResourceContainers ` 52 | | where type =~ 'microsoft.resources/subscriptions' ` 53 | | extend status = properties.state ` 54 | $global:Query_Exclude 55 | | project id, subscriptionId, name, status | order by id, subscriptionId desc" 56 | First = $pageSize 57 | } 58 | 59 | $results = do { 60 | $iteration += 1 61 | $pageResults = Search-AzGraph -ManagementGroup $Global:ManagementGroupScope @searchParams 62 | $searchParams.Skip += $pageResults.Count 63 | $Subscriptions_in_Scope += $pageResults 64 | } while ($pageResults.Count -eq $pageSize) 65 | 66 | $Global:Subscriptions_in_Scope = $Subscriptions_in_Scope 67 | 68 | # Output 69 | $Global:Subscriptions_in_Scope 70 | } 71 | 72 | 73 | #------------------------------------------------------------------------------------------------------------ 74 | # Variables 75 | #------------------------------------------------------------------------------------------------------------ 76 | 77 | # Scope (MG) | You can define the scope for the targetting, supporting management groups or tenant root id (all subs) 78 | $Global:ManagementGroupScope = "f0fa27a0-8e7c-4f63-9a77-ec94786b7c9e" # can mg e.g. mg-company or AAD Id (=Tenant Root Id) 79 | 80 | # Exclude list | You can exclude certain subs, resource groups, resources, if you don't want to have them as part of the scope 81 | $global:Exclude_Subscriptions = @("xxxxxxxxxxxxxxxxxxxxxx") # for example platform-connectivity 82 | $global:Exclude_ResourceGroups = @() 83 | $global:Exclude_Resource = @() 84 | $global:Exclude_Resource_Contains = @() 85 | $global:Exclude_Resource_Startswith = @() 86 | $global:Exclude_Resource_Endswith = @() 87 | 88 | # Content file 89 | $HelpContentFile = "C:\SCRIPTS\Azure-Recommendations-Get-In-Control\Content.csv" 90 | 91 | # OutputFile 92 | $FileOutput = "C:\SCRIPTS\Azure-Recommendations-Get-In-Control\Azure_Recommendations_Get-in-Control.xlsx" 93 | 94 | 95 | #------------------------------------------------------------------------------------------------------------ 96 | # Connect to Azure & Azure AD 97 | #------------------------------------------------------------------------------------------------------------ 98 | Connect-AzAccount 99 | Connect-AzureAD 100 | 101 | 102 | #-------------------------------------------------------------------------------------------------------- 103 | # Powershell Modules 104 | #-------------------------------------------------------------------------------------------------------- 105 | <# 106 | Install-module Az 107 | Install-module Az.ResourceGraph 108 | Install-module ImportExcel 109 | #> 110 | 111 | #------------------------------------------------------------------------------------------------------------ 112 | # File Check for Content.csv 113 | #------------------------------------------------------------------------------------------------------------ 114 | 115 | If (!(Test-Path $HelpContentFile)) 116 | { 117 | Write-Output "Content-file $($HelpContentFile) was NOT found !!" 118 | Break 119 | } 120 | 121 | 122 | #-------------------------------------------------------------------------------------------------------- 123 | # Scope - subscriptions - where to look for Azure Role Assignments 124 | #-------------------------------------------------------------------------------------------------------- 125 | 126 | # Retrieving data using Azure ARG 127 | AZ_Find_Subscriptions_in_Tenant_With_Subscription_Exclusions 128 | 129 | # Filter - only lists ENABLED subscriptions 130 | $Global:Subscriptions_in_Scope = $Global:Subscriptions_in_Scope | Where-Object {$_.status -eq "Enabled"} 131 | 132 | Write-Output "Scope (target)" 133 | 134 | $Global:Subscriptions_in_Scope 135 | 136 | 137 | 138 | ############################### 139 | # (A1) Azure RBAC Role Assignments 140 | ############################### 141 | 142 | $RBAC_RoleAssignments = @() 143 | ForEach ($Subscription in $Global:Subscriptions_in_Scope) 144 | { 145 | # Get Azure RBAC Role Assignments 146 | Write-output "" 147 | $AzContext = Set-AzContext -SubscriptionId $Subscription.subscriptionId -Force 148 | 149 | $SubscriptionName = $Subscription.Name 150 | $SubscriptionId = $Subscription.subscriptionId 151 | 152 | # Getting information about Role Assignments for choosen subscription 153 | Write-output "[$($Subscription.Name)] - Getting Role Assignments" 154 | 155 | $RBAC_Roles = Get-AzRoleAssignment -WarningAction SilentlyContinue 156 | 157 | foreach ($Role in $RBAC_Roles) 158 | { 159 | $AccObj = New-Object -TypeName PSObject 160 | 161 | If ($Role.ObjectType -eq "Unknown") 162 | { 163 | Write-Output " [$($Role.RoleDefinitionName)] - Unknown (clean-up needed)" 164 | } 165 | Else 166 | { 167 | Write-Output " [$($Role.RoleDefinitionName)] - $($Role.DisplayName)" 168 | } 169 | 170 | # Role Assignment info 171 | $RoleAssignmentName = $role.RoleAssignmentName 172 | $RoleAssignmentId = $role.RoleAssignmentId 173 | 174 | # Role Assignement Scope - Direct on Sub or Inheritance 175 | 176 | # Inheritance 177 | If ($Role.Scope -eq "/subscriptions/$($SubscriptionId)") 178 | { 179 | $Scope_Delegation = "Direct_SUB" 180 | $Scope = $Role.Scope 181 | } 182 | ElseIf ($Role.Scope -like "/subscriptions/$($SubscriptionId)/*") 183 | { 184 | $Scope_Delegation = "Direct_RG" 185 | $Scope = $Role.Scope 186 | } 187 | ElseIf ($Role.Scope -like "/providers/Microsoft.Management/managementGroups*") 188 | { 189 | $Scope_Delegation = "Inheritance_MG" 190 | $Scope = ($Role.Scope.split("/")[4]) 191 | } 192 | 193 | 194 | # Direct Account Role Assignment (not group) 195 | If ( ($Role.ObjectType -ne "Group") -and ($Role.ObjectType -ne "Unknown") ) 196 | { 197 | # Delegated Account (direct) 198 | $AccInfo = Get-AzureADObjectByObjectId -ObjectIds $Role.ObjectId 199 | $DisplayName = $AccInfo.DisplayName 200 | $UserPrincipalName = $AccInfo.UserPrincipalName 201 | $ObjectId = $AccInfo.ObjectId 202 | $ObjectType = $AccInfo.ObjectType 203 | $AccEnabled = $AccInfo.AccountEnabled 204 | 205 | $AccObj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $DisplayName 206 | $AccObj | Add-Member -MemberType NoteProperty -Name UserPrincipalName -Value $UserPrincipalName 207 | $AccObj | Add-Member -MemberType NoteProperty -Name ObjectType -value $ObjectType 208 | $AccObj | Add-Member -MemberType NoteProperty -Name ObjectId -value $ObjectId 209 | $AccObj | Add-Member -MemberType NoteProperty -Name AccEnabled -value $AccEnabled 210 | $AccObj | Add-Member -MemberType NoteProperty -Name RBAC_Delegation_Type -value "Direct" 211 | $AccObj | Add-Member -MemberType NoteProperty -Name RBAC_GroupName -value "N/A" 212 | } 213 | 214 | ElseIf ( ($Role.ObjectType -eq "Unknown") ) 215 | { 216 | # Delegated Account (direct) 217 | $DisplayName = "" 218 | $UserPrincipalName = "" 219 | $ObjectId = $Role.ObjectId 220 | $ObjectType = "Unknown" 221 | $AccEnabled = "NotFound" 222 | 223 | $AccObj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $DisplayName 224 | $AccObj | Add-Member -MemberType NoteProperty -Name UserPrincipalName -Value $UserPrincipalName 225 | $AccObj | Add-Member -MemberType NoteProperty -Name ObjectType -value $ObjectType 226 | $AccObj | Add-Member -MemberType NoteProperty -Name ObjectId -value $ObjectId 227 | $AccObj | Add-Member -MemberType NoteProperty -Name AccEnabled -value $AccEnabled 228 | $AccObj | Add-Member -MemberType NoteProperty -Name RBAC_Delegation_Type -value "Direct" 229 | $AccObj | Add-Member -MemberType NoteProperty -Name RBAC_GroupName -value "N/A" 230 | } 231 | 232 | # Delegated Account (indirect due to group) 233 | 234 | ElseIf ($Role.ObjectType -eq "Group") 235 | { 236 | $AccObj = @() 237 | $IndirectMembers = Get-AzureADGroupMember -ObjectId $Role.ObjectId -All $true | Select-Object DisplayName, SignInName, ObjectId, ObjectType 238 | ForEach ($Member in $IndirectMembers) 239 | { 240 | $AccInfo = Get-AzureADObjectByObjectId -ObjectIds $Role.ObjectId 241 | $DisplayName = $AccInfo.DisplayName 242 | $UserPrincipalName = $AccInfo.UserPrincipalName 243 | $ObjectId = $AccInfo.ObjectId 244 | $ObjectType = $AccInfo.ObjectType 245 | $AccEnabled = $AccInfo.AccountEnabled 246 | 247 | $Indirectobj = New-Object -TypeName PSObject 248 | $Indirectobj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $DisplayName 249 | $Indirectobj | Add-Member -MemberType NoteProperty -Name UserPrincipalName -Value $UserPrincipalName 250 | $Indirectobj | Add-Member -MemberType NoteProperty -Name ObjectType -value $ObjectType 251 | $Indirectobj | Add-Member -MemberType NoteProperty -Name ObjectId -value $ObjectId 252 | $Indirectobj | Add-Member -MemberType NoteProperty -Name AccEnabled -value $AccEnabled 253 | $Indirectobj | Add-Member -MemberType NoteProperty -Name RBAC_Delegation_Type -value "Group_inheritance" 254 | $Indirectobj | Add-Member -MemberType NoteProperty -Name RBAC_GroupName -value $Role.DisplayName 255 | 256 | $AccObj += $Indirectobj 257 | } 258 | } 259 | 260 | # Role Delegation 261 | $RoleDefName = $role.RoleDefinitionName 262 | $RoleDefId = $role.RoleDefinitionId 263 | 264 | # Subscription Info 265 | $SubscriptionName = $AzContext.Subscription.Name 266 | $SubscriptionId = $AzContext.Subscription.SubscriptionId 267 | 268 | # Checking for Custom Role 269 | $CustomRolesIfAny = Get-AzRoleDefinition -Name $RoleDefName 270 | $CustomRole = $CustomRolesIfAny.IsCustom 271 | 272 | # Make an array with the result - used for reporting in Excel 273 | ForEach ($Entry in $AccObj) 274 | { 275 | $Roleobj = New-Object -TypeName PSObject 276 | $Roleobj | Add-Member -MemberType NoteProperty -Name SubscriptionName -value $SubscriptionName 277 | $Roleobj | Add-Member -MemberType NoteProperty -Name SubscriptionId -value $SubscriptionId 278 | 279 | $Roleobj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $Entry.DisplayName 280 | $Roleobj | Add-Member -MemberType NoteProperty -Name UserPrincipalName -Value $Entry.UserPrincipalName 281 | $Roleobj | Add-Member -MemberType NoteProperty -Name ObjectType -value $Entry.ObjectType 282 | $Roleobj | Add-Member -MemberType NoteProperty -Name ObjectId -value $Entry.ObjectId 283 | $Roleobj | Add-Member -MemberType NoteProperty -Name AccEnabled -value $Entry.AccEnabled 284 | $Roleobj | Add-Member -MemberType NoteProperty -Name RBAC_Delegation_Type -value $Entry.RBAC_Delegation_Type 285 | $Roleobj | Add-Member -MemberType NoteProperty -Name RBAC_GroupName -value $Entry.RBAC_GroupName 286 | 287 | $Roleobj | Add-Member -MemberType NoteProperty -Name RoleDefinitionName -value $RoleDefName 288 | $Roleobj | Add-Member -MemberType NoteProperty -Name CustomRole -value $CustomRole 289 | $Roleobj | Add-Member -MemberType NoteProperty -Name Scope -value $Scope 290 | $Roleobj | Add-Member -MemberType NoteProperty -Name Scope_Delegation -value $Scope_Delegation 291 | 292 | $RBAC_RoleAssignments += $Roleobj 293 | } 294 | } 295 | } 296 | 297 | 298 | 299 | <# EXCLUDED SINCE IT IS VERY SLOW IN A LARGE ENTERPRISE SETUP !!! 300 | ######################################################### 301 | # (A2) MDC | Recommendations with SubAssessments (VERY SLOW) 302 | ######################################################### 303 | 304 | 305 | $MDC_Recommendations = @() 306 | $pageSize = 1000 307 | $iteration = 0 308 | $searchParams = @{ 309 | Query = "SecurityResources ` 310 | | where type == 'microsoft.security/assessments' ` 311 | | mvexpand Category=properties.metadata.categories ` 312 | | extend AssessmentId=id, ` 313 | AssessmentKey=name, ` 314 | ResourceId=properties.resourceDetails.Id, ` 315 | ResourceIdsplit = split(properties.resourceDetails.Id,'/'), ` 316 | RecommendationId=name, ` 317 | RecommendationName=properties.displayName, ` 318 | Source=properties.resourceDetails.Source, ` 319 | RecommendationState=properties.status.code, ` 320 | ActionDescription=properties.metadata.description, ` 321 | AssessmentType=properties.metadata.assessmentType, ` 322 | RemediationDescription=properties.metadata.remediationDescription, ` 323 | PolicyDefinitionId=properties.metadata.policyDefinitionId, ` 324 | ImplementationEffort=properties.metadata.implementationEffort, ` 325 | RecommendationSeverity=properties.metadata.severity, ` 326 | Threats=properties.metadata.threats, ` 327 | UserImpact=properties.metadata.userImpact, ` 328 | AzPortalLink=properties.links.azurePortal, ` 329 | MoreInfo=properties ` 330 | | extend ResourceSubId = tostring(ResourceIdsplit[(2)]), ` 331 | ResourceRgName = tostring(ResourceIdsplit[(4)]), ` 332 | ResourceType = tostring(ResourceIdsplit[(6)]), ` 333 | ResourceName = tostring(ResourceIdsplit[(8)]), ` 334 | FirstEvaluationDate = MoreInfo.status.firstEvaluationDate, ` 335 | StatusChangeDate = MoreInfo.status.statusChangeDate, ` 336 | Status = MoreInfo.status.code ` 337 | | join kind=leftouter (resourcecontainers | where type=='microsoft.resources/subscriptions' | project SubName=name, subscriptionId) on subscriptionId ` 338 | | where AssessmentType == 'BuiltIn' ` 339 | | project-away kind,managedBy,sku,plan,tags,identity,zones,location,ResourceIdsplit,id,name,type,resourceGroup,subscriptionId, extendedLocation,subscriptionId1 ` 340 | | project SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,TenantId=tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink, AssessmentKey ` 341 | | where RecommendationState == 'Unhealthy' ` 342 | | join kind=leftouter ( 343 | securityresources 344 | | where type == 'microsoft.security/assessments/subassessments' 345 | | extend AssessmentKey = extract('.*assessments/(.+?)/.*',1, id) 346 | | project AssessmentKey, subassessmentKey=name, id, parse_json(properties), resourceGroup, subscriptionId, tenantId 347 | | extend SubAssessmentSescription = properties.description, 348 | SubAssessmentDisplayName = properties.displayName, 349 | SubAssessmentResourceId = properties.resourceDetails.id, 350 | SubAssessmentResourceSource = properties.resourceDetails.source, 351 | SubAssessmentCategory = properties.category, 352 | SubAssessmentSeverity = properties.status.severity, 353 | SubAssessmentCode = properties.status.code, 354 | SubAssessmentTimeGenerated = properties.timeGenerated, 355 | SubAssessmentRemediation = properties.remediation, 356 | SubAssessmentImpact = properties.impact, 357 | SubAssessmentVulnId = properties.id, 358 | SubAssessmentMoreInfo = properties.additionalData, 359 | SubAssessmentMoreInfoAssessedResourceType = properties.additionalData.assessedResourceType, 360 | SubAssessmentMoreInfoData = properties.additionalData.data 361 | ) on AssessmentKey" 362 | First = $pageSize 363 | } 364 | 365 | $results = do { 366 | $iteration += 1 367 | Write-Verbose "Iteration #$iteration" -Verbose 368 | $pageResults = Search-AzGraph @searchParams -ManagementGroup $Global:ManagementGroupScope 369 | $searchParams.Skip += $pageResults.Count 370 | $MDC_Recommendations += $pageResults 371 | } while ($pageResults.Count -eq $pageSize) 372 | #> 373 | 374 | ################################################ 375 | # (A2) MDC | Recommendations with link 376 | ################################################ 377 | 378 | $MDC_Recommendations = @() 379 | $pageSize = 1000 380 | $iteration = 0 381 | $searchParams = @{ 382 | Query = "SecurityResources ` 383 | | where type == 'microsoft.security/assessments' ` 384 | | mvexpand Category=properties.metadata.categories ` 385 | | extend AssessmentId=id, ` 386 | AssessmentKey=name, ` 387 | ResourceId=properties.resourceDetails.Id, ` 388 | ResourceIdsplit = split(properties.resourceDetails.Id,'/'), ` 389 | RecommendationId=name, ` 390 | RecommendationName=properties.displayName, ` 391 | Source=properties.resourceDetails.Source, ` 392 | RecommendationState=properties.status.code, ` 393 | ActionDescription=properties.metadata.description, ` 394 | AssessmentType=properties.metadata.assessmentType, ` 395 | RemediationDescription=properties.metadata.remediationDescription, ` 396 | PolicyDefinitionId=properties.metadata.policyDefinitionId, ` 397 | ImplementationEffort=properties.metadata.implementationEffort, ` 398 | RecommendationSeverity=properties.metadata.severity, ` 399 | Threats=properties.metadata.threats, ` 400 | UserImpact=properties.metadata.userImpact, ` 401 | AzPortalLink=properties.links.azurePortal, ` 402 | MoreInfo=properties ` 403 | | extend ResourceSubId = tostring(ResourceIdsplit[(2)]), ` 404 | ResourceRgName = tostring(ResourceIdsplit[(4)]), ` 405 | ResourceType = tostring(ResourceIdsplit[(6)]), ` 406 | ResourceName = tostring(ResourceIdsplit[(8)]), ` 407 | FirstEvaluationDate = MoreInfo.status.firstEvaluationDate, ` 408 | StatusChangeDate = MoreInfo.status.statusChangeDate, ` 409 | Status = MoreInfo.status.code ` 410 | | join kind=leftouter (resourcecontainers | where type=='microsoft.resources/subscriptions' | project SubName=name, subscriptionId) on subscriptionId ` 411 | | where AssessmentType == 'BuiltIn' ` 412 | | project-away kind,managedBy,sku,plan,tags,identity,zones,location,ResourceIdsplit,id,name,type,resourceGroup,subscriptionId, extendedLocation,subscriptionId1 ` 413 | | project SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,TenantId=tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink, AssessmentKey ` 414 | | where RecommendationState == 'Unhealthy' " 415 | First = $pageSize 416 | } 417 | 418 | $results = do { 419 | $iteration += 1 420 | Write-Verbose "Iteration #$iteration" -Verbose 421 | $pageResults = Search-AzGraph @searchParams -ManagementGroup $Global:ManagementGroupScope 422 | $searchParams.Skip += $pageResults.Count 423 | $MDC_Recommendations += $pageResults 424 | } while ($pageResults.Count -eq $pageSize) 425 | 426 | 427 | ######################################################### 428 | # (A3) MDC | SubAssessments (Detailed Infomation) 429 | ######################################################### 430 | 431 | $MDC_Recommendations_SubAssessments = @() 432 | $pageSize = 1000 433 | $iteration = 0 434 | $searchParams = @{ 435 | Query = "SecurityResources ` 436 | | where type == 'microsoft.security/assessments/subassessments' 437 | | extend AssessmentKey = extract('.*assessments/(.+?)/.*',1, id) 438 | | project AssessmentKey, subassessmentKey=name, id, parse_json(properties), resourceGroup, subscriptionId, tenantId 439 | | extend SubAssessDescription = properties.description, 440 | SubAssessDisplayName = properties.displayName, 441 | SubAssessResourceId = properties.resourceDetails.id, 442 | SubAssessResourceSource = properties.resourceDetails.source, 443 | SubAssessCategory = properties.category, 444 | SubAssessSeverity = properties.status.severity, 445 | SubAssessCode = properties.status.code, 446 | SubAssessTimeGenerated = properties.timeGenerated, 447 | SubAssessRemediation = properties.remediation, 448 | SubAssessImpact = properties.impact, 449 | SubAssessVulnId = properties.id, 450 | SubAssessMoreInfo = properties.additionalData, 451 | SubAssessMoreInfoAssessedResourceType = properties.additionalData.assessedResourceType, 452 | SubAssessMoreInfoData = properties.additionalData.data ` 453 | | join kind=leftouter (resourcecontainers | where type=='microsoft.resources/subscriptions' | project SubName=name, subscriptionId) on subscriptionId " 454 | First = $pageSize 455 | } 456 | 457 | $results = do { 458 | $iteration += 1 459 | Write-Verbose "Iteration #$iteration" -Verbose 460 | $pageResults = Search-AzGraph @searchParams -ManagementGroup $Global:ManagementGroupScope 461 | $searchParams.Skip += $pageResults.Count 462 | $MDC_Recommendations_SubAssessments += $pageResults 463 | } while ($pageResults.Count -eq $pageSize) 464 | 465 | ################################################### 466 | # (A3.1) SubAssessment Identity Lookup 467 | ################################################### 468 | 469 | # Identities 470 | $Identities_SubAssessments_Full = $MDC_Recommendations_SubAssessments | where-Object { $_.SubAssessResourceId -like "*/identities/*" } 471 | 472 | $Identities_SubAssessments_Obj = @() 473 | ForEach ($Obj in $Identities_SubAssessments_Full) 474 | { 475 | $Identities_SubAssessments_Obj += ($Obj.SubAssessResourceId.split("/"))[4] 476 | } 477 | $Identities_SubAssessments_List = $Identities_SubAssessments_Obj | Sort-Object -Unique 478 | 479 | $IdArray = @() 480 | ForEach ($IdObj in $Identities_SubAssessments_List) 481 | { 482 | $ObjInfo = Get-AzureADObjectByObjectId -ObjectIds $IdObj 483 | $Object = New-Object -TypeName PSObject 484 | $Object | Add-Member -MemberType NoteProperty -Name ObjectId -value $ObjInfo.ObjectId 485 | $Object | Add-Member -MemberType NoteProperty -Name AccountEnabled -value $ObjInfo.AccountEnabled 486 | $Object | Add-Member -MemberType NoteProperty -Name UserPrincipalName -value $ObjInfo.UserPrincipalName 487 | $Object | Add-Member -MemberType NoteProperty -Name DisplayName -value $ObjInfo.DisplayName 488 | Write-Output "Checking $($ObjInfo.UserPrincipalName)" 489 | $IdArray += $Object 490 | } 491 | 492 | $MDC_Recommendations_SubAssessments_Count = $MDC_Recommendations_SubAssessments.count 493 | $Iteration = 0 494 | 495 | Do 496 | { 497 | Write-Output "Processing $($Iteration) / $($MDC_Recommendations_SubAssessments_Count) ..." 498 | $Iteration += 1 499 | If ($MDC_Recommendations_SubAssessments[$Iteration].SubAssessResourceId -like "*/identities/*") 500 | { 501 | $Identities_SubAssessments_Obj = $MDC_Recommendations_SubAssessments[$Iteration].SubAssessResourceId.split("/")[4] 502 | $UserInfo = $IdArray | Where-Object { $_.ObjectId -eq $Identities_SubAssessments_Obj } 503 | $MDC_Recommendations_SubAssessments[$Iteration] | Add-Member -MemberType NoteProperty -Name SubAssessResObjectId -value $UserInfo.ObjectId -Force 504 | $MDC_Recommendations_SubAssessments[$Iteration] | Add-Member -MemberType NoteProperty -Name SubAssessResAccountEnabled -value $UserInfo.AccountEnabled -Force 505 | $MDC_Recommendations_SubAssessments[$Iteration] | Add-Member -MemberType NoteProperty -Name SubAssessResUserPrincipalName -value $UserInfo.UserPrincipalName -Force 506 | $MDC_Recommendations_SubAssessments[$Iteration] | Add-Member -MemberType NoteProperty -Name SubAssessResDisplayName -value $UserInfo.DisplayName -Force 507 | } 508 | Else 509 | { 510 | $MDC_Recommendations_SubAssessments[$Iteration] | Add-Member -MemberType NoteProperty -Name SubAssessResObjectId -value "" -Force 511 | $MDC_Recommendations_SubAssessments[$Iteration] | Add-Member -MemberType NoteProperty -Name SubAssessResAccountEnabled -value "" -Force 512 | $MDC_Recommendations_SubAssessments[$Iteration] | Add-Member -MemberType NoteProperty -Name SubAssessResUserPrincipalName -value "" -Force 513 | $MDC_Recommendations_SubAssessments[$Iteration] | Add-Member -MemberType NoteProperty -Name SubAssessResDisplayName -value "" -Force 514 | } 515 | } 516 | Until ($Iteration -eq $MDC_Recommendations_SubAssessments_Count) 517 | 518 | 519 | ####################### 520 | # (B1) Filtering of Scope 521 | ####################### 522 | 523 | $MDC_Recommendations_Unhealthy = $MDC_Recommendations | Where-Object {$_.MoreInfo.status.code -eq "Unhealthy" } 524 | 525 | # Scope for pivot 526 | $Category_SubLevel = $MDC_Recommendations_Unhealthy | Where-Object { ($_.resourceName -eq $null) -or ($_.resourceName -eq "") } | Select-Object SubName, ResourceSubId, tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 527 | $Category_ResourceLevel = $MDC_Recommendations_Unhealthy | Where-Object { ($_.resourceName -ne $null) -and ($_.resourceName -ne "") } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 528 | $Category_Networking = $MDC_Recommendations_Unhealthy | Where-Object { ($_.Category -eq "Networking") } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 529 | $Category_AppServices = $MDC_Recommendations_Unhealthy | Where-Object { ($_.Category -eq "AppServices") } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 530 | $Category_Compute = $MDC_Recommendations_Unhealthy | Where-Object { ($_.Category -eq "Compute") } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 531 | $Category_Container = $MDC_Recommendations_Unhealthy | Where-Object { ($_.Category -eq "Container") } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 532 | $Category_Data = $MDC_Recommendations_Unhealthy | Where-Object { ($_.Category -eq "Data") } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 533 | $Category_IoT = $MDC_Recommendations_Unhealthy | Where-Object { ($_.Category -eq "IoT") } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 534 | $Category_IdentityAndAccess = $MDC_Recommendations_Unhealthy | Where-Object { ($_.Category -eq "IdentityAndAccess") } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 535 | $Category_Other = $MDC_Recommendations_Unhealthy | Where-Object { ( ($_.Category -ne "Networking") -and ` 536 | ($_.Category -ne "AppServices") -and ` 537 | ($_.Category -ne "Compute") -and ` 538 | ($_.Category -ne "Container") -and ` 539 | ($_.Category -ne "Data") -and ` 540 | ($_.Category -ne "IoT") -and ` 541 | ($_.Category -ne "IdentityAndAccess") ) } | Select-Object SubName, ResourceSubId, ResourceRgName,ResourceType,ResourceName,tenantId, RecommendationName, RecommendationId, RecommendationState, RecommendationSeverity, AssessmentType, PolicyDefinitionId, ImplementationEffort, UserImpact, Category, Threats, Source, ActionDescription, RemediationDescription, MoreInfo, ResourceId, AzPortalLink 542 | 543 | 544 | 545 | $RBAC_Scope_Delegation_Direct = $RBAC_RoleAssignments | Where-Object { $_.Scope_Delegation -like "Direct*" } 546 | $RBAC_Scope_Delegation_Inheritance_MG = $RBAC_RoleAssignments | Where-Object { $_.Scope_Delegation -eq "Inheritance_MG" } 547 | 548 | $RBAC_Delegation_Type_Direct_Sub = $RBAC_RoleAssignments | Where-Object { ($_.RBAC_Delegation_Type -eq "Direct") -and ($_.Scope_Delegation -eq 'Direct_SUB') } 549 | 550 | # Sub - RBAC_Delegation_Type_Direct_FilterAway 551 | 552 | $RBAC_Delegation_Type_Direct_Sub_Filtered = $RBAC_Delegation_Type_Direct_Sub 553 | 554 | <# 555 | Enable these 2 filters, if you want to remove these from the overview 556 | 557 | # Filter away User Access Administrator Role Definininition Name (inherited) 558 | $RBAC_Delegation_Type_Direct_Sub_Filtered = $RBAC_Delegation_Type_Direct_Sub_Filtered | Where-Object { $_.RoleDefinitionName -ne "User Access Administrator" } 559 | 560 | # Filter away DisplayName startswith Defender* 561 | $RBAC_Delegation_Type_Direct_Sub_Filtered = $RBAC_Delegation_Type_Direct_Sub_Filtered | Where-Object { $_.DisplayName -notlike "Defender*" } 562 | #> 563 | 564 | $RBAC_Delegation_Type_Direct_Mg = $RBAC_RoleAssignments | Where-Object { ($_.RBAC_Delegation_Type -eq "Direct") -and ($_.Scope_Delegation -eq 'Inheritance_MG') } 565 | 566 | # MG - RBAC_Delegation_Type_Direct_FilterAway 567 | 568 | $RBAC_Delegation_Type_Direct_Mg_Filtered = $RBAC_Delegation_Type_Direct_Mg 569 | 570 | <# 571 | Enable this filter, if you want to remove these from the overview 572 | 573 | # Filter away DisplayName startswith Defender* 574 | $RBAC_Delegation_Type_Direct_Mg_Filtered = $RBAC_Delegation_Type_Direct_Mg_Filtered | Where-Object { $_.DisplayName -notlike "Defender*" } 575 | #> 576 | 577 | ######################################################### 578 | # (C1) Reporting | Export to Excel 579 | ######################################################### 580 | 581 | Remove-Item $FileOutput 582 | 583 | #-------------------------------------- 584 | 585 | # Content 586 | # Purpose: Show introduction of tables and pivots 587 | 588 | $TableName = "Introduction_Help" 589 | $TableSelection = Import-csv $HelpContentFile -Delimiter ";" -Encoding UTF8 590 | Write-Output "Exporting $($TableName) .... Please Wait !" 591 | $excel = $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 -PassThru 592 | $excel.Workbook.Worksheets[1].Column(1) | Set-Format -VerticalAlignment Top -HorizontalAlignment Left -WrapText -Width 35 593 | $excel.Workbook.Worksheets[1].Column(2) | Set-Format -VerticalAlignment Top -HorizontalAlignment Left -WrapText -Width 90 594 | $excel.Workbook.Worksheets[1].Column(3) | Set-Format -VerticalAlignment Top -HorizontalAlignment Left -WrapText -Width 40 595 | Close-ExcelPackage $excel 596 | 597 | 598 | #-------------------------------------- 599 | 600 | # Unhealthy_All 601 | # Purpose: Show All Unhealthy recommendations 602 | 603 | $TableName = "Unhealthy_All" 604 | $TableSelection = $MDC_Recommendations_Unhealthy 605 | If ($TableSelection) 606 | { 607 | Write-Output "Exporting $($TableName) .... Please Wait !" 608 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 609 | } 610 | #-------------------------------------- 611 | 612 | # Unhealthy_High 613 | # Purpose: Show all Unhealthy recommendations with High priority 614 | 615 | $TableName = "Unhealthy_High" 616 | $TableSelection = $MDC_Recommendations_Unhealthy | Where-Object { $_.recommendationSeverity -eq "High" } 617 | If ($TableSelection) 618 | { 619 | Write-Output "Exporting $($TableName) .... Please Wait !" 620 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 621 | } 622 | 623 | #-------------------------------------- 624 | 625 | # Unhealthy_Medium 626 | # Purpose: Show all Unhealthy recommendations with medium priority 627 | 628 | $TableName = "Unhealthy_Medium" 629 | $TableSelection = $MDC_Recommendations_Unhealthy | Where-Object { $_.recommendationSeverity -eq "Medium" } 630 | Write-Output "Exporting $($TableName) .... Please Wait !" 631 | If ($TableSelection) 632 | { 633 | Write-Output "Exporting $($TableName) .... Please Wait !" 634 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 635 | } 636 | 637 | #-------------------------------------- 638 | 639 | # Unhealthy_Low 640 | # Purpose: Show all Unhealthy recommendations with Low priority 641 | 642 | $TableName = "Unhealthy_Low" 643 | $TableSelection = $MDC_Recommendations_Unhealthy | Where-Object { $_.recommendationSeverity -eq "Low" } 644 | If ($TableSelection) 645 | { 646 | Write-Output "Exporting $($TableName) .... Please Wait !" 647 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 648 | } 649 | 650 | #-------------------------------------- 651 | 652 | # Unhealthy_All_SubLevel 653 | # Purpose: Show all Unhealthy recommendations where the target is on subcription-level 654 | 655 | $TableName = "Unhealthy_All_SubLevel" 656 | $TableSelection = $Category_SubLevel 657 | If ($TableSelection) 658 | { 659 | Write-Output "Exporting $($TableName) .... Please Wait !" 660 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 661 | } 662 | 663 | #-------------------------------------- 664 | # Unhealthy_All_ResourceLevel 665 | # Purpose: Show all Unhealthy recommendations where the target is on resource-level 666 | 667 | $TableName = "Unhealthy_All_ResourceLevel" 668 | $TableSelection = $Category_ResourceLevel 669 | If ($TableSelection) 670 | { 671 | Write-Output "Exporting $($TableName) .... Please Wait !" 672 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 673 | } 674 | 675 | #-------------------------------------- 676 | # Unhealthy_Category_Networking 677 | # Purpose: Show all Unhealthy recommendations related to Networking category 678 | 679 | $TableName = "Unhealthy_Category_Networking" 680 | $TableSelection = $Category_Networking 681 | If ($TableSelection) 682 | { 683 | Write-Output "Exporting $($TableName) .... Please Wait !" 684 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 685 | } 686 | 687 | #-------------------------------------- 688 | # Unhealthy_Category_AppServices 689 | # Purpose: Show all Unhealthy recommendations related to AppServices category 690 | 691 | $TableName = "Unhealthy_Category_AppServices" 692 | $TableSelection = $Category_AppServices 693 | If ($TableSelection) 694 | { 695 | Write-Output "Exporting $($TableName) .... Please Wait !" 696 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 697 | } 698 | 699 | #-------------------------------------- 700 | # Unhealthy_Category_Compute 701 | # Purpose: Show all Unhealthy recommendations related to Compute category 702 | 703 | $TableName = "Unhealthy_Category_Compute" 704 | $TableSelection = $Category_Compute 705 | If ($TableSelection) 706 | { 707 | Write-Output "Exporting $($TableName) .... Please Wait !" 708 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 709 | } 710 | 711 | #-------------------------------------- 712 | # Unhealthy_Category_Container 713 | # Purpose: Show all Unhealthy recommendations related to Container category 714 | 715 | $TableName = "Unhealthy_Category_Container" 716 | $TableSelection = $Category_Container 717 | If ($TableSelection) 718 | { 719 | Write-Output "Exporting $($TableName) .... Please Wait !" 720 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 721 | } 722 | 723 | #-------------------------------------- 724 | # Unhealthy_Category_Data 725 | # Purpose: Show all Unhealthy recommendations related to Data category 726 | 727 | $TableName = "Unhealthy_Category_Data" 728 | $TableSelection = $Category_Data 729 | If ($TableSelection) 730 | { 731 | Write-Output "Exporting $($TableName) .... Please Wait !" 732 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 733 | } 734 | 735 | #-------------------------------------- 736 | # Unhealthy_Category_IoT 737 | # Purpose: Show all Unhealthy recommendations related to IoT category 738 | 739 | $TableName = "Unhealthy_Category_IoT" 740 | $TableSelection = $Category_IoT 741 | If ($TableSelection) 742 | { 743 | Write-Output "Exporting $($TableName) .... Please Wait !" 744 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 745 | } 746 | 747 | #-------------------------------------- 748 | # Unhealthy_Category_Id_Access 749 | # Purpose: Show all Unhealthy recommendations related to IdentityAndAccess category 750 | 751 | $TableName = "Unhealthy_Category_Id_Access" 752 | $TableSelection = $Category_IdentityAndAccess 753 | If ($TableSelection) 754 | { 755 | Write-Output "Exporting $($TableName) .... Please Wait !" 756 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 757 | } 758 | 759 | #-------------------------------------- 760 | # Unhealthy_Category_Other 761 | # Purpose: Show all Unhealthy recommendations related to Other categories 762 | 763 | $TableName = "Unhealthy_Category_Other" 764 | $TableSelection = $Category_Other 765 | If ($TableSelection) 766 | { 767 | Write-Output "Exporting $($TableName) .... Please Wait !" 768 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 769 | } 770 | 771 | #-------------------------------------- 772 | # SubAssess_All 773 | # Purpose: Show all detailed information (SubAssessments) 774 | 775 | $TableName = "SubAssess_All" 776 | $TableSelection = $MDC_Recommendations_SubAssessments | Select-Object SubName,subscriptionId,SubAssessDisplayName,SubAssessResourceId,SubAssessResObjectId,SubAssessResDisplayName,SubAssessResUserPrincipalName,SubAssessResAccountEnabled,SubAssessResourceSource,SubAssessCategory,SubAssessSeverity,SubAssessCode,SubAssessTimeGenerated,SubAssessRemediation,SubAssessImpact,SubAssessVulnId 777 | If ($TableSelection) 778 | { 779 | Write-Output "Exporting $($TableName) .... Please Wait !" 780 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 781 | } 782 | 783 | #-------------------------------------- 784 | # SubAssess_Identity 785 | # Purpose: Show all identity detailed information (SubAssessments). 786 | 787 | $TableName = "SubAssess_Identity" 788 | $TableSelection = $MDC_Recommendations_SubAssessments | Where-Object { $_.SubAssessResUserPrincipalName -ne "" } | Select-Object SubName,subscriptionId,SubAssessDisplayName,SubAssessResourceId,SubAssessResObjectId,SubAssessResDisplayName,SubAssessResUserPrincipalName,SubAssessResAccountEnabled,SubAssessResourceSource,SubAssessCategory,SubAssessSeverity,SubAssessCode,SubAssessTimeGenerated,SubAssessRemediation,SubAssessImpact,SubAssessVulnId 789 | If ($TableSelection) 790 | { 791 | Write-Output "Exporting $($TableName) .... Please Wait !" 792 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 793 | } 794 | 795 | #-------------------------------------- 796 | # SubAssess_Other 797 | # Purpose: Show all other, non-identity detailed information (SubAssessments) 798 | 799 | $TableName = "SubAssess_Other" 800 | $TableSelection = $MDC_Recommendations_SubAssessments | Where-Object { $_.SubAssessResUserPrincipalName -eq "" } | Select-Object SubName,subscriptionId,SubAssessDisplayName,SubAssessResourceId,SubAssessResObjectId,SubAssessResDisplayName,SubAssessResUserPrincipalName,SubAssessResAccountEnabled,SubAssessResourceSource,SubAssessCategory,SubAssessSeverity,SubAssessCode,SubAssessTimeGenerated,SubAssessRemediation,SubAssessImpact,SubAssessVulnId 801 | If ($TableSelection) 802 | { 803 | Write-Output "Exporting $($TableName) .... Please Wait !" 804 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 805 | } 806 | 807 | #-------------------------------------- 808 | # RBAC_RoleAssignments 809 | # Purpose: Show all Azure RBAC Role Assignments 810 | 811 | $TableName = "RBAC_RoleAssignments" 812 | $TableSelection = $RBAC_RoleAssignments 813 | If ($TableSelection) 814 | { 815 | Write-Output "Exporting $($TableName) .... Please Wait !" 816 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 817 | } 818 | 819 | 820 | #-------------------------------------- 821 | # RBAC_Direct_Sublevel 822 | # Purpose: Show all Azure RBAC Role Assignments directly on Sub-level (not part of group) 823 | 824 | $TableName = "RBAC_Direct_Sublevel" 825 | $TableSelection = $RBAC_Delegation_Type_Direct_Sub_Filtered 826 | If ($TableSelection) 827 | { 828 | Write-Output "Exporting $($TableName) .... Please Wait !" 829 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 830 | } 831 | 832 | #-------------------------------------- 833 | # RBAC_Direct_Mglevel 834 | # Purpose: Show all Azure RBAC Role Assignments directly on Mg-level (not part of group) 835 | 836 | $TableName = "RBAC_Direct_Mglevel" 837 | $TableSelection = $RBAC_Delegation_Type_Direct_Mg_Filtered 838 | If ($TableSelection) 839 | { 840 | Write-Output "Exporting $($TableName) .... Please Wait !" 841 | $TableSelection | Export-Excel -Path $FileOutput -WorksheetName $TableName -AutoFilter -AutoSize -BoldTopRow -tablename $TableName -tablestyle Medium13 842 | } 843 | 844 | #-------------------------------------- 845 | # Excel export - prepare pivots 846 | 847 | Write-Output "Preparing Pivot tables .... Please Wait !" 848 | $Pivottable = [ordered]@{} 849 | 850 | #-------------------------------------- 851 | 852 | <# 853 | PT_CATEGORY_SUBLEVEL 854 | Purpose: Prioritize recommendations based on Category and RecommendationSeverity 855 | SourceWorkSheet: Unhealthy_All_SubLevel 856 | Sort-order: 857 | (1) Category (Storage, Network, Identity, etc.) 858 | (2) RecommendationSeverity (High, Medium, Low) 859 | (3) RecommendationName 860 | (4) SubName (suscription name) 861 | #> 862 | If ($Category_SubLevel) 863 | { 864 | $Pivottable.PT_CATEGORY_SUBLEVEL = @{ SourceWorkSheet = "Unhealthy_All_SubLevel" 865 | PivotRows = @('Category','RecommendationSeverity','RecommendationName','SubName') 866 | PivotData = @{"RecommendationId"="Count"} 867 | } 868 | } 869 | 870 | 871 | #-------------------------------------- 872 | 873 | <# 874 | PT_CATEGORY_RESOURCELEVEL 875 | Purpose: Prioritize recommendations based on ResourceType and RecommendationSeverity 876 | SourceWorkSheet: Unhealthy_All_ResourceLevel 877 | Sort-order: 878 | (1) Category (Storage, Network, Identity, etc.) 879 | (2) RecommendationSeverity (High, Medium, Low) 880 | (3) RecommendationName 881 | (4) SubName (suscription name) 882 | (5) ResourceRgName 883 | (6) ResourceName 884 | #> 885 | 886 | If ($Category_ResourceLevel) 887 | { 888 | $Pivottable.PT_CATEGORY_RESOURCELEVEL = @{ SourceWorkSheet = "Unhealthy_ALL_ResourceLevel" 889 | PivotRows = @('Category','RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 890 | PivotData = @{"RecommendationId"="Count"} 891 | } 892 | } 893 | 894 | #-------------------------------------- 895 | 896 | <# 897 | PT_CATEGORY_NETWORKING 898 | Purpose: Prioritize networking recommendations based on RecommendationSeverity 899 | SourceWorkSheet: Unhealthy_Category_Networking 900 | Sort-order: 901 | (1) RecommendationSeverity (High, Medium, Low) 902 | (2) RecommendationName 903 | (3) SubName (suscription name) 904 | (4) ResourceRgName 905 | (5) ResourceName 906 | #> 907 | 908 | If ($Category_Networking) 909 | { 910 | $Pivottable.PT_CATEGORY_NETWORKING = @{ SourceWorkSheet = "Unhealthy_Category_Networking" 911 | PivotRows = @('RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 912 | PivotData = @{"RecommendationId"="Count"} 913 | } 914 | } 915 | 916 | #-------------------------------------- 917 | 918 | <# 919 | PT_CATEGORY_APPSERVICES 920 | Purpose: Prioritize AppServices recommendations based on RecommendationSeverity 921 | SourceWorkSheet: Unhealthy_Category_AppServices 922 | Sort-order: 923 | (1) RecommendationSeverity (High, Medium, Low) 924 | (2) RecommendationName 925 | (3) SubName (suscription name) 926 | (4) ResourceRgName 927 | (5) ResourceName 928 | #> 929 | 930 | If ($Category_AppServices) 931 | { 932 | $Pivottable.PT_CATEGORY_APPSERVICES = @{ SourceWorkSheet = "Unhealthy_Category_AppServices" 933 | PivotRows = @('RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 934 | PivotData = @{"RecommendationId"="Count"} 935 | } 936 | } 937 | 938 | #-------------------------------------- 939 | 940 | <# 941 | PT_CATEGORY_COMPUTE 942 | Purpose: Prioritize Compute recommendations based on RecommendationSeverity 943 | SourceWorkSheet: Unhealthy_Category_Compute 944 | Sort-order: 945 | (1) RecommendationSeverity (High, Medium, Low) 946 | (2) RecommendationName 947 | (3) SubName (suscription name) 948 | (4) ResourceRgName 949 | (5) ResourceName 950 | #> 951 | 952 | If ($Category_Compute) 953 | { 954 | $Pivottable.PT_CATEGORY_COMPUTE = @{ SourceWorkSheet = "Unhealthy_Category_Compute" 955 | PivotRows = @('RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 956 | PivotData = @{"RecommendationId"="Count"} 957 | } 958 | 959 | } 960 | 961 | #-------------------------------------- 962 | 963 | <# 964 | PT_CATEGORY_CONTAINER 965 | Purpose: Prioritize Container recommendations based on RecommendationSeverity 966 | SourceWorkSheet: Unhealthy_Category_Container 967 | Sort-order: 968 | (1) RecommendationSeverity (High, Medium, Low) 969 | (2) RecommendationName 970 | (3) SubName (suscription name) 971 | (4) ResourceRgName 972 | (5) ResourceName 973 | #> 974 | 975 | If ($Category_Container) 976 | { 977 | $Pivottable.PT_CATEGORY_CONTAINER = @{ SourceWorkSheet = "Unhealthy_Category_Container" 978 | PivotRows = @('RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 979 | PivotData = @{"RecommendationId"="Count"} 980 | } 981 | } 982 | 983 | #-------------------------------------- 984 | 985 | <# 986 | PT_CATEGORY_DATA 987 | Purpose: Prioritize Data recommendations based on RecommendationSeverity 988 | SourceWorkSheet: Unhealthy_Category_Data 989 | Sort-order: 990 | (1) RecommendationSeverity (High, Medium, Low) 991 | (2) RecommendationName 992 | (3) SubName (suscription name) 993 | (4) ResourceRgName 994 | (5) ResourceName 995 | #> 996 | 997 | If ($Category_Data) 998 | { 999 | $Pivottable.PT_CATEGORY_DATA = @{ SourceWorkSheet = "Unhealthy_Category_Data" 1000 | PivotRows = @('RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 1001 | PivotData = @{"RecommendationId"="Count"} 1002 | } 1003 | } 1004 | 1005 | #-------------------------------------- 1006 | 1007 | <# 1008 | PT_CATEGORY_ID_ACCESS 1009 | Purpose: Prioritize Identity and Access recommendations based on RecommendationSeverity 1010 | SourceWorkSheet: Unhealthy_Category_IdentityAndAccess 1011 | Sort-order: 1012 | (1) RecommendationSeverity (High, Medium, Low) 1013 | (2) RecommendationName 1014 | (3) SubName (suscription name) 1015 | (4) ResourceRgName 1016 | (5) ResourceName 1017 | #> 1018 | 1019 | If ($Category_IdentityAndAccess) 1020 | { 1021 | $Pivottable.PT_CATEGORY_ID_ACCESS = @{ SourceWorkSheet = "Unhealthy_Category_Id_Access" 1022 | PivotRows = @('RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 1023 | PivotData = @{"RecommendationId"="Count"} 1024 | } 1025 | } 1026 | 1027 | #-------------------------------------- 1028 | 1029 | <# 1030 | PT_CATEGORY_IOT 1031 | Purpose: Prioritize IoT recommendations based on RecommendationSeverity 1032 | SourceWorkSheet: Unhealthy_Category_IoT 1033 | Sort-order: 1034 | (1) RecommendationSeverity (High, Medium, Low) 1035 | (2) RecommendationName 1036 | (3) SubName (suscription name) 1037 | (4) ResourceRgName 1038 | (5) ResourceName 1039 | #> 1040 | 1041 | If ($Category_IoT) 1042 | { 1043 | $Pivottable.PT_CATEGORY_IOT = @{ SourceWorkSheet = "Unhealthy_Category_IoT" 1044 | PivotRows = @('RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 1045 | PivotData = @{"RecommendationId"="Count"} 1046 | } 1047 | } 1048 | 1049 | #-------------------------------------- 1050 | 1051 | <# 1052 | PT_CATEGORY_OTHER 1053 | Purpose: Prioritize other recommendations based on RecommendationSeverity 1054 | SourceWorkSheet: Unhealthy_Category_Other 1055 | Sort-order: 1056 | (1) RecommendationSeverity (High, Medium, Low) 1057 | (2) RecommendationName 1058 | (3) SubName (suscription name) 1059 | (4) ResourceRgName 1060 | (5) ResourceName 1061 | #> 1062 | 1063 | If ($Category_Other) 1064 | { 1065 | $Pivottable.PT_CATEGORY_OTHER = @{ SourceWorkSheet = "Unhealthy_Category_Other" 1066 | PivotRows = @('RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 1067 | PivotData = @{"RecommendationId"="Count"} 1068 | } 1069 | } 1070 | 1071 | #-------------------------------------- 1072 | 1073 | <# 1074 | PT_RESOURCETYPE 1075 | Purpose: Prioritize recommendations based on ResourceType and RecommendationSeverity 1076 | SourceWorkSheet: Unhealthy_All_ResourceLevel 1077 | Sort-order: 1078 | (1) ResourceType (SQL, Keyvault, Storage, Network, Identity, etc.) 1079 | (2) RecommendationSeverity (High, Medium, Low) 1080 | (3) RecommendationName 1081 | (4) SubName (suscription name) 1082 | (5) ResourceRgName 1083 | (6) ResourceName 1084 | #> 1085 | 1086 | If ($Category_ResourceLevel) 1087 | { 1088 | $Pivottable.PT_RESOURCETYPE = @{ SourceWorkSheet = "Unhealthy_All_ResourceLevel" 1089 | PivotRows = @('ResourceType','RecommendationSeverity','RecommendationName','SubName','ResourceRgName','ResourceName') 1090 | PivotData = @{"RecommendationId"="Count"} 1091 | } 1092 | } 1093 | 1094 | #-------------------------------------- 1095 | 1096 | <# 1097 | PT_SUBASSESS_IDENTITY 1098 | Purpose: Prioritize recommendations based on SubAssessmentSeverity (identity-related) 1099 | SourceWorkSheet: Unhealthy_All_ResourceLevel 1100 | Sort-order: 1101 | (1) SubAssessSeverity (High, Medium, Low) 1102 | (2) SubAssessDisplayName (recommendation) 1103 | (3) SubAssessResDisplayName (resource) 1104 | (4) SubAssessResUserPrincipalName (resource) 1105 | (5) SubName (suscription name) 1106 | #> 1107 | 1108 | If ($MDC_Recommendations_SubAssessments) 1109 | { 1110 | $Pivottable.PT_SUBASSESS_IDENTITY = @{ SourceWorkSheet = "SubAssess_Identity" 1111 | PivotRows = @('SubAssessSeverity','SubAssessDisplayName','SubAssessResDisplayName','SubAssessResUserPrincipalName','SubName') 1112 | PivotData = @{"SubAssessCode"="Count"} 1113 | } 1114 | } 1115 | 1116 | #-------------------------------------- 1117 | 1118 | <# 1119 | PT_SUBASSESS_OTHER 1120 | Purpose: Prioritize recommendations based on SubAssessmentSeverity (non-identity related) 1121 | SourceWorkSheet: SubAssess_Other 1122 | Sort-order: 1123 | (1) SubAssessSeverity (High, Medium, Low) 1124 | (2) SubAssessDisplayName (recommendation) 1125 | (3) SubAssessResDisplayName (resource) 1126 | (4) SubAssessResUserPrincipalName (resource) 1127 | (5) SubName (suscription name) 1128 | #> 1129 | 1130 | If ($MDC_Recommendations_SubAssessments) 1131 | { 1132 | $Pivottable.PT_SUBASSESS_OTHER = @{ SourceWorkSheet = "SubAssess_Other" 1133 | PivotRows = @('SubAssessSeverity','SubAssessDisplayName','SubName','SubAssessResourceId') 1134 | PivotData = @{"SubAssessCode"="Count"} 1135 | } 1136 | } 1137 | 1138 | #-------------------------------------- 1139 | <# 1140 | PT_RBAC_ROLEDEF 1141 | Purpose: Detail RBAC permissions, sorted by RoleDefinitionName and Scope_Delegation 1142 | SourceWorkSheet: RBAC_RoleAssignments 1143 | Sort-order: 1144 | (1) SubscriptionName 1145 | (2) RoleDefinitionName (Contributor, Owner, Cost Management Reader, etc.) 1146 | (3) Scope_Delegation (Direct_SUB, Direct_RG, Inheritance_MG) 1147 | (4) Scope (management group name or /) 1148 | (5) RBAC_Delegation_Type (Group_inheritance, Direct) 1149 | (6) RBAC_GroupName 1150 | (7) ObjectType 1151 | (8) DisplayName 1152 | (9) UserPrincipalName 1153 | #> 1154 | 1155 | If ($RBAC_RoleAssignments) 1156 | { 1157 | $Pivottable.PT_RBAC_ROLEDEF = @{ SourceWorkSheet = "RBAC_RoleAssignments" 1158 | PivotRows = @('SubscriptionName','RoleDefinitionName','Scope_Delegation','Scope','RBAC_Delegation_Type','RBAC_GroupName','ObjectType','DisplayName','UserPrincipalName') 1159 | PivotData = @{"ObjectId"="Count"} 1160 | } 1161 | } 1162 | 1163 | #-------------------------------------- 1164 | <# 1165 | PT_RBAC_SCOPE_DELEGATION 1166 | Purpose: Detail RBAC permissions, sorted by Scope_Delegation and RoleDefinitionName 1167 | SourceWorkSheet: RBAC_RoleAssignments 1168 | Sort-order: 1169 | (1) SubscriptionName 1170 | (2) Scope_Delegation (Direct_SUB, Direct_RG, Inheritance_MG) 1171 | (3) RoleDefinitionName (Contributor, Owner, Cost Management Reader, etc.) 1172 | (4) Scope (management group name or /) 1173 | (5) RBAC_Delegation_Type (Group_inheritance, Direct) 1174 | (6) RBAC_GroupName 1175 | (7) ObjectType 1176 | (8) DisplayName 1177 | (9) UserPrincipalName 1178 | #> 1179 | 1180 | If ($RBAC_RoleAssignments) 1181 | { 1182 | $Pivottable.PT_RBAC_SCOPE_DELEGATION = @{ SourceWorkSheet = "RBAC_RoleAssignments" 1183 | PivotRows = @('SubscriptionName','Scope_Delegation','RoleDefinitionName','Scope','RBAC_Delegation_Type','RBAC_GroupName','ObjectType','DisplayName','UserPrincipalName') 1184 | PivotData = @{"ObjectId"="Count"} 1185 | } 1186 | } 1187 | 1188 | #-------------------------------------- 1189 | 1190 | <# 1191 | PT_RBAC_MG 1192 | Purpose: Detect direct RBAC permissions on Mg-level (not done by group) 1193 | SourceWorkSheet: RBAC_Direct_Mglevel 1194 | Sort-order: 1195 | (1) SubscriptionName 1196 | (2) RoleDefinitionName (Contributor, Owner, Cost Management Reader, etc.) 1197 | (3) Scope_Delegation (Direct_SUB, Direct_RG, Inheritance_MG) 1198 | (4) Scope (management group name or /) 1199 | (5) ObjectType 1200 | (6) DisplayName 1201 | (7) UserPrincipalName 1202 | #> 1203 | 1204 | If ($RBAC_Delegation_Type_Direct_Mg_Filtered) 1205 | { 1206 | $Pivottable.PT_RBAC_DIRECT_MG = @{ SourceWorkSheet = "RBAC_Direct_Mglevel" 1207 | PivotRows = @('SubscriptionName','RoleDefinitionName','Scope_Delegation','Scope','ObjectType','DisplayName','UserPrincipalName') 1208 | PivotData = @{"ObjectId"="Count"} 1209 | } 1210 | 1211 | } 1212 | 1213 | #-------------------------------------- 1214 | 1215 | <# 1216 | PT_RBAC_SUB 1217 | Purpose: Detect direct RBAC permissions on Sub-level (not done by group) 1218 | SourceWorkSheet: RBAC_Direct_Sublevel 1219 | Sort-order: 1220 | (1) SubscriptionName 1221 | (2) RoleDefinitionName (Contributor, Owner, Cost Management Reader, etc.) 1222 | (3) Scope_Delegation (Direct_SUB, Direct_RG, Inheritance_MG) 1223 | (4) Scope (management group name or /) 1224 | (5) ObjectType 1225 | (6) DisplayName 1226 | (7) UserPrincipalName 1227 | #> 1228 | 1229 | If ($RBAC_Delegation_Type_Direct_Sub_Filtered) 1230 | { 1231 | $Pivottable.PT_RBAC_DIRECT_SUB = @{ SourceWorkSheet = "RBAC_Direct_Sublevel" 1232 | PivotRows = @('SubscriptionName','RoleDefinitionName','Scope_Delegation','Scope','ObjectType','DisplayName','UserPrincipalName') 1233 | PivotData = @{"ObjectId"="Count"} 1234 | } 1235 | } 1236 | 1237 | #-------------------------------------- 1238 | # Exporting Excel pivot tables 1239 | 1240 | Write-Output "Exporting Pivot tables .... Please Wait !" 1241 | Export-Excel -Path $FileOutput -PivotTableDefinition $Pivottable -NoTotalsInPivot 1242 | 1243 | # Finished 1244 | Write-Output "" 1245 | Write-Output "Export finished - $($FileOutput)" 1246 | --------------------------------------------------------------------------------