Grant-TDCErhvervSecurityInsightsPermissions.ps1
|
<#PSScriptInfo .VERSION 1.0.0 .GUID b061761a-8a26-443a-b023-27b0a132792f .AUTHOR TDC Erhverv .COMPANYNAME .COPYRIGHT .TAGS TDC Security Insights Entra Microsoft365 .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .DESCRIPTION Configures TDC Erhverv Security Insights permissions in your tenant. #> param( [Switch]$Force ) function ConvertTo-SecureStringFromPlainText { param ( [Parameter(Mandatory, ValueFromPipeline)] [String]$PlainText ) $SecureString = New-Object System.Security.SecureString $PlainText.ToCharArray() | ForEach-Object { $SecureString.AppendChar($_) } $SecureString.MakeReadOnly() return $SecureString } function Initialize-TDCErhvervSecurityInsightsMsalPackages { try { if ($PSVersionTable.PSEdition -eq 'Core') { $MicrosoftIdentityModelAbstractionsVersion = '8.8.0' } else { $MicrosoftIdentityModelAbstractionsVersion = '6.35.0' } $MicrosoftIdentityModelAbstractions = Get-Package -Name 'Microsoft.IdentityModel.Abstractions' -RequiredVersion $MicrosoftIdentityModelAbstractionsVersion -WarningAction SilentlyContinue -ErrorAction Stop $MicrosoftIdentityClient = Get-Package -Name 'Microsoft.Identity.Client' -RequiredVersion '4.70.2' -WarningAction SilentlyContinue -ErrorAction Stop } catch { Install-Package -Scope CurrentUser -Source 'https://www.nuget.org/api/v2' -Name 'Microsoft.IdentityModel.Abstractions' -RequiredVersion $MicrosoftIdentityModelAbstractionsVersion -Force -WarningAction SilentlyContinue -ErrorAction Stop | Out-Null Install-Package -Scope CurrentUser -Source 'https://www.nuget.org/api/v2' -Name 'Microsoft.Identity.Client' -RequiredVersion '4.70.2' -Force -WarningAction SilentlyContinue -ErrorAction Stop | Out-Null $MicrosoftIdentityModelAbstractions = Get-Package -Name 'Microsoft.IdentityModel.Abstractions' -RequiredVersion $MicrosoftIdentityModelAbstractionsVersion -WarningAction SilentlyContinue -ErrorAction Stop $MicrosoftIdentityClient = Get-Package -Name 'Microsoft.Identity.Client' -RequiredVersion '4.70.2' -WarningAction SilentlyContinue -ErrorAction Stop } $MicrosoftIdentityModelAbstractionsPath = $MicrosoftIdentityModelAbstractions.Source | Split-Path | Join-Path -ChildPath 'lib\net472\Microsoft.IdentityModel.Abstractions.dll' $MicrosoftIdentityClientPath = $MicrosoftIdentityClient.Source | Split-Path | Join-Path -ChildPath 'lib\net472\Microsoft.Identity.Client.dll' Add-Type -Path $MicrosoftIdentityModelAbstractionsPath -WarningAction SilentlyContinue -ErrorAction Stop Add-Type -Path $MicrosoftIdentityClientPath -WarningAction SilentlyContinue -ErrorAction Stop } function Initialize-TDCErhvervSecurityInsightsMsalApp { param ( [String]$ClientId = '81e6c433-88f4-490b-9266-f266a1f89c55', [String]$RedirectUri = 'https://login.microsoftonline.com/common/oauth2/nativeclient' ) Initialize-TDCErhvervSecurityInsightsMsalPackages $Script:ClientApplication = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($ClientId).WithRedirectUri($RedirectUri).Build() } function Get-TDCErhvervSecurityInsightsToken { param( [String]$Scope = 'https://graph.microsoft.com/.default', [Switch]$Force ) try { if ($env:AZUREPS_HOST_ENVIRONMENT -like 'cloud-shell/*') { $TaskAuthenticationResult = Get-AzAccessToken -ResourceUrl $Scope -AsSecureString -WarningAction Ignore $AuthenticationResult = [PSCustomObject]@{ AccessToken = [System.Net.NetworkCredential]::new('', $TaskAuthenticationResult.Token).Password SecureAccessToken = $TaskAuthenticationResult.Token AccountId = $TaskAuthenticationResult.UserId } } else { if (-not $Script:ClientApplication) { throw 'ClientApplication not initialized. Please call Initialize-TDCErhvervSecurityInsightsMsalApp first.' } [Microsoft.Identity.Client.IAccount]$Account = $Script:ClientApplication.GetAccountsAsync().GetAwaiter().GetResult() | Select-Object -First 1 [String[]]$Scope = $Scope if ($Account -and -not $Force) { $AquireTokenParameters = $Script:ClientApplication.AcquireTokenSilent($Scope, $Account) } else { $AquireTokenParameters = $Script:ClientApplication.AcquireTokenInteractive($Scope).WithPrompt([Microsoft.Identity.Client.Prompt]::Consent) } $TokenSource = New-Object System.Threading.CancellationTokenSource $TaskAuthenticationResult = $AquireTokenParameters.ExecuteAsync($TokenSource.Token) try { if (!$Timeout) { $Timeout = [System.TimeSpan]::Zero } $EndTime = [System.DateTime]::Now.Add($Timeout) while (!$TaskAuthenticationResult.IsCompleted) { if ($Timeout -eq [System.TimeSpan]::Zero -or [System.DateTime]::Now -lt $EndTime) { Start-Sleep -Seconds 1 } else { $TokenSource.Cancel() try { $TaskAuthenticationResult.Wait() } catch { } Write-Error -Exception (New-Object System.TimeoutException) -Category ([System.Management.Automation.ErrorCategory]::OperationTimeout) -CategoryActivity $MyInvocation.MyCommand -ErrorId 'GetMsalTokenFailureOperationTimeout' -TargetObject $Script:ClientApplication -ErrorAction Stop } } } finally { if (!$TaskAuthenticationResult.IsCompleted) { $TokenSource.Cancel() } $TokenSource.Dispose() } if ($TaskAuthenticationResult.IsFaulted) { Write-Error -Exception $TaskAuthenticationResult.Exception.InnerException -Category ([System.Management.Automation.ErrorCategory]::AuthenticationError) -CategoryActivity $MyInvocation.MyCommand -ErrorId 'GetMsalTokenFailureAuthenticationError' -TargetObject $Script:ClientApplication -ErrorAction Stop } if ($TaskAuthenticationResult.IsCanceled) { Write-Error -Exception (New-Object System.Threading.Tasks.TaskCanceledException $TaskAuthenticationResult) -Category ([System.Management.Automation.ErrorCategory]::OperationStopped) -CategoryActivity $MyInvocation.MyCommand -ErrorId 'GetMsalTokenFailureOperationStopped' -TargetObject $Script:ClientApplication -ErrorAction Stop } else { $AuthenticationResult = [PSCustomObject]@{ AccessToken = $TaskAuthenticationResult.Result.AccessToken SecureAccessToken = $TaskAuthenticationResult.Result.AccessToken | ConvertTo-SecureStringFromPlainText AccountId = $TaskAuthenticationResult.Result.Account.Username } } } return $AuthenticationResult } catch { throw } } function Grant-TDCErhvervSecurityInsightsPermissions { [CmdletBinding(SupportsShouldProcess = $true)] param( [Switch]$Force = $false ) try { try { if ((Get-Host -ErrorAction SilentlyContinue).UI.RawUI.BufferSize.Width -ge 114) { $motd = @' _____________________________________________________________________________________________________________________ | | | _____________________________________ | | /████████████████████████████████████/ ███████╗███████╗ ██████╗██╗ ██╗██████╗ ██╗████████╗██╗ ██╗ | | /████████████████████████████████████/ ██╔════╝██╔════╝██╔════╝██║ ██║██╔══██╗██║╚══██╔══╝╚██╗ ██╔╝ | | /████████████████████████████████████/ ███████╗█████╗ ██║ ██║ ██║██████╔╝██║ ██║ ╚████╔╝ | | /████████████████████████████████████/ ╚════██║██╔══╝ ██║ ██║ ██║██╔══██╗██║ ██║ ╚██╔╝ | | /████ ██ ████ ████████/ ███████║███████╗╚██████╗╚██████╔╝██║ ██║██║ ██║ ██║ | | /████████ ███ ██ ██ ██████████/ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ | | /█████████ ███ ███ █ ██████████/ | | /██████████ ███ ██ ██ ████████/ ██╗███╗ ██╗███████╗██╗ ██████╗ ██╗ ██╗████████╗███████╗ | | /███████████ ███ ████ ████/ ██║████╗ ██║██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝██╔════╝ | | /████████████████████████████████████/ ██║██╔██╗ ██║███████╗██║██║ ███╗███████║ ██║ ███████╗ | | /████████████████████████████████████/ ██║██║╚██╗██║╚════██║██║██║ ██║██╔══██║ ██║ ╚════██║ | | /████████████████████████████████████/ ██║██║ ╚████║███████║██║╚██████╔╝██║ ██║ ██║ ███████║ | | /████████████████████████████████████/ ╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ | | | |_____________________________________________________________________________________________________________________| '@ Write-Host -ForegroundColor White -BackgroundColor DarkBlue $motd } Write-Host -NoNewline 'Initializing: ' Write-Progress -Id 0 -Activity 'Configuring TDC Erhverv Security Insights permissions' -Status 'Initializing' -PercentComplete ((1 / 9) * 100) -ErrorAction SilentlyContinue if ($env:AZUREPS_HOST_ENVIRONMENT -notlike 'cloud-shell/*') { if (-not (Get-Module -Name Microsoft.Graph.Authentication -ListAvailable)) { Install-Module -Name 'Microsoft.Graph.Authentication' -Scope CurrentUser -AllowClobber -Force -WarningAction SilentlyContinue -ErrorAction Stop | Out-Null } Initialize-TDCErhvervSecurityInsightsMsalApp } Write-Host -ForegroundColor Black -BackgroundColor Green -Object ' OK ' } catch { Write-Host -ForegroundColor White -BackgroundColor Red -Object ' Failed ' throw } try { Write-Progress -Id 0 -Activity 'Configuring TDC Erhverv Security Insights permissions' -Status 'Authenticating' -PercentComplete ((2 / 9) * 100) -ErrorAction SilentlyContinue Write-Host -NoNewline 'Authenticating: ' $Authentication = Get-TDCErhvervSecurityInsightsToken -Force:$Force -ErrorAction Stop Connect-MgGraph -AccessToken $Authentication.SecureAccessToken -NoWelcome | Out-Null Write-Host -ForegroundColor Black -BackgroundColor Green -Object ' OK ' } catch { Write-Host -ForegroundColor White -BackgroundColor Red -Object ' Failed ' throw } try { Write-Progress -Id 0 -Activity 'Configuring TDC Erhverv Security Insights permissions' -Status 'Preparing modules' -PercentComplete ((3 / 9) * 100) -ErrorAction SilentlyContinue Write-Host -NoNewline 'Preparing modules: ' $EntraIDDirectoryRoles = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/directoryRoles' -OutputType PSObject -ErrorAction Stop).value $ServicePrincipals = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/servicePrincipals?$select=displayName,appId,id&filter=appId in ("1c47d605-cb57-4fe8-81f8-a878d718ada3", "a1a66b43-7a53-43a8-b986-41299f22becd", "59e16c46-95c2-442b-a45d-aa795c4892b7", "44c5021d-baf4-47b5-b35f-545b8ed1d464", "7d726de8-d48e-4304-9b21-f6eaf8d825e5", "b52dcb6f-c5df-436a-ae43-c039113ad7fa", "a6ff5070-d517-432b-8ed6-042c2a3560e1", "3286cce6-8b4a-4e8d-bd59-a02a9474e33d", "f0279a37-2a96-4d3d-8f19-d4eee0af7fc4", "ddfdf184-9b52-4d19-b5e6-79c8965cd0a5", "8e507df8-4bdc-44cb-bba4-b3d9812d8576", "80d592c4-5e3b-4469-8586-8d7a5d7fc3f6", "dcf00d66-0307-413a-8ff6-f6dede0d584f", "19641d75-3997-468f-84de-ddfa7e5e4e74")' -OutputType PSObject -ErrorAction Stop).value $TDCErhvervSecurityInsightsAnalyticsMSAzure = $ServicePrincipals | Where-Object { $_.AppId -in ('59e16c46-95c2-442b-a45d-aa795c4892b7', '44c5021d-baf4-47b5-b35f-545b8ed1d464') } $TDCErhvervSecurityInsightsAnalyticsMSExchangeOnline = $ServicePrincipals | Where-Object { $_.AppId -in ('7d726de8-d48e-4304-9b21-f6eaf8d825e5', 'b52dcb6f-c5df-436a-ae43-c039113ad7fa') } $TDCErhvervSecurityInsightsAnalyticsMSTeams = $ServicePrincipals | Where-Object { $_.AppId -in ('f0279a37-2a96-4d3d-8f19-d4eee0af7fc4', 'ddfdf184-9b52-4d19-b5e6-79c8965cd0a5') } $TDCErhvervSecurityInsightsAnalyticsMSPowerPlatform = $ServicePrincipals | Where-Object { $_.AppId -in ('8e507df8-4bdc-44cb-bba4-b3d9812d8576', '80d592c4-5e3b-4469-8586-8d7a5d7fc3f6') } $TDCErhvervSecurityInsightsAnalyticsMSPowerBI = $ServicePrincipals | Where-Object { $_.AppId -in ('dcf00d66-0307-413a-8ff6-f6dede0d584f', '19641d75-3997-468f-84de-ddfa7e5e4e74') } if ($env:AZUREPS_HOST_ENVIRONMENT -notlike 'cloud-shell/*') { [System.Collections.ArrayList]$ModulesToInstall = @() if ($TDCErhvervSecurityInsightsAnalyticsMSExchangeOnline -and -not (Get-Module -Name ExchangeOnlineManagement -ListAvailable)) { $ModulesToInstall.Add('ExchangeOnlineManagement') | Out-Null } if ($TDCErhvervSecurityInsightsAnalyticsMSAzure -and -not (Get-Module -Name Az.Accounts -ListAvailable)) { $ModulesToInstall.Add('Az.Accounts') | Out-Null } if ($TDCErhvervSecurityInsightsAnalyticsMSAzure -and -not (Get-Module -Name Az.Resources -ListAvailable)) { $ModulesToInstall.Add('Az.Resources') | Out-Null } if ($TDCErhvervSecurityInsightsAnalyticsMSPowerPlatform -and -not (Get-Module -Name Microsoft.PowerApps.Administration.PowerShell -ListAvailable)) { $ModulesToInstall.Add('Microsoft.PowerApps.Administration.PowerShell') | Out-Null } if ($ModulesToInstall.Count -gt 0) { Install-Module -Name $ModulesToInstall -Scope CurrentUser -AllowClobber -Force -WarningAction SilentlyContinue -ErrorAction Stop | Out-Null } } Write-Host -ForegroundColor Black -BackgroundColor Green -Object ' OK ' } catch { Write-Host -ForegroundColor White -BackgroundColor Red -Object ' Failed ' throw } Write-Host -NoNewline 'Configuring module [MSTeams]: ' if ($TDCErhvervSecurityInsightsAnalyticsMSTeams) { try { try { Write-Progress -Id 0 -Activity 'Configuring TDC Erhverv Security Insights permissions' -Status 'Configuring additional permissions for Microsoft Teams' -PercentComplete ((4 / 9) * 100) -ErrorAction SilentlyContinue $EntraIDDirectoryRoleTeamsReader = $EntraIDDirectoryRoles | Where-Object { $_.roleTemplateId -eq '1076ac91-f3d9-41a7-a339-dcdf5f480acc' } if (-not $EntraIDDirectoryRoleTeamsReader) { $EntraIDDirectoryRoleTeamsReader = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/directoryRoles' -Body @{roleTemplateId = '1076ac91-f3d9-41a7-a339-dcdf5f480acc' } -OutputType PSObject -ErrorAction Stop Write-Verbose -Message '[OK] Enabled Entra ID role: Teams Administrator' } } catch { Write-Verbose -Message "[Error] Failed to enable Entra ID role [Teams Administrator]: $($_.Exception.Message)" throw } try { $EntraIDDirectoryRoleTeamsReaderMembers = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/directoryRoles/$($EntraIDDirectoryRoleTeamsReader.id)/members" -OutputType PSObject).value foreach ($Module in $TDCErhvervSecurityInsightsAnalyticsMSTeams) { if ($Module.id -notin $EntraIDDirectoryRoleTeamsReaderMembers.id) { Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/directoryRoles/$($EntraIDDirectoryRoleTeamsReader.id)/members/`$ref" -Body @{'@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$($Module.id)" } -OutputType PSObject -ErrorAction Stop | Out-Null Write-Verbose -Message "[OK] Added Teams Administrator role to module: $($Module.id)" } } } catch { Write-Verbose -Message "[Error] Failed to assign Entra ID role [Teams Administrator] to module id [$($Module.id)]: $($_.Exception.Message)" throw } Write-Host -ForegroundColor Black -BackgroundColor Green -Object ' OK ' } catch { Write-Host -ForegroundColor White -BackgroundColor Red -Object ' Failed ' } } else { Write-Host -ForegroundColor Black -BackgroundColor Gray -Object ' Skipped ' } Write-Host -NoNewline 'Configuring module [MSAzure]: ' if ($TDCErhvervSecurityInsightsAnalyticsMSAzure) { try { try { Write-Progress -Id 0 -Activity 'Configuring TDC Erhverv Security Insights permissions' -Status 'Configuring additional permissions for Azure' -PercentComplete ((5 / 9) * 100) -ErrorAction SilentlyContinue Write-Verbose -Message 'Connecting to Azure' if ($env:AZUREPS_HOST_ENVIRONMENT -notlike 'cloud-shell/*') { Update-AzConfig -Scope Process -DefaultSubscriptionForLogin '' -LoginExperienceV2 Off -CheckForUpgrade $false -DisplayBreakingChangeWarning $false -DisplayRegionIdentified $false -DisplaySecretsWarning $false -DisplaySurveyMessage $false -EnableDataCollection $false -EnableErrorRecordsPersistence $false -EnableLoginByWam $false -WarningAction SilentlyContinue | Out-Null $Authentication = Get-TDCErhvervSecurityInsightsToken -Scope 'https://management.azure.com/.default' -ErrorAction Stop $AzAccountContext = Connect-AzAccount -AccessToken $Authentication.AccessToken -AccountId $Authentication.AccountId -WarningAction Ignore -InformationAction Ignore -ErrorAction Stop } $TenantId = (Get-AzContext).Tenant.Id Write-Verbose -Message '[OK] Connected to Azure' } catch { Write-Verbose -Message "[Error] Failed to connect to Azure: $($_.Exception.Message)" throw } try { # Use the bearer token directly for management group lookup — Connect-AzAccount # -AccessToken creates a token-only session with no refresh token, so Az cmdlets # that internally re-acquire tokens (e.g. Get-AzManagementGroup) will hit MFA/CA # policies. Calling the REST API directly with our MSAL token avoids this entirely. $AzureAuthHeader = @{ Authorization = "Bearer $($Authentication.AccessToken)" } $ManagementGroups = try { (Invoke-RestMethod -Method GET -Uri 'https://management.azure.com/providers/Microsoft.Management/managementGroups?api-version=2024-02-01-preview' -Headers $AzureAuthHeader -ErrorAction Stop).value } catch { $null } $TenantRootGroup = $ManagementGroups | Where-Object { $_.name -eq $TenantId } if ($TenantRootGroup) { [Array]$AssignableScopes = $TenantRootGroup.id } else { $Subscriptions = (Invoke-RestMethod -Method GET -Uri "https://management.azure.com/subscriptions?api-version=2025-04-01" -Headers $AzureAuthHeader -ErrorAction Stop).value [Array]$AssignableScopes = $Subscriptions | Where-Object { $_.tenantId -eq $TenantId } | ForEach-Object { $_.id } } Write-Verbose -Message "[OK] Found $($AssignableScopes.Count) assignable scopes." } catch { Write-Verbose -Message "[Error] Failed to get Azure assignable scopes: $($_.Exception.Message)" throw } try { # Use REST directly — Get/New-AzRoleAssignment also internally re-acquire tokens # which fails under MFA/CA policies when using a token-only Az session. $ReaderRoleDefinitionId = 'acdd72a7-3385-48ef-bd42-f606fba81ae7' foreach ($Scope in $AssignableScopes) { foreach ($Module in $TDCErhvervSecurityInsightsAnalyticsMSAzure) { Write-Verbose -Message "Assigning Azure role [Reader] to scope: ${Scope}" $ExistingAssignments = (Invoke-RestMethod -Method GET -Uri "https://management.azure.com${Scope}/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&`$filter=principalId eq '$($Module.id)'" -Headers $AzureAuthHeader -ErrorAction Stop).value $ExistingAssignment = $ExistingAssignments | Where-Object { $_.properties.roleDefinitionId -like "*/$ReaderRoleDefinitionId" } if (-not $ExistingAssignment) { $RoleAssignmentId = [System.Guid]::NewGuid().ToString() Invoke-RestMethod -Method PUT -Uri "https://management.azure.com${Scope}/providers/Microsoft.Authorization/roleAssignments/${RoleAssignmentId}?api-version=2022-04-01" -Headers $AzureAuthHeader -ContentType 'application/json' -Body (@{ properties = @{ roleDefinitionId = "${Scope}/providers/Microsoft.Authorization/roleDefinitions/${ReaderRoleDefinitionId}" principalId = $Module.id principalType = 'ServicePrincipal' } } | ConvertTo-Json) -ErrorAction Stop | Out-Null } Write-Verbose -Message "[OK] Assigned Azure role [Reader] to scope: ${Scope}" } } } catch { throw "Unable to assign Azure role [Reader]: $($_.Exception.Message)" } Write-Host -ForegroundColor Black -BackgroundColor Green -Object ' OK ' } catch { Write-Host -ForegroundColor White -BackgroundColor Red -Object " Failed: $($_.Exception.Message)" } } else { Write-Host -ForegroundColor Black -BackgroundColor Gray -Object ' Skipped ' } Write-Host -NoNewline 'Configuring module [MSExchangeOnline]: ' if ($TDCErhvervSecurityInsightsAnalyticsMSExchangeOnline) { try { try { Write-Progress -Id 0 -Activity 'Configuring TDC Erhverv Security Insights permissions' -Status 'Configuring additional permissions for Microsoft Exchange Online' -PercentComplete ((6 / 9) * 100) -ErrorAction SilentlyContinue Write-Verbose -Message 'Connecting to Exchange Online' if ($env:AZUREPS_HOST_ENVIRONMENT -like 'cloud-shell/*') { Connect-ExchangeOnline -ShowBanner:$false -ErrorAction Stop | Out-Null } else { $Authentication = Get-TDCErhvervSecurityInsightsToken -Scope 'https://outlook.office.com/.default' -ErrorAction Stop Connect-ExchangeOnline -AccessToken $Authentication.AccessToken -UserPrincipalName $Authentication.AccountId -ShowBanner:$false -ErrorAction Stop | Out-Null 2>&1 } Write-Verbose -Message '[OK] Connected to Exchange Online' } catch { Write-Verbose -Message "[Error] Failed to connect to Exchange Online: $($_.Exception.Message)" throw } try { $EntraIDDirectoryRoleSecurityReader = $EntraIDDirectoryRoles | Where-Object { $_.roleTemplateId -eq '5d6b6bb7-de71-4623-b4af-96380a352509' } if (-not $EntraIDDirectoryRoleSecurityReader) { $EntraIDDirectoryRoleSecurityReader = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/directoryRoles' -Body @{roleTemplateId = '5d6b6bb7-de71-4623-b4af-96380a352509' } -OutputType PSObject -ErrorAction Stop Write-Verbose -Message '[OK] Enabled Entra ID role: Security Reader' } } catch { Write-Verbose -Message "[Error] Failed to enable Entra ID role [Security Reader]: $($_.Exception.Message)" throw } try { $EntraIDDirectoryRoleSecurityReaderMembers = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/directoryRoles/$($EntraIDDirectoryRoleSecurityReader.id)/members" -OutputType PSObject).value foreach ($Module in $TDCErhvervSecurityInsightsAnalyticsMSExchangeOnline) { if ($Module.id -notin $EntraIDDirectoryRoleSecurityReaderMembers.id) { Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/directoryRoles/$($EntraIDDirectoryRoleSecurityReader.id)/members/`$ref" -Body @{'@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$($Module.id)" } -OutputType PSObject -ErrorAction Stop | Out-Null Write-Verbose -Message "[OK] Assigned Security Reader role to: $($Module.id)" } } } catch { Write-Verbose -Message "[Error] Failed to assign Entra ID role [Security Reader] to module id [$($Module.id)]: $($_.Exception.Message)" throw } try { foreach ($Module in $TDCErhvervSecurityInsightsAnalyticsMSExchangeOnline) { if (-not (Get-ServicePrincipal -Identity $Module.id -ErrorAction SilentlyContinue)) { New-ServicePrincipal -AppId $Module.appId -ObjectId $Module.id -DisplayName $Module.displayName -ErrorAction Stop | Out-Null Write-Verbose -Message "[OK] Created Exchange Online Service Principal for module: $($Module.displayName)" } } } catch { Write-Verbose -Message "[Error] Failed to create Exchange Online principal for module [$($Module.displayName)]: $($_.Exception.Message)" throw } try { foreach ($Module in $TDCErhvervSecurityInsightsAnalyticsMSExchangeOnline) { if (-not (Get-ManagementRoleAssignment -Role 'View-Only Configuration' -RoleAssignee $Module.id -ErrorAction SilentlyContinue)) { New-ManagementRoleAssignment -Name "View-Only Configuration-$($Module.appId)" -Role 'View-Only Configuration' -App $Module.displayName -ErrorAction Stop | Out-Null Write-Verbose -Message "[OK] Assigned Exchange Online role [View-Only Configuration] to module: $($Module.displayName)" } } } catch { Write-Verbose -Message "[Error] Failed to assign Exchange Online role [View-Only Configuration] to module [$($Module.displayName)]: $($_.Exception.Message)" throw } try { foreach ($Module in $TDCErhvervSecurityInsightsAnalyticsMSExchangeOnline) { if (-not (Get-ManagementRoleAssignment -Role 'View-Only Recipients' -RoleAssignee $Module.id -ErrorAction SilentlyContinue)) { New-ManagementRoleAssignment -Name "View-Only Recipients-$($Module.appId)" -Role 'View-Only Recipients' -App $Module.displayName -ErrorAction Stop | Out-Null Write-Verbose -Message "[OK] Assigned Exchange Online role [View-Only Recipients] to module: $($Module.displayName)" } } } catch { Write-Verbose -Message "[Error] Failed to assign Exchange Online role [View-Only Recipients] to module [$($Module.displayName)]: $($_.Exception.Message)" throw } Write-Host -ForegroundColor Black -BackgroundColor Green -Object ' OK ' } catch { Write-Host -ForegroundColor White -BackgroundColor Red -Object ' Failed ' } } else { Write-Host -ForegroundColor Black -BackgroundColor Gray -Object ' Skipped ' } Write-Host -NoNewline 'Configuring module [MSPowerPlatform]: ' if ($TDCErhvervSecurityInsightsAnalyticsMSPowerPlatform) { try { try { Write-Progress -Id 0 -Activity 'Configuring TDC Erhverv Security Insights permissions' -Status 'Configuring additional permissions for Microsoft Power Platform' -PercentComplete ((7 / 9) * 100) -ErrorAction SilentlyContinue Write-Verbose 'Connecting to Power Platform' $Authentication = Get-TDCErhvervSecurityInsightsToken -Scope 'https://management.azure.com/.default' -ErrorAction Stop $Authorization = @{Authorization = "Bearer $($Authentication.AccessToken)" } $PowerAppManagementApps = (Invoke-RestMethod -Method GET -Uri 'https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/adminApplications?api-version=2020-06-01' -Headers $Authorization -ErrorAction Stop).value Write-Verbose '[OK] Connected to Power Platform' } catch { Write-Verbose -Message "[Error] Failed to connect to Power Platform: $($_.Exception.Message)" throw } try { foreach ($Module in $TDCErhvervSecurityInsightsAnalyticsMSPowerPlatform) { if ($Module.appId -notin $PowerAppManagementApps.applicationId) { Write-Verbose "Adding Power Platform management app: $($Module.appId)" Invoke-RestMethod -Method PUT -Uri "https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/adminApplications/$($Module.appId)?api-version=2020-06-01" -Headers $Authorization -ErrorAction Stop | Out-Null Write-Verbose "[OK] Added Power Platform management app: $($Module.appId)" } } } catch { Write-Verbose -Message "[Error] Failed to add Power Platform management app [$($Module.appId)]: $($_.Exception.Message)" throw } Write-Host -ForegroundColor Black -BackgroundColor Green -Object ' OK ' } catch { Write-Host -ForegroundColor White -BackgroundColor Red -Object ' Failed: A license is required (e.g. Power Automate Free). Please assign this to your user and try again. ' } } else { Write-Host -ForegroundColor Black -BackgroundColor Gray -Object ' Skipped ' } Write-Host -NoNewline 'Configuring module [MSPowerBI]: ' if ($TDCErhvervSecurityInsightsAnalyticsMSPowerBI) { try { try { Write-Progress -Id 0 -Activity 'Configuring TDC Erhverv Security Insights permissions' -Status 'Configuring additional permissions for Microsoft Power BI' -PercentComplete ((8 / 9) * 100) -ErrorAction SilentlyContinue Write-Verbose -Message 'Connecting to Power BI' $Authentication = Get-TDCErhvervSecurityInsightsToken -Scope 'https://analysis.windows.net/powerbi/api/.default' -ErrorAction Stop $Authorization = @{Authorization = "Bearer $($Authentication.AccessToken)" } Write-Verbose -Message '[OK] Connected to Power BI' } catch { Write-Verbose -Message "[Error] Failed to connect to Power BI: $($_.Exception.Message)" throw } try { $EntraIDPowerBIGroup = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/groups?`$filter=displayName eq 'TDC Erhverv Security Insights Analytics - MS Power BI'" -OutputType PSObject -ErrorAction Stop).value if ($EntraIDPowerBIGroup.Count -gt 1) { throw 'There are more than one group with the name: TDC Erhverv Security Insights Analytics - MS Power BI' } elseif ($EntraIDPowerBIGroup) { $EntraIDPowerBIGroupMembers = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/groups/$($EntraIDPowerBIGroup.id)/members" -OutputType PSObject -ErrorAction Stop).value $EntraIDPowerBIGroupMemberCompare = Compare-Object -ReferenceObject $TDCErhvervSecurityInsightsAnalyticsMSPowerBI -DifferenceObject @($EntraIDPowerBIGroupMembers | Select-Object) -Property 'appId' -PassThru if ($EntraIDPowerBIGroupMemberCompare | Where-Object { $_.SideIndicator -eq '=>' }) { throw 'Unknown members in Entra ID Group: TDC Erhverv Security Insights Analytics - MS Power BI' } else { foreach ($Module in $EntraIDPowerBIGroupMemberCompare | Where-Object { $_.SideIndicator -eq '<=' }) { Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/groups/$($EntraIDPowerBIGroup.id)/members/`$ref" -Body @{'@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$($Module.id)" } -OutputType PSObject -ErrorAction Stop Write-Verbose -Message "[OK] Added group member to [TDC Erhverv Security Insights Analytics - MS Power BI]: $($Module.id)" } } } else { $EntraIDPowerBIGroup = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/groups' -Body @{'displayName' = 'TDC Erhverv Security Insights Analytics - MS Power BI'; description = 'This group is used for allowing TDC Erhverv Security Insights read-only access to Power BI Admin API.'; mailEnabled = $false; securityEnabled = $true; mailNickname = 'NotSet' } -OutputType PSObject -ErrorAction Stop do { $EntraIDPowerBIGroupAttempts++ if ($EntraIDPowerBIGroupAttempts -gt 10) { throw "Waiting for Power BI Group creation exceeded attempts: ${EntraIDPowerBIGroupAttempts}" } Start-Sleep -Seconds 5 Write-Verbose -Message "Waiting for Power BI Group creation attempt: ${EntraIDPowerBIGroupAttempts}" $EntraIDPowerBIGroup = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/groups?`$filter=displayName eq 'TDC Erhverv Security Insights Analytics - MS Power BI'" -OutputType PSObject -ErrorAction SilentlyContinue).value } while (-not $EntraIDPowerBIGroup) Write-Verbose -Message '[OK] Created Entra ID group: TDC Erhverv Security Insights Analytics - MS Power BI' foreach ($Module in $TDCErhvervSecurityInsightsAnalyticsMSPowerBI) { Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/groups/$($EntraIDPowerBIGroup.id)/members/`$ref" -Body @{'@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$($Module.id)" } -OutputType PSObject -ErrorAction Stop | Out-Null Write-Verbose -Message "[OK] Added group member: $($Module.id)" } } } catch { Write-Verbose -Message "[Error] Failed to configure Entra ID group [TDC Erhverv Security Insights Analytics - MS Power BI]: $($_.Exception.Message)" throw } try { $PowerBIAdminAPISettings = Invoke-RestMethod -UseBasicParsing -Uri 'https://api.fabric.microsoft.com/v1/admin/tenantsettings' -Method 'GET' -Headers $Authorization -ContentType 'application/json;charset=UTF-8' -ErrorAction Stop if ($EntraIDPowerBIGroup.id -notin $PowerBIAdminAPISettings.tenantSettings.Where({ $_.settingName -eq 'AllowServicePrincipalsUseReadAdminAPIs' }).enabledSecurityGroups.graphId) { $PowerBIAdminAPIAllowedSecurityGroups = New-Object System.Collections.ArrayList($null) if ($PowerBIAdminAPISettings.tenantSettings.Where({ $_.settingName -eq 'AllowServicePrincipalsUseReadAdminAPIs' }).enabledSecurityGroups.graphId) { $PowerBIAdminAPIAllowedSecurityGroups.AddRange(@($PowerBIAdminAPISettings.tenantSettings.Where({ $_.settingName -eq 'AllowServicePrincipalsUseReadAdminAPIs' }).enabledSecurityGroups)) } $PowerBIAdminAPIAllowedSecurityGroups.Add([PSCustomObject]@{graphId = $EntraIDPowerBIGroup.id; name = $EntraIDPowerBIGroup.DisplayName }) | Out-Null Invoke-RestMethod -UseBasicParsing -Uri 'https://api.fabric.microsoft.com/v1/admin/tenantsettings/AllowServicePrincipalsUseReadAdminAPIs/update' -Method 'POST' -Headers $Authorization -ContentType 'application/json;charset=UTF-8' -Body "{`"enabled`": true,`"enabledSecurityGroups`": $(ConvertTo-Json -InputObject $PowerBIAdminAPIAllowedSecurityGroups -Compress)}" -ErrorAction Stop | Out-Null Write-Verbose -Message '[OK] Added security group' } } catch { Write-Verbose -Message "[Error] Failed to add security group [TDC Erhverv Security Insights Analytics - MS Power BI] to [Service principals can access read-only admin APIs]: $($_.Exception.Message)" throw } Write-Host -ForegroundColor Black -BackgroundColor Green -Object ' OK ' } catch { Write-Host -ForegroundColor White -BackgroundColor Red -Object ' Failed: A license is required (e.g. Microsoft Fabric Free). Please assign this to your user and try again. ' } } else { Write-Host -ForegroundColor Black -BackgroundColor Gray -Object ' Skipped ' } if ($env:AZUREPS_HOST_ENVIRONMENT -notlike 'cloud-shell/*') { Write-Host -Object 'The enterprise registered application "TDC Erhverv Security Insights Configuration" should be removed from Entra ID.' } } catch { throw } finally { #Disconnect-MgGraph -WarningAction SilentlyContinue -ErrorAction SilentlyContinue | Out-Null #$AzAccountContext | Disconnect-AzAccount -WarningAction SilentlyContinue -ErrorAction SilentlyContinue | Out-Null #Disconnect-ExchangeOnline -Confirm:$false -WarningAction SilentlyContinue -ErrorAction SilentlyContinue | Out-Null } } Grant-TDCErhvervSecurityInsightsPermissions @PSBoundParameters |