From f27b9c8964b91f9ab70245c2634bcc478dce0879 Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Mon, 12 Jan 2026 17:56:03 +0530 Subject: [PATCH 01/10] AzureAD Pacakge cmd updated to Microsoft.Graph cmds --- ...ediate-InvalidAADObjectRoleAssignments.ps1 | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index 2caeb7f0..2dd3a949 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -53,9 +53,9 @@ function Pre_requisites This command would check pre requisites modules to perform remediation. #> - Write-Host "Required modules are: Az.Resources, AzureAD, Az.Accounts, Az.ResourceGraph" -ForegroundColor Cyan + Write-Host "Required modules are: Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph" -ForegroundColor Cyan Write-Host "Checking for required modules..." - $availableModules = $(Get-Module -ListAvailable Az.Resources, AzureAD, Az.Accounts, Az.ResourceGraph) + $availableModules = $(Get-Module -ListAvailable Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph) # Checking if 'Az.Accounts' module is available or not. if($availableModules.Name -notcontains 'Az.Accounts') @@ -90,15 +90,16 @@ function Pre_requisites Write-Host "Az.ResourceGraph module is available." -ForegroundColor Green } - # Checking if 'AzureAD' module is available or not. - if($availableModules.Name -notcontains 'AzureAD') + # Checking if 'Microsoft.Graph' module is available or not. + if($availableModules.Name -notcontains 'Microsoft.Graph') { - Write-Host "Installing module AzureAD..." -ForegroundColor Yellow - Install-Module -Name AzureAD -Scope CurrentUser -Repository 'PSGallery' + Write-Host "Installing module Microsoft.Graph..." -ForegroundColor Yellow + Install-Module -Name Microsoft.Graph -Scope CurrentUser -Repository 'PSGallery' + Write-Host "Microsoft.Graph Module installed" -ForegroundColor Green } else { - Write-Host "AzureAD module is available." -ForegroundColor Green + Write-Host "Microsoft.Graph module is available." -ForegroundColor Green } } @@ -172,7 +173,7 @@ function Remove-AzTSInvalidAADAccounts } # Setting context for current subscription. - $currentSub = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop + $currentSub = Set-AzContext -Tenant $isContextSet.Tenant -subscription $SubscriptionId -ErrorAction Stop Write-Host "Note: `n 1. Exclude checking PIM assignment for deprecated account due to insufficient privilege. `n 2. Exclude checking deprecated account with 'AccountAdministrator' role due to insufficient privilege. `n (To remove deprecated account role assignment with 'AccountAdministrator' role, please reach out to Azure Support) `n 3. Exclude checking role assignments at MG scope. `n 4. Checking only for user type assignments." -ForegroundColor Yellow @@ -197,28 +198,28 @@ function Remove-AzTSInvalidAADAccounts $currentLoginUserObjectId = ""; $requiredRoleDefinitionName = @("Owner", "User Access Administrator") - if(($currentLoginRoleAssignments | Where { $_.RoleDefinitionName -in $requiredRoleDefinitionName} | Measure-Object).Count -le 0 ) + if(($currentLoginRoleAssignments | Where { $_.RoleDefinitionName -in $requiredRoleDefinitionName} | Measure-Object).Count -le 0) { # The user does not have direct access to the subscription, checking if the user has access through groups # Need to connect to Azure AD before running any other command for fetching Entra Id related information (e.g. - group membership) try { - Get-AzureADTenantDetail | Out-Null + Get-MgOrganization | Out-Null } catch { - Connect-AzureAD -TenantId $currentSub.Tenant.Id | Out-Null + Connect-MgGraph -TenantId $currentSub.Tenant.Id | Out-Null } $allRoleAssignments = Get-AzRoleAssignment -Scope "/subscriptions/$($SubscriptionId)" # Fetch all the role assignmenets for the given scope - $userMemberGroups = Get-AzureADUserMembership -ObjectId $currentSub.Account.Id -All $true | Select-Object -ExpandProperty ObjectId # Fetch all the groups the user has access to and get all the object ids + $userMemberGroups = Get-MgUserMemberOf -UserId $currentSub.Account.Id -All $true | Select-Object -ExpandProperty ObjectId # Fetch all the groups the user has access to and get all the object ids if(($allRoleAssignments | Where-Object { $_.RoleDefinitionName -in $requiredRoleDefinitionName -and $_.ObjectId -in $userMemberGroups } | Measure-Object).Count -le 0) { Write-Host "Warning: This script can only be run by an [$($requiredRoleDefinitionName -join ", ")]." -ForegroundColor Yellow return; } - $currentLoginUserObjectId = Get-AzureADUser -Filter "userPrincipalName eq '$($currentSub.Account.Id)'" | Select-Object ObjectId -ExpandProperty ObjectId # Fetch the user object id + $currentLoginUserObjectId = Get-MgUser -Filter "userPrincipalName eq '$($currentSub.Account.Id)'" | Select-Object ObjectId -ExpandProperty ObjectId # Fetch the user object id } Write-Host "Current user [$($currentSub.Account.Id)] has the required permission for subscription [$($SubscriptionId)]." -ForegroundColor Green @@ -301,12 +302,12 @@ function Remove-AzTSInvalidAADAccounts try { # Check if Connect-AzureAD session is already active - Get-AzureADUser -ObjectId $currentLoginUserObjectId | Out-Null + Get-MgUser -UserId $currentLoginUserObjectId | Out-Null } catch { Write-Host "Connecting to Azure AD..." - Connect-AzureAD -TenantId $currentSub.Tenant.Id -ErrorAction Stop + Connect-MgGraph -TenantId $currentSub.Tenant.Id -ErrorAction Stop } # Batching object ids in count of 900. @@ -325,7 +326,7 @@ function Remove-AzTSInvalidAADAccounts $subRange = $distinctObjectIds[$i..$endRange] # Getting active identities from Azure Active Directory. - $subActiveIdentities = Get-AzureADObjectByObjectId -ObjectIds $subRange + $subActiveIdentities = Get-MgDirectoryObjectById -Ids $subRange # Safe Check if(($subActiveIdentities | Measure-Object).Count -le 0) { @@ -345,7 +346,7 @@ function Remove-AzTSInvalidAADAccounts if(($classicRoleAssignments | Measure-Object).count -gt 0) { $classicRoleAssignments | ForEach-Object { - $userDetails = Get-AzureADUser -Filter "userPrincipalName eq '$($_.SignInName)' or Mail eq '$($_.SignInName)'" + $userDetails = Get-MgUser -Filter "userPrincipalName eq '$($_.SignInName)' or Mail eq '$($_.SignInName)'" if (($userDetails | Measure-Object).Count -eq 0 ) { $invalidClassicRoles += $_ @@ -368,12 +369,12 @@ function Remove-AzTSInvalidAADAccounts try { # Check if Connect-AzureAD session is already active - Get-AzureADUser -ObjectId $currentLoginUserObjectId | Out-Null + Get-MgUser -UserId $currentLoginUserObjectId | Out-Null } catch { Write-Host "Connecting to Azure AD..." - Connect-AzureAD -ErrorAction Stop + Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All" -ErrorAction Stop } $allRoleAssignments = Import-Csv -LiteralPath $FilePath From 3a0612a786ada8045d8d8d7daf08b4e0d4dacb22 Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Mon, 12 Jan 2026 18:08:08 +0530 Subject: [PATCH 02/10] Tenant Id parameter added in cmd --- ...ediate-InvalidAADObjectRoleAssignments.ps1 | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index 2dd3a949..159b58e9 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -165,15 +165,15 @@ function Remove-AzTSInvalidAADAccounts # Connect to AzAccount $isContextSet = Get-AzContext - if ([string]::IsNullOrEmpty($isContextSet)) + if ($true) { Write-Host "Connecting to AzAccount..." - Connect-AzAccount -ErrorAction Stop + Connect-AzAccount -Tenant $isContextSet.Tenant.Id -ErrorAction Stop Write-Host "Connected to AzAccount" -ForegroundColor Green } # Setting context for current subscription. - $currentSub = Set-AzContext -Tenant $isContextSet.Tenant -subscription $SubscriptionId -ErrorAction Stop + $currentSub = Set-AzContext -Tenant $isContextSet.Tenant.Id -subscription $SubscriptionId -ErrorAction Stop Write-Host "Note: `n 1. Exclude checking PIM assignment for deprecated account due to insufficient privilege. `n 2. Exclude checking deprecated account with 'AccountAdministrator' role due to insufficient privilege. `n (To remove deprecated account role assignment with 'AccountAdministrator' role, please reach out to Azure Support) `n 3. Exclude checking role assignments at MG scope. `n 4. Checking only for user type assignments." -ForegroundColor Yellow @@ -576,18 +576,10 @@ class ClassicRoleAssignments [psobject] $headers = $null try { - $resourceAppIdUri = "https://management.core.windows.net/" - $rmContext = Get-AzContext - $authResult = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate( - $rmContext.Account, - $rmContext.Environment, - $rmContext.Tenant, - [System.Security.SecureString] $null, - "Never", - $null, - $resourceAppIdUri); - - $header = "Bearer " + $authResult.AccessToken + $resourceAppIdUri = "https://management.azure.com" + $accessToken = (Get-AzAccessToken -ResourceUrl $resourceAppIdUri).Token + + $header = "Bearer " + $accessToken $headers = @{"Authorization"=$header;"Content-Type"="application/json";} } catch From 26a31ce4c62c80833d57a2832f17fc89a68ae6b5 Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Tue, 13 Jan 2026 11:46:13 +0530 Subject: [PATCH 03/10] token retirval logic changed --- .../Remediate-InvalidAADObjectRoleAssignments.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index 159b58e9..684590da 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -577,9 +577,11 @@ class ClassicRoleAssignments try { $resourceAppIdUri = "https://management.azure.com" - $accessToken = (Get-AzAccessToken -ResourceUrl $resourceAppIdUri).Token + $accessToken = (Get-AzAccessToken -ResourceUrl $resourceAppIdUri -AsSecureString) + $credential = New-Object System.Net.NetworkCredential("", $accessToken.Token) + $token = $credential.Password - $header = "Bearer " + $accessToken + $header = "Bearer " + $token $headers = @{"Authorization"=$header;"Content-Type"="application/json";} } catch From 511c817b7007102d7f4e5db4007faef72ed6e832 Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Tue, 13 Jan 2026 15:28:55 +0530 Subject: [PATCH 04/10] Code refactoring --- ...emediate-InvalidAADObjectRoleAssignments.ps1 | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index 684590da..51bc6411 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -165,7 +165,7 @@ function Remove-AzTSInvalidAADAccounts # Connect to AzAccount $isContextSet = Get-AzContext - if ($true) + if ([string]::IsNullOrEmpty($isContextSet)) { Write-Host "Connecting to AzAccount..." Connect-AzAccount -Tenant $isContextSet.Tenant.Id -ErrorAction Stop @@ -301,13 +301,12 @@ function Remove-AzTSInvalidAADAccounts # Connect to Azure Active Directory. try { - # Check if Connect-AzureAD session is already active - Get-MgUser -UserId $currentLoginUserObjectId | Out-Null + # Connect with Microsoft Graph + Connect-MgGraph -TenantId $currentSub.Tenant.Id -ErrorAction Stop } catch { - Write-Host "Connecting to Azure AD..." - Connect-MgGraph -TenantId $currentSub.Tenant.Id -ErrorAction Stop + Write-Host "Failed to connect with Microsoft Graph..." } # Batching object ids in count of 900. @@ -368,13 +367,13 @@ function Remove-AzTSInvalidAADAccounts # Connect to Azure Active Directory. try { - # Check if Connect-AzureAD session is already active - Get-MgUser -UserId $currentLoginUserObjectId | Out-Null + # Connect with Microsoft Graph + Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All" -ErrorAction Stop } catch { - Write-Host "Connecting to Azure AD..." - Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All" -ErrorAction Stop + Write-Host "Failed to connect with Microsoft Graph..." + } $allRoleAssignments = Import-Csv -LiteralPath $FilePath From 70d295cebdaa8c035ed41c95872b03dcf6fbb099 Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Tue, 13 Jan 2026 18:11:04 +0530 Subject: [PATCH 05/10] Code changes --- .../Remediate-InvalidAADObjectRoleAssignments.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index 51bc6411..33c2b131 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -164,6 +164,7 @@ function Remove-AzTSInvalidAADAccounts } # Connect to AzAccount + Connect-AzAccount -ErrorAction Stop $isContextSet = Get-AzContext if ([string]::IsNullOrEmpty($isContextSet)) { @@ -173,7 +174,7 @@ function Remove-AzTSInvalidAADAccounts } # Setting context for current subscription. - $currentSub = Set-AzContext -Tenant $isContextSet.Tenant.Id -subscription $SubscriptionId -ErrorAction Stop + $currentSub = Set-AzContext -Tenant $isContextSet.Tenant.Id -ErrorAction Stop Write-Host "Note: `n 1. Exclude checking PIM assignment for deprecated account due to insufficient privilege. `n 2. Exclude checking deprecated account with 'AccountAdministrator' role due to insufficient privilege. `n (To remove deprecated account role assignment with 'AccountAdministrator' role, please reach out to Azure Support) `n 3. Exclude checking role assignments at MG scope. `n 4. Checking only for user type assignments." -ForegroundColor Yellow @@ -204,15 +205,15 @@ function Remove-AzTSInvalidAADAccounts # Need to connect to Azure AD before running any other command for fetching Entra Id related information (e.g. - group membership) try { - Get-MgOrganization | Out-Null + Connect-MgGraph -TenantId $currentSub.Tenant.Id | Out-Null } catch { - Connect-MgGraph -TenantId $currentSub.Tenant.Id | Out-Null + Write-Host "Failed to connect with Microsoft.Graph" } $allRoleAssignments = Get-AzRoleAssignment -Scope "/subscriptions/$($SubscriptionId)" # Fetch all the role assignmenets for the given scope - $userMemberGroups = Get-MgUserMemberOf -UserId $currentSub.Account.Id -All $true | Select-Object -ExpandProperty ObjectId # Fetch all the groups the user has access to and get all the object ids + $userMemberGroups = Get-MgUserMemberOf -UserId $currentSub.Account.Id -All | Select-Object -ExpandProperty ObjectId # Fetch all the groups the user has access to and get all the object ids if(($allRoleAssignments | Where-Object { $_.RoleDefinitionName -in $requiredRoleDefinitionName -and $_.ObjectId -in $userMemberGroups } | Measure-Object).Count -le 0) { Write-Host "Warning: This script can only be run by an [$($requiredRoleDefinitionName -join ", ")]." -ForegroundColor Yellow @@ -334,7 +335,7 @@ function Remove-AzTSInvalidAADAccounts return; } - $activeIdentities += $subActiveIdentities.ObjectId + $activeIdentities += $subActiveIdentities | Select-Object -ExpandProperty Id } $invalidAADObjectIds = $distinctObjectIds | Where-Object { $_ -notin $activeIdentities} From 208ef09ddaa06db873fb362006f771a2df4a2fcb Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Fri, 16 Jan 2026 15:16:36 +0530 Subject: [PATCH 06/10] Powershell script change to work with SAW --- ...ediate-InvalidAADObjectRoleAssignments.ps1 | 206 +++++++++--------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index 33c2b131..8230bfc3 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -51,17 +51,18 @@ function Pre_requisites This command would check pre requisites modules. .DESCRIPTION This command would check pre requisites modules to perform remediation. - #> - + #> + $folderPath = $env:PSModulePath -split ';' | Where-Object {$_ -like "*SAWPSModulePath"}[0] Write-Host "Required modules are: Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph" -ForegroundColor Cyan Write-Host "Checking for required modules..." - $availableModules = $(Get-Module -ListAvailable Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph) + Import-Module PowerShellGet + $availableModules = $(Get-Module -ListAvailable "Az.Resources", "Microsoft.Graph", "Az.Accounts", "Az.ResourceGraph") # Checking if 'Az.Accounts' module is available or not. if($availableModules.Name -notcontains 'Az.Accounts') { Write-Host "Installing module Az.Accounts..." -ForegroundColor Yellow - Install-Module -Name Az.Accounts -Scope CurrentUser -Repository 'PSGallery' + Save-Module -Name Az.Accounts -RequiredVersion 5.3.2 -Path $folderPath } else { @@ -72,7 +73,7 @@ function Pre_requisites if($availableModules.Name -notcontains 'Az.Resources') { Write-Host "Installing module Az.Resources..." -ForegroundColor Yellow - Install-Module -Name Az.Resources -Scope CurrentUser -Repository 'PSGallery' + Save-Module -Name Az.Resources -RequiredVersion 9.0.0 -Path $folderPath } else { @@ -83,7 +84,7 @@ function Pre_requisites if($availableModules.Name -notcontains 'Az.ResourceGraph') { Write-Host "Installing module Az.ResourceGraph..." -ForegroundColor Yellow - Install-Module -Name Az.ResourceGraph -Scope CurrentUser -Repository 'PSGallery' + Save-Module -Name Az.ResourceGraph -RequiredVersion 1.2.1 -Path $folderPath } else { @@ -93,14 +94,22 @@ function Pre_requisites # Checking if 'Microsoft.Graph' module is available or not. if($availableModules.Name -notcontains 'Microsoft.Graph') { - Write-Host "Installing module Microsoft.Graph..." -ForegroundColor Yellow - Install-Module -Name Microsoft.Graph -Scope CurrentUser -Repository 'PSGallery' - Write-Host "Microsoft.Graph Module installed" -ForegroundColor Green + Write-Host "Installing module Microsoft.Graph...`nThis module may take more time to install. Please wait patiently." -ForegroundColor Yellow + Save-Module -Name Microsoft.Graph -RequiredVersion 2.32.0 -Path $folderPath } else { Write-Host "Microsoft.Graph module is available." -ForegroundColor Green } + Write-Host "Importing required modules...`nIf you started new PowerShell session, It may take more time to import all modules." -ForegroundColor Cyan + Import-Module Az.Resources + Write-Host "Az.Resources module imported successfully." -ForegroundColor Green + Import-Module Az.Accounts + Write-Host "Az.Accounts module imported successfully." -ForegroundColor Green + Import-Module Az.ResourceGraph + Write-Host "Az.ResourceGraph module imported successfully." -ForegroundColor Green + Import-Module Microsoft.Graph + Write-Host "Microsoft.Graph module imported successfully." -ForegroundColor Green } function Remove-AzTSInvalidAADAccounts @@ -164,12 +173,11 @@ function Remove-AzTSInvalidAADAccounts } # Connect to AzAccount - Connect-AzAccount -ErrorAction Stop $isContextSet = Get-AzContext if ([string]::IsNullOrEmpty($isContextSet)) { Write-Host "Connecting to AzAccount..." - Connect-AzAccount -Tenant $isContextSet.Tenant.Id -ErrorAction Stop + Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop Write-Host "Connected to AzAccount" -ForegroundColor Green } @@ -246,8 +254,7 @@ function Remove-AzTSInvalidAADAccounts if(($ObjectIds | Measure-Object).Count -eq 0) { # Getting all classic role assignments. - $classicAssignments = [ClassicRoleAssignments]::new() - $res = $classicAssignments.GetClassicRoleAssignments($subscriptionId) + $res = GetClassicRoleAssignments -SubscriptionId $subscriptionId $classicDistinctRoleAssignmentList = $res.value | Where-Object { ![string]::IsNullOrWhiteSpace($_.properties.emailAddress) } # Renaming property name @@ -264,14 +271,8 @@ function Remove-AzTSInvalidAADAccounts $currentRoleAssignmentList = $currentRoleAssignmentList | Where-Object {![string]::IsNullOrWhiteSpace($_.ObjectId)}; $currentRoleAssignmentList | select -Unique -Property 'ObjectId' | ForEach-Object { $distinctObjectIds += $_.ObjectId } - - $method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get - $classicAssignments = [ClassicRoleAssignments]::new() - $headers = $classicAssignments.GetAuthHeader() - # Getting MDC reported deprecated account object ids. - $mdcDeprecated = [MDCDeprecatedAccounts]::new() - $mdcDeprecatedAccountList = $mdcDeprecated.GetMDCDeprecatedAccounts([string] $SubscriptionId) + $mdcDeprecatedAccountList = GetMDCDeprecatedAccounts -SubcriptionId $SubscriptionId $mdcDeprecatedRoleAssignmentList = @(); if (($mdcDeprecatedAccountList | Measure-Object).Count -gt 0) @@ -499,8 +500,7 @@ function Remove-AzTSInvalidAADAccounts $isServiceAdminAccount = $true; } - $classicAssignments = [ClassicRoleAssignments]::new() - $res = $classicAssignments.DeleteClassicRoleAssignment($_.RoleAssignmentId, $isServiceAdminAccount) + $res = DeleteClassicRoleAssignment -roleAssignmentId $_.RoleAssignmentId -isServiceAdminAccount $isServiceAdminAccount if(($null -ne $res) -and ($res.StatusCode -eq 202 -or $res.StatusCode -eq 200)) { @@ -510,7 +510,6 @@ function Remove-AzTSInvalidAADAccounts } catch { - $isRemoved = $false Write-Host "Not able to remove invalid classic role assignment. ErrorMessage [$($_)]" -ForegroundColor Red } } @@ -557,7 +556,7 @@ function Get-ARGData $graphResult = Search-AzGraph -Query $kqlQuery -First $batchSize } - $kqlResponse += $graphResult.data.ToArray() + $kqlResponse += $graphResult.data | Select-Object * if ($graphResult.data.Count -lt $batchSize) { break; @@ -569,104 +568,105 @@ function Get-ARGData } -class ClassicRoleAssignments +function GetAuthHeader { - [PSObject] GetAuthHeader() + $headers = $null + try { - [psobject] $headers = $null - try - { - $resourceAppIdUri = "https://management.azure.com" - $accessToken = (Get-AzAccessToken -ResourceUrl $resourceAppIdUri -AsSecureString) - $credential = New-Object System.Net.NetworkCredential("", $accessToken.Token) - $token = $credential.Password - - $header = "Bearer " + $token - $headers = @{"Authorization"=$header;"Content-Type"="application/json";} - } - catch - { - Write-Host "Error occurred while fetching auth header. ErrorMessage [$($_)]" -ForegroundColor Red - } - return($headers) + $resourceAppIdUri = "https://management.azure.com" + $accessToken = (Get-AzAccessToken -ResourceUrl "https://management.azure.com" -AsSecureString).Token + $credential = New-Object System.Net.NetworkCredential("", $accessToken.Token) + $token = $credential.Password + $header = "Bearer " + $token + $headers = @{"Authorization"=$header;"Content-Type"="application/json";} } - - [PSObject] GetClassicRoleAssignments([string] $subscriptionId) + catch { - $content = $null - try - { - $armUri = "https://management.azure.com/subscriptions/$($subscriptionId)/providers/Microsoft.Authorization/classicadministrators?api-version=2015-06-01" - $headers = $this.GetAuthHeader() - # API to get classic role assignments - $response = Invoke-WebRequest -Method Get -Uri $armUri -Headers $headers -UseBasicParsing - $content = ConvertFrom-Json $response.Content - } - catch - { - Write-Host "Error occurred while fetching classic role assignment. ErrorMessage [$($_)]" -ForegroundColor Red - } - - return($content) + Write-Host "Error occurred while fetching auth header. ErrorMessage [$($_)]" -ForegroundColor Red } + return $headers +} +function GetClassicRoleAssignments +{ + param ( + [string] + $SubscriptionId + ) + $content = $null + try + { + $accessToken = (Get-AzAccessToken -ResourceUrl "https://management.azure.com" -AsSecureString).Token + $armUri = "https://management.azure.com/subscriptions/$($subscriptionId)/providers/Microsoft.Authorization/classicadministrators?api-version=2015-06-01" + #$headers = GetAuthHeader + # API to get classic role assignments + $response = Invoke-RestMethod -Method Get -Uri $armUri -SkipHeaderValidation -Authentication Bearer -Token (Get-AzAccessToken -ResourceUrl "https://management.azure.com" -AsSecureString).Token -UseBasicParsing + $content = $response.value + } + catch + { + Write-Host "Error occurred while fetching classic role assignment. ErrorMessage [$($_)]" -ForegroundColor Red + } + + return $content +} +function DeleteClassicRoleAssignment +{ + param ( + [string] + $roleAssignmentId, - [PSObject] DeleteClassicRoleAssignment([string] $roleAssignmentId, [bool] $isServiceAdminAccount) + [bool] + $isServiceAdminAccount + ) + $content = $null + try { - $content = $null - try + $armUri = "https://management.azure.com" + $roleAssignmentId + "?api-version=2015-06-01" + if ($isServiceAdminAccount) { - $armUri = "https://management.azure.com" + $roleAssignmentId + "?api-version=2015-06-01" - if ($isServiceAdminAccount) - { - $armUri += "&adminType=serviceAdmin" - } - $headers = $this.GetAuthHeader() - - # API to get classic role assignments - $response = Invoke-WebRequest -Method Delete -Uri $armUri -Headers $headers -UseBasicParsing - $content = $response - } - catch - { - Write-Host "Error occurred while deleting classic role assignment. ErrorMessage [$($_)]" -ForegroundColor Red - throw; + $armUri += "&adminType=serviceAdmin" } + #$headers = GetAuthHeader - return($content) + # API to get classic role assignments + $accessToken = (Get-AzAccessToken -ResourceUrl "https://management.azure.com" -AsSecureString).Token + $response = Invoke-RestMethod -Method Delete -Uri $armUri -SkipHeaderValidation -Authentication Bearer -Token $accessToken -UseBasicParsing + $content = $response.value } + catch + { + Write-Host "Error occurred while deleting classic role assignment. ErrorMessage [$($_)]" -ForegroundColor Red + throw; + } + + return $content } -class MDCDeprecatedAccounts +function GetMDCDeprecatedAccounts { - [PSObject] GetMDCDeprecatedAccounts([string] $SubcriptionId) + param ( + [string] + $SubcriptionId + ) + $response = @() + $invalidObjectIds = @() + $response += Get-ARGData -kqlQuery "securityresources | where type == 'microsoft.security/assessments' | where name =~ '1ff0b4c9-ed56-4de6-be9c-d7ab39645926' and subscriptionId =~ '$($SubcriptionId)'" + if (($response | Measure-Object).Count -gt 0 ) { - $response = @() - $invalidObjectIds = @() - - $response += Get-ARGData -kqlQuery "securityresources | where type == 'microsoft.security/assessments' | where name =~ '1ff0b4c9-ed56-4de6-be9c-d7ab39645926' and subscriptionId =~ '$($SubcriptionId)'" - - if (($response | Measure-Object).Count -gt 0 ) + $mdcAssessmentState = $response[0].properties.status.code + if ((-not [string]::IsNullOrWhiteSpace($mdcAssessmentState)) -and ($mdcAssessmentState -eq 'Unhealthy') -and (-not [string]::IsNullOrWhiteSpace($response[0].properties.additionalData.subAssessmentsLink))) + { + $nextSubAssessmentLink = $response[0].properties.additionalData.subAssessmentsLink + $SubAssessmentResponse = Get-ARGData -kqlQuery "securityresources | where type == 'microsoft.security/assessments/subassessments' | where id contains '$($nextSubAssessmentLink)'" + if (($SubAssessmentResponse | Measure-Object).Count -gt 0 ) { - $mdcAssessmentState = $response[0].properties.status.code - - if ((-not [string]::IsNullOrWhiteSpace($mdcAssessmentState)) -and ($mdcAssessmentState -eq 'Unhealthy') -and (-not [string]::IsNullOrWhiteSpace($response[0].properties.additionalData.subAssessmentsLink))) - { - - $nextSubAssessmentLink = $response[0].properties.additionalData.subAssessmentsLink - - $SubAssessmentResponse = Get-ARGData -kqlQuery "securityresources | where type == 'microsoft.security/assessments/subassessments' | where id contains '$($nextSubAssessmentLink)'" - - if (($SubAssessmentResponse | Measure-Object).Count -gt 0 ) - { - $invalidObjectIds += foreach ($obj in $SubAssessmentResponse) { ($obj.properties.resourceDetails.id -split "/")[-1] } - } - } - + $invalidObjectIds += foreach ($obj in $SubAssessmentResponse) { ($obj.properties.resourceDetails.id -split "/")[-1] } } + } - return($invalidObjectIds) - } + return $invalidObjectIds + } # ***************************************************** # From 4b8857204fd507dce903d48e56aa85ea3eb06f9a Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Fri, 16 Jan 2026 15:30:44 +0530 Subject: [PATCH 07/10] Code refactoring --- ...ediate-InvalidAADObjectRoleAssignments.ps1 | 154 ++++++++++++------ 1 file changed, 104 insertions(+), 50 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index 8230bfc3..99a77d1d 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -52,64 +52,118 @@ function Pre_requisites .DESCRIPTION This command would check pre requisites modules to perform remediation. #> - $folderPath = $env:PSModulePath -split ';' | Where-Object {$_ -like "*SAWPSModulePath"}[0] - Write-Host "Required modules are: Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph" -ForegroundColor Cyan - Write-Host "Checking for required modules..." - Import-Module PowerShellGet - $availableModules = $(Get-Module -ListAvailable "Az.Resources", "Microsoft.Graph", "Az.Accounts", "Az.ResourceGraph") - - # Checking if 'Az.Accounts' module is available or not. - if($availableModules.Name -notcontains 'Az.Accounts') - { - Write-Host "Installing module Az.Accounts..." -ForegroundColor Yellow - Save-Module -Name Az.Accounts -RequiredVersion 5.3.2 -Path $folderPath - } - else + $folderPaths = $env:PSModulePath -split ';' | Where-Object {$_ -like "*SAWPSModulePath"} + if($folderPaths.Count -gt 0) { - Write-Host "Az.Accounts module is available." -ForegroundColor Green - } + $folderPath = $folderPaths[0] + Write-Host "Required modules are: Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph" -ForegroundColor Cyan + Write-Host "Checking for required modules..." + Import-Module PowerShellGet + $availableModules = $(Get-Module -ListAvailable "Az.Resources", "Microsoft.Graph", "Az.Accounts", "Az.ResourceGraph") + + # Checking if 'Az.Accounts' module is available or not. + if($availableModules.Name -notcontains 'Az.Accounts') + { + Write-Host "Installing module Az.Accounts..." -ForegroundColor Yellow + Save-Module -Name Az.Accounts -RequiredVersion 5.3.2 -Path $folderPath + } + else + { + Write-Host "Az.Accounts module is available." -ForegroundColor Green + } - # Checking if 'Az.Resources' module is available or not. - if($availableModules.Name -notcontains 'Az.Resources') - { - Write-Host "Installing module Az.Resources..." -ForegroundColor Yellow - Save-Module -Name Az.Resources -RequiredVersion 9.0.0 -Path $folderPath - } - else - { - Write-Host "Az.Resources module is available." -ForegroundColor Green - } + # Checking if 'Az.Resources' module is available or not. + if($availableModules.Name -notcontains 'Az.Resources') + { + Write-Host "Installing module Az.Resources..." -ForegroundColor Yellow + Save-Module -Name Az.Resources -RequiredVersion 9.0.0 -Path $folderPath + } + else + { + Write-Host "Az.Resources module is available." -ForegroundColor Green + } - # Checking if 'ARG' module is available or not. - if($availableModules.Name -notcontains 'Az.ResourceGraph') - { - Write-Host "Installing module Az.ResourceGraph..." -ForegroundColor Yellow - Save-Module -Name Az.ResourceGraph -RequiredVersion 1.2.1 -Path $folderPath - } - else - { - Write-Host "Az.ResourceGraph module is available." -ForegroundColor Green - } + # Checking if 'ARG' module is available or not. + if($availableModules.Name -notcontains 'Az.ResourceGraph') + { + Write-Host "Installing module Az.ResourceGraph..." -ForegroundColor Yellow + Save-Module -Name Az.ResourceGraph -RequiredVersion 1.2.1 -Path $folderPath + } + else + { + Write-Host "Az.ResourceGraph module is available." -ForegroundColor Green + } - # Checking if 'Microsoft.Graph' module is available or not. - if($availableModules.Name -notcontains 'Microsoft.Graph') - { - Write-Host "Installing module Microsoft.Graph...`nThis module may take more time to install. Please wait patiently." -ForegroundColor Yellow - Save-Module -Name Microsoft.Graph -RequiredVersion 2.32.0 -Path $folderPath + # Checking if 'Microsoft.Graph' module is available or not. + if($availableModules.Name -notcontains 'Microsoft.Graph') + { + Write-Host "Installing module Microsoft.Graph...`nThis module may take more time to install. Please wait patiently." -ForegroundColor Yellow + Save-Module -Name Microsoft.Graph -RequiredVersion 2.32.0 -Path $folderPath + } + else + { + Write-Host "Microsoft.Graph module is available." -ForegroundColor Green + } + Write-Host "Importing required modules...`nIf you started new PowerShell session, It may take more time to import all modules." -ForegroundColor Cyan + Import-Module Az.Resources + Write-Host "Az.Resources module imported successfully." -ForegroundColor Green + Import-Module Az.Accounts + Write-Host "Az.Accounts module imported successfully." -ForegroundColor Green + Import-Module Az.ResourceGraph + Write-Host "Az.ResourceGraph module imported successfully." -ForegroundColor Green + Import-Module Microsoft.Graph + Write-Host "Microsoft.Graph module imported successfully." -ForegroundColor Green } else { - Write-Host "Microsoft.Graph module is available." -ForegroundColor Green + Write-Host "Required modules are: Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph" -ForegroundColor Cyan + Write-Host "Checking for required modules..." + $availableModules = $(Get-Module -ListAvailable Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph) + + # Checking if 'Az.Accounts' module is available or not. + if($availableModules.Name -notcontains 'Az.Accounts') + { + Write-Host "Installing module Az.Accounts..." -ForegroundColor Yellow + Install-Module -Name Az.Accounts -Scope CurrentUser -Repository 'PSGallery' + } + else + { + Write-Host "Az.Accounts module is available." -ForegroundColor Green + } + + # Checking if 'Az.Resources' module is available or not. + if($availableModules.Name -notcontains 'Az.Resources') + { + Write-Host "Installing module Az.Resources..." -ForegroundColor Yellow + Install-Module -Name Az.Resources -Scope CurrentUser -Repository 'PSGallery' + } + else + { + Write-Host "Az.Resources module is available." -ForegroundColor Green + } + + # Checking if 'ARG' module is available or not. + if($availableModules.Name -notcontains 'Az.ResourceGraph') + { + Write-Host "Installing module Az.ResourceGraph..." -ForegroundColor Yellow + Install-Module -Name Az.ResourceGraph -Scope CurrentUser -Repository 'PSGallery' + } + else + { + Write-Host "Az.ResourceGraph module is available." -ForegroundColor Green + } + + # Checking if 'Microsoft.Graph' module is available or not. + if($availableModules.Name -notcontains 'Microsoft.Graph') + { + Write-Host "Installing module Microsoft.Graph..." -ForegroundColor Yellow + Install-Module -Name Microsoft.Graph -Scope CurrentUser -Repository 'PSGallery' + } + else + { + Write-Host "Microsoft.Graph module is available." -ForegroundColor Green + } } - Write-Host "Importing required modules...`nIf you started new PowerShell session, It may take more time to import all modules." -ForegroundColor Cyan - Import-Module Az.Resources - Write-Host "Az.Resources module imported successfully." -ForegroundColor Green - Import-Module Az.Accounts - Write-Host "Az.Accounts module imported successfully." -ForegroundColor Green - Import-Module Az.ResourceGraph - Write-Host "Az.ResourceGraph module imported successfully." -ForegroundColor Green - Import-Module Microsoft.Graph - Write-Host "Microsoft.Graph module imported successfully." -ForegroundColor Green } function Remove-AzTSInvalidAADAccounts From 20f0ca91f2ebf047ee849f17b858224e58fbdb19 Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Fri, 16 Jan 2026 17:36:28 +0530 Subject: [PATCH 08/10] Folder Path Changed --- .../Remediate-InvalidAADObjectRoleAssignments.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index 99a77d1d..e6dbc903 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -55,7 +55,8 @@ function Pre_requisites $folderPaths = $env:PSModulePath -split ';' | Where-Object {$_ -like "*SAWPSModulePath"} if($folderPaths.Count -gt 0) { - $folderPath = $folderPaths[0] + $folderPaths[0] + Write-Host "Detected SAW environment. Installing required modules in SAW module path, if not present." -ForegroundColor Cyan Write-Host "Required modules are: Az.Resources, Microsoft.Graph, Az.Accounts, Az.ResourceGraph" -ForegroundColor Cyan Write-Host "Checking for required modules..." Import-Module PowerShellGet @@ -476,8 +477,9 @@ function Remove-AzTSInvalidAADAccounts { Write-Host "Found [$($invalidClassicRolesCount)] invalid classic role assignments for the subscription [$($SubscriptionId)]" -ForegroundColor Cyan } - - $folderPath = [Environment]::GetFolderPath("MyDocuments") + $folderPath = ($env:PSModulePath -split ';' | + Where-Object { $_ -match '\\Documents(\\|$)' } | + Select-Object -First 1) -replace '(?i)^(.*?\\Documents)(?:\\.*)?$','$1' if (Test-Path -Path $folderPath) { $folderPath += "\AzTS\Remediation\Subscriptions\$($subscriptionid.replace("-","_"))\$((Get-Date).ToString('yyyyMMdd_hhmm'))\InvalidAADAccounts\" From 9b65b9eeff875c4cbbcaa30d86da1fbc1d0e5a50 Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Fri, 16 Jan 2026 18:19:34 +0530 Subject: [PATCH 09/10] Code chngs --- .../Remediate-InvalidAADObjectRoleAssignments.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index e6dbc903..e523bf9c 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -237,7 +237,7 @@ function Remove-AzTSInvalidAADAccounts } # Setting context for current subscription. - $currentSub = Set-AzContext -Tenant $isContextSet.Tenant.Id -ErrorAction Stop + $currentSub = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop Write-Host "Note: `n 1. Exclude checking PIM assignment for deprecated account due to insufficient privilege. `n 2. Exclude checking deprecated account with 'AccountAdministrator' role due to insufficient privilege. `n (To remove deprecated account role assignment with 'AccountAdministrator' role, please reach out to Azure Support) `n 3. Exclude checking role assignments at MG scope. `n 4. Checking only for user type assignments." -ForegroundColor Yellow From 42475ad5f726a17c0f72f449e1eb06f8a6e12897 Mon Sep 17 00:00:00 2001 From: "Omkar Mohite (TATA CONSULTANCY SERVICES LTD)" Date: Thu, 22 Jan 2026 18:06:15 +0530 Subject: [PATCH 10/10] Code formatting --- ...ediate-InvalidAADObjectRoleAssignments.ps1 | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 index e523bf9c..70c58d83 100644 --- a/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 +++ b/Scripts/RemediationScripts/Remediate-InvalidAADObjectRoleAssignments.ps1 @@ -622,26 +622,6 @@ function Get-ARGData return $kqlResponse } - - -function GetAuthHeader -{ - $headers = $null - try - { - $resourceAppIdUri = "https://management.azure.com" - $accessToken = (Get-AzAccessToken -ResourceUrl "https://management.azure.com" -AsSecureString).Token - $credential = New-Object System.Net.NetworkCredential("", $accessToken.Token) - $token = $credential.Password - $header = "Bearer " + $token - $headers = @{"Authorization"=$header;"Content-Type"="application/json";} - } - catch - { - Write-Host "Error occurred while fetching auth header. ErrorMessage [$($_)]" -ForegroundColor Red - } - return $headers -} function GetClassicRoleAssignments { param ( @@ -653,7 +633,6 @@ function GetClassicRoleAssignments { $accessToken = (Get-AzAccessToken -ResourceUrl "https://management.azure.com" -AsSecureString).Token $armUri = "https://management.azure.com/subscriptions/$($subscriptionId)/providers/Microsoft.Authorization/classicadministrators?api-version=2015-06-01" - #$headers = GetAuthHeader # API to get classic role assignments $response = Invoke-RestMethod -Method Get -Uri $armUri -SkipHeaderValidation -Authentication Bearer -Token (Get-AzAccessToken -ResourceUrl "https://management.azure.com" -AsSecureString).Token -UseBasicParsing $content = $response.value @@ -682,7 +661,6 @@ function DeleteClassicRoleAssignment { $armUri += "&adminType=serviceAdmin" } - #$headers = GetAuthHeader # API to get classic role assignments $accessToken = (Get-AzAccessToken -ResourceUrl "https://management.azure.com" -AsSecureString).Token