From 7de044ea4ef873ec5b59b8632195f29bd3fc45a9 Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Thu, 27 Feb 2025 10:34:51 +0300 Subject: [PATCH 01/25] Initial commit --- .../Assign-EntraAppRoleToApplicationUsers.ps1 | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 module/Entra/Microsoft.Entra/Governance/Assign-EntraAppRoleToApplicationUsers.ps1 diff --git a/module/Entra/Microsoft.Entra/Governance/Assign-EntraAppRoleToApplicationUsers.ps1 b/module/Entra/Microsoft.Entra/Governance/Assign-EntraAppRoleToApplicationUsers.ps1 new file mode 100644 index 000000000..995954387 --- /dev/null +++ b/module/Entra/Microsoft.Entra/Governance/Assign-EntraAppRoleToApplicationUsers.ps1 @@ -0,0 +1,347 @@ +function Assign-EntraAppRoleToApplicationUsers { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic'")] + [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] + [string]$DataSource, + + [Parameter(Mandatory = $true, HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv")] + [ValidateNotNullOrEmpty()] + [ValidateScript({ Test-Path $_ })] + [string]$FileName, + + [Parameter(Mandatory = $true, HelpMessage = "Name of the application (Service Principal) to assign roles for")] + [ValidateNotNullOrEmpty()] + [string]$ApplicationName + ) + + process { + + # Call the orchestrator function + Write-Verbose "Service principal already exists for application with params: AppName - " $ApplicationName" | FileName - " $FileName" | DataSource - " $DataSource + + Main + + function Main() { + + try { + # Import users from the CSV file + $users = Import-Csv -Path $FileName + if (-not $users) { + Write-Error "No users found in the provided file: $FileName" + return + } + + # Define the property name for user lookup based on data source + $sourceMatchPropertyName = switch ($DataSource) { + "DatabaseorDirectory" { "email" } + "SAPCloudIdentity" { "userName" } + "Generic" { "userPrincipalName" } + } + + # Get or create the application and service principal once + $application = Get-OrCreateEntraApplication -DisplayName $ApplicationName + if (-not $application) { + Write-Error "Failed to retrieve or create application: $ApplicationName" + return + } + + # Get unique roles from users - commenting out this part due to creation error + <# $uniqueRoles = $users | + Where-Object { -not [string]::IsNullOrWhiteSpace($_.Role) } | + Select-Object -ExpandProperty Role -Unique + + if (-not $uniqueRoles) { + Write-Error "No roles found in the input file" + return + } + + Write-Verbose "Found $($uniqueRoles.Count) unique roles to process" + + # Create app roles for each unique role + $createdRoles = @{} + foreach ($role in $uniqueRoles) { + $cleanRoleName = Clean-PropertyValue -Value $role -PropertyName "Role" + if (-not $cleanRoleName) { continue } + + $newRole = New-AppRoleIfNotExists -ApplicationId $application.ApplicationId -RoleDisplayName $cleanRoleName + if ($newRole) { + $createdRoles[$cleanRoleName] = $newRole + Write-Verbose "Created/Retrieved role: $cleanRoleName" + } + } #> + + + # Process users in bulk where possible + foreach ($user in $users) { + $cleanUserPrincipalName = Clean-PropertyValue -Value $user.$sourceMatchPropertyName -PropertyName $sourceMatchPropertyName + if (-not $cleanUserPrincipalName) { + Write-Warning "Skipping user due to invalid sourceMatchPropertyName: $($user.$sourceMatchPropertyName)" + continue + } + + $cleanDisplayName = Clean-PropertyValue -Value $user.displayName -PropertyName "displayName" + + if (-not $cleanDisplayName) { + Write-Warning "Skipping user due to invalid sourceMatchPropertyName: $($user.$DisplayName)" + continue + } + $cleanMailNickname = Clean-PropertyValue -Value $user.mailNickname -PropertyName "mailNickname" + + if (-not $cleanMailNickname) { + Write-Warning "Skipping user due to invalid sourceMatchPropertyName: $($user.$MailNickname)" + continue + } + + # Get the user's role + $userRole = Clean-PropertyValue -Value $user.Role -PropertyName "Role" + if (-not $userRole -or -not $createdRoles.ContainsKey($userRole)) { + Write-Warning "Invalid or unmapped role for user $($userInfo.UserPrincipalName): $($user.Role)" + continue + } + + + # Create user if they do not exist + $userInfo = New-EntraUserIfNotExists -UserPrincipalName $cleanUserPrincipalName -DisplayName $cleanDisplayName -MailNickname $cleanMailNickname + if (-not $userInfo) { continue } + + # Assign roles to the user (Placeholder for role assignment logic) + + $assignment = Assign-AppServicePrincipalRoleAssignmentIfNotExists -ServicePrincipalId $application.ServicePrincipalId -UserId $userInfo.Id -ApplicationName $ApplicationName -RoleDisplayName $userRole + if (-not $assignment) { continue } + Write-Verbose "Assigning roles to user $($userInfo.UserPrincipalName) in application $ApplicationName" + } + } + catch { + Write-Error "Error in Start-Orchestration: $_" + } + } + + function New-EntraUserIfNotExists($UserPrincipalName, $DisplayName, $MailNickname) { + + try { + # Check if user exists + $existingUser = Get-EntraUser -Filter "userPrincipalName eq '$UserPrincipalName'" -ErrorAction SilentlyContinue + if ($existingUser) { + Write-Verbose "User exists: $UserPrincipalName" + return $existingUser + } + + # Create new user + $params = @{ + UserPrincipalName = $UserPrincipalName + DisplayName = ($UserPrincipalName -split '@')[0] + MailNickname = ($UserPrincipalName -split '@')[0] + AccountEnabled = $false + PasswordProfile = @{ + Password = -join (((48..90) + (96..122)) * 16 | Get-Random -Count 16 | % { [char]$_ }) + ForceChangePasswordNextSignIn = $true + } + } + + $newUser = New-EntraUser @params + Write-Verbose "Created new user: $UserPrincipalName" + return $newUser + } + catch { + Write-Error "Failed to create or verify user $($UserPrincipalName): $_" + return $null + } + } + + function Clean-PropertyValue($Value, $PropertyName) { + + try { + if ([string]::IsNullOrWhiteSpace($Value)) { + Write-Warning "Invalid $PropertyName value" + return $null + } + + # Remove unwanted characters and normalize + $cleanValue = $Value -replace '[^\w\s@\.-]', '' -replace '\s+', ' ' | ForEach-Object { $_.Trim().ToLower() } + + return if ([string]::IsNullOrWhiteSpace($cleanValue)) { + Write-Warning "$PropertyName is empty after cleanup" + $null + } else { $cleanValue } + } + catch { + Write-Error "Error cleaning $($PropertyName): $($_)" + return $null + } + } + + function Get-OrCreateEntraApplication($DisplayName) { + + try { + # Check if application exists + $existingApp = Get-EntraApplication -Filter "displayName eq '$DisplayName'" -ErrorAction SilentlyContinue + + if (-not $existingApp) { + # Create new application + $appParams = @{ + DisplayName = $DisplayName + Description = $DisplayName + SignInAudience = "AzureADMyOrg" + Web = @{ + RedirectUris = @("https://localhost") + } + } + + $newApp = New-EntraApplication @appParams + Write-Verbose "Created new application: $DisplayName" + + # Create service principal for the application + $spParams = @{ + AppId = $newApp.AppId + DisplayName = $DisplayName + } + + $newSp = New-EntraServicePrincipal @spParams + Write-Verbose "Created new service principal for application: $DisplayName" + + [PSCustomObject]@{ + ApplicationId = $newApp.Id + ApplicationDisplayName = $newApp.DisplayName + ServicePrincipalId = $newSp.Id + ServicePrincipalDisplayName = $newSp.DisplayName + Status = 'Created' + } + } + else { + # Get existing service principal + $existingSp = Get-EntraServicePrincipal -Filter "appId eq '$($existingApp.AppId)'" -ErrorAction SilentlyContinue + + if (-not $existingSp) { + # Create service principal if it doesn't exist + $spParams = @{ + AppId = $existingApp.AppId + DisplayName = $DisplayName + } + + $newSp = New-EntraServicePrincipal @spParams + Write-Verbose "Created new service principal for existing application: $DisplayName" + } + else { + $newSp = $existingSp + Write-Verbose "Service principal already exists for application: $DisplayName" + } + + [PSCustomObject]@{ + ApplicationId = $existingApp.Id + ApplicationDisplayName = $existingApp.DisplayName + ServicePrincipalId = $newSp.Id + ServicePrincipalDisplayName = $newSp.DisplayName + Status = 'Exists' + } + } + } + catch { + Write-Error "Error in Get-OrCreateEntraApplication: $_" + return $null + } + } + + function New-AppRoleIfNotExists($ApplicationId, $RoleDisplayName) { + + try { + # Get existing application + $application = Get-EntraApplication -ApplicationId $ApplicationId -ErrorAction Stop + if (-not $application) { + Write-Error "Application not found with ID: $ApplicationId" + return $null + } + + $AllowedMemberTypes = "User" + + # Create new app role + $appRole = @{ + AllowedMemberTypes = $AllowedMemberTypes + Description = $RoleDisplayName + DisplayName = $RoleDisplayName + Id = [guid]::NewGuid() + IsEnabled = $true + Value = $RoleValue ?? $RoleDisplayName + } + + # Get existing roles and add new role + $existingRoles = $application.AppRoles ?? @() + + # Check if role already exists + if ($existingRoles.Where({ $_.DisplayName -eq $RoleDisplayName })) { + Write-Warning "Role '$RoleDisplayName' already exists in application" + return $null + } + + $updatedRoles = $existingRoles + $appRole + + # Update application with new role + $params = @{ + ApplicationId = $ApplicationId + AppRoles = $updatedRoles + Tags = @("WindowsAzureActiveDirectoryIntegratedApp") + } + + $updatedApp = Set-EntraApplication @params + + Write-Verbose "Created new role '$RoleDisplayName' in application '$($application.DisplayName)'" + + # Return the newly created role + return [PSCustomObject]@{ + ApplicationId = $ApplicationId + RoleId = $appRole.Id + DisplayName = $RoleDisplayName + Description = $RoleDescription + Value = $appRole.Value + IsEnabled = $true + } + } + catch { + Write-Error "Failed to create app role: $_" + return $null + } + } + + function Assign-AppServicePrincipalRoleAssignmentIfNotExists($ServicePrincipalId, $UserId, $ApplicationName, $RoleDisplayName) { + + try { + # Check if assignment exists + + $servicePrincipalObject = Get-EntraServicePrincipal -ServicePrincipalId $ServicePrincipalId + $appRoleId = $servicePrincipalObject.AppRoles | Where-Object { $_.displayName -eq $RoleDisplayName } | Select-Object -Property Id + + $existingAssignment = Get-EntraServicePrincipalAppRoleAssignedTo -ServicePrincipalId $ServicePrincipalId | Where-Object { $_.AppRoleId -eq $appRoleId } -ErrorAction SilentlyContinue + + if ($existingAssignment) { + Write-Verbose "Role assignment already exists for user '$ApplicationName' with role '$RoleDisplayName'" + + return [PSCustomObject]@{ + ServicePrincipalId = $ServicePrincipalId + PrincipalId = $UserId + AppRoleId = $appRoleId + Status = 'Exists' + } + } + + # Create new assignment + + $newAssignment = New-EntraServicePrincipalAppRoleAssignment -ServicePrincipalId $ServicePrincipalId -ResourceId $ServicePrincipalId -Id $appRoleId -PrincipalId $UserId + Write-Verbose "Created new role assignment for user '$UserId' - AppName: '$ApplicationName' with role '$RoleDisplayName'" + + return [PSCustomObject]@{ + ServicePrincipalId = $ServicePrincipalId + PrincipalId = $UserId + AppRoleId = $appRoleId + Status = 'Created' + } + } + catch { + Write-Error "Failed to create or verify role assignment: $_" + return $null + } + } + + } + +} + From 41fff779ed09886e557e8efc9ab02b526e6937a4 Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Fri, 28 Feb 2025 11:39:21 +0300 Subject: [PATCH 02/25] Add export capability --- .../Assign-EntraAppRoleToApplicationUsers.ps1 | 347 ------------ .../Set-EntraAppRoleToApplicationUser.ps1 | 529 ++++++++++++++++++ 2 files changed, 529 insertions(+), 347 deletions(-) delete mode 100644 module/Entra/Microsoft.Entra/Governance/Assign-EntraAppRoleToApplicationUsers.ps1 create mode 100644 module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 diff --git a/module/Entra/Microsoft.Entra/Governance/Assign-EntraAppRoleToApplicationUsers.ps1 b/module/Entra/Microsoft.Entra/Governance/Assign-EntraAppRoleToApplicationUsers.ps1 deleted file mode 100644 index 995954387..000000000 --- a/module/Entra/Microsoft.Entra/Governance/Assign-EntraAppRoleToApplicationUsers.ps1 +++ /dev/null @@ -1,347 +0,0 @@ -function Assign-EntraAppRoleToApplicationUsers { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true, HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic'")] - [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] - [string]$DataSource, - - [Parameter(Mandatory = $true, HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv")] - [ValidateNotNullOrEmpty()] - [ValidateScript({ Test-Path $_ })] - [string]$FileName, - - [Parameter(Mandatory = $true, HelpMessage = "Name of the application (Service Principal) to assign roles for")] - [ValidateNotNullOrEmpty()] - [string]$ApplicationName - ) - - process { - - # Call the orchestrator function - Write-Verbose "Service principal already exists for application with params: AppName - " $ApplicationName" | FileName - " $FileName" | DataSource - " $DataSource - - Main - - function Main() { - - try { - # Import users from the CSV file - $users = Import-Csv -Path $FileName - if (-not $users) { - Write-Error "No users found in the provided file: $FileName" - return - } - - # Define the property name for user lookup based on data source - $sourceMatchPropertyName = switch ($DataSource) { - "DatabaseorDirectory" { "email" } - "SAPCloudIdentity" { "userName" } - "Generic" { "userPrincipalName" } - } - - # Get or create the application and service principal once - $application = Get-OrCreateEntraApplication -DisplayName $ApplicationName - if (-not $application) { - Write-Error "Failed to retrieve or create application: $ApplicationName" - return - } - - # Get unique roles from users - commenting out this part due to creation error - <# $uniqueRoles = $users | - Where-Object { -not [string]::IsNullOrWhiteSpace($_.Role) } | - Select-Object -ExpandProperty Role -Unique - - if (-not $uniqueRoles) { - Write-Error "No roles found in the input file" - return - } - - Write-Verbose "Found $($uniqueRoles.Count) unique roles to process" - - # Create app roles for each unique role - $createdRoles = @{} - foreach ($role in $uniqueRoles) { - $cleanRoleName = Clean-PropertyValue -Value $role -PropertyName "Role" - if (-not $cleanRoleName) { continue } - - $newRole = New-AppRoleIfNotExists -ApplicationId $application.ApplicationId -RoleDisplayName $cleanRoleName - if ($newRole) { - $createdRoles[$cleanRoleName] = $newRole - Write-Verbose "Created/Retrieved role: $cleanRoleName" - } - } #> - - - # Process users in bulk where possible - foreach ($user in $users) { - $cleanUserPrincipalName = Clean-PropertyValue -Value $user.$sourceMatchPropertyName -PropertyName $sourceMatchPropertyName - if (-not $cleanUserPrincipalName) { - Write-Warning "Skipping user due to invalid sourceMatchPropertyName: $($user.$sourceMatchPropertyName)" - continue - } - - $cleanDisplayName = Clean-PropertyValue -Value $user.displayName -PropertyName "displayName" - - if (-not $cleanDisplayName) { - Write-Warning "Skipping user due to invalid sourceMatchPropertyName: $($user.$DisplayName)" - continue - } - $cleanMailNickname = Clean-PropertyValue -Value $user.mailNickname -PropertyName "mailNickname" - - if (-not $cleanMailNickname) { - Write-Warning "Skipping user due to invalid sourceMatchPropertyName: $($user.$MailNickname)" - continue - } - - # Get the user's role - $userRole = Clean-PropertyValue -Value $user.Role -PropertyName "Role" - if (-not $userRole -or -not $createdRoles.ContainsKey($userRole)) { - Write-Warning "Invalid or unmapped role for user $($userInfo.UserPrincipalName): $($user.Role)" - continue - } - - - # Create user if they do not exist - $userInfo = New-EntraUserIfNotExists -UserPrincipalName $cleanUserPrincipalName -DisplayName $cleanDisplayName -MailNickname $cleanMailNickname - if (-not $userInfo) { continue } - - # Assign roles to the user (Placeholder for role assignment logic) - - $assignment = Assign-AppServicePrincipalRoleAssignmentIfNotExists -ServicePrincipalId $application.ServicePrincipalId -UserId $userInfo.Id -ApplicationName $ApplicationName -RoleDisplayName $userRole - if (-not $assignment) { continue } - Write-Verbose "Assigning roles to user $($userInfo.UserPrincipalName) in application $ApplicationName" - } - } - catch { - Write-Error "Error in Start-Orchestration: $_" - } - } - - function New-EntraUserIfNotExists($UserPrincipalName, $DisplayName, $MailNickname) { - - try { - # Check if user exists - $existingUser = Get-EntraUser -Filter "userPrincipalName eq '$UserPrincipalName'" -ErrorAction SilentlyContinue - if ($existingUser) { - Write-Verbose "User exists: $UserPrincipalName" - return $existingUser - } - - # Create new user - $params = @{ - UserPrincipalName = $UserPrincipalName - DisplayName = ($UserPrincipalName -split '@')[0] - MailNickname = ($UserPrincipalName -split '@')[0] - AccountEnabled = $false - PasswordProfile = @{ - Password = -join (((48..90) + (96..122)) * 16 | Get-Random -Count 16 | % { [char]$_ }) - ForceChangePasswordNextSignIn = $true - } - } - - $newUser = New-EntraUser @params - Write-Verbose "Created new user: $UserPrincipalName" - return $newUser - } - catch { - Write-Error "Failed to create or verify user $($UserPrincipalName): $_" - return $null - } - } - - function Clean-PropertyValue($Value, $PropertyName) { - - try { - if ([string]::IsNullOrWhiteSpace($Value)) { - Write-Warning "Invalid $PropertyName value" - return $null - } - - # Remove unwanted characters and normalize - $cleanValue = $Value -replace '[^\w\s@\.-]', '' -replace '\s+', ' ' | ForEach-Object { $_.Trim().ToLower() } - - return if ([string]::IsNullOrWhiteSpace($cleanValue)) { - Write-Warning "$PropertyName is empty after cleanup" - $null - } else { $cleanValue } - } - catch { - Write-Error "Error cleaning $($PropertyName): $($_)" - return $null - } - } - - function Get-OrCreateEntraApplication($DisplayName) { - - try { - # Check if application exists - $existingApp = Get-EntraApplication -Filter "displayName eq '$DisplayName'" -ErrorAction SilentlyContinue - - if (-not $existingApp) { - # Create new application - $appParams = @{ - DisplayName = $DisplayName - Description = $DisplayName - SignInAudience = "AzureADMyOrg" - Web = @{ - RedirectUris = @("https://localhost") - } - } - - $newApp = New-EntraApplication @appParams - Write-Verbose "Created new application: $DisplayName" - - # Create service principal for the application - $spParams = @{ - AppId = $newApp.AppId - DisplayName = $DisplayName - } - - $newSp = New-EntraServicePrincipal @spParams - Write-Verbose "Created new service principal for application: $DisplayName" - - [PSCustomObject]@{ - ApplicationId = $newApp.Id - ApplicationDisplayName = $newApp.DisplayName - ServicePrincipalId = $newSp.Id - ServicePrincipalDisplayName = $newSp.DisplayName - Status = 'Created' - } - } - else { - # Get existing service principal - $existingSp = Get-EntraServicePrincipal -Filter "appId eq '$($existingApp.AppId)'" -ErrorAction SilentlyContinue - - if (-not $existingSp) { - # Create service principal if it doesn't exist - $spParams = @{ - AppId = $existingApp.AppId - DisplayName = $DisplayName - } - - $newSp = New-EntraServicePrincipal @spParams - Write-Verbose "Created new service principal for existing application: $DisplayName" - } - else { - $newSp = $existingSp - Write-Verbose "Service principal already exists for application: $DisplayName" - } - - [PSCustomObject]@{ - ApplicationId = $existingApp.Id - ApplicationDisplayName = $existingApp.DisplayName - ServicePrincipalId = $newSp.Id - ServicePrincipalDisplayName = $newSp.DisplayName - Status = 'Exists' - } - } - } - catch { - Write-Error "Error in Get-OrCreateEntraApplication: $_" - return $null - } - } - - function New-AppRoleIfNotExists($ApplicationId, $RoleDisplayName) { - - try { - # Get existing application - $application = Get-EntraApplication -ApplicationId $ApplicationId -ErrorAction Stop - if (-not $application) { - Write-Error "Application not found with ID: $ApplicationId" - return $null - } - - $AllowedMemberTypes = "User" - - # Create new app role - $appRole = @{ - AllowedMemberTypes = $AllowedMemberTypes - Description = $RoleDisplayName - DisplayName = $RoleDisplayName - Id = [guid]::NewGuid() - IsEnabled = $true - Value = $RoleValue ?? $RoleDisplayName - } - - # Get existing roles and add new role - $existingRoles = $application.AppRoles ?? @() - - # Check if role already exists - if ($existingRoles.Where({ $_.DisplayName -eq $RoleDisplayName })) { - Write-Warning "Role '$RoleDisplayName' already exists in application" - return $null - } - - $updatedRoles = $existingRoles + $appRole - - # Update application with new role - $params = @{ - ApplicationId = $ApplicationId - AppRoles = $updatedRoles - Tags = @("WindowsAzureActiveDirectoryIntegratedApp") - } - - $updatedApp = Set-EntraApplication @params - - Write-Verbose "Created new role '$RoleDisplayName' in application '$($application.DisplayName)'" - - # Return the newly created role - return [PSCustomObject]@{ - ApplicationId = $ApplicationId - RoleId = $appRole.Id - DisplayName = $RoleDisplayName - Description = $RoleDescription - Value = $appRole.Value - IsEnabled = $true - } - } - catch { - Write-Error "Failed to create app role: $_" - return $null - } - } - - function Assign-AppServicePrincipalRoleAssignmentIfNotExists($ServicePrincipalId, $UserId, $ApplicationName, $RoleDisplayName) { - - try { - # Check if assignment exists - - $servicePrincipalObject = Get-EntraServicePrincipal -ServicePrincipalId $ServicePrincipalId - $appRoleId = $servicePrincipalObject.AppRoles | Where-Object { $_.displayName -eq $RoleDisplayName } | Select-Object -Property Id - - $existingAssignment = Get-EntraServicePrincipalAppRoleAssignedTo -ServicePrincipalId $ServicePrincipalId | Where-Object { $_.AppRoleId -eq $appRoleId } -ErrorAction SilentlyContinue - - if ($existingAssignment) { - Write-Verbose "Role assignment already exists for user '$ApplicationName' with role '$RoleDisplayName'" - - return [PSCustomObject]@{ - ServicePrincipalId = $ServicePrincipalId - PrincipalId = $UserId - AppRoleId = $appRoleId - Status = 'Exists' - } - } - - # Create new assignment - - $newAssignment = New-EntraServicePrincipalAppRoleAssignment -ServicePrincipalId $ServicePrincipalId -ResourceId $ServicePrincipalId -Id $appRoleId -PrincipalId $UserId - Write-Verbose "Created new role assignment for user '$UserId' - AppName: '$ApplicationName' with role '$RoleDisplayName'" - - return [PSCustomObject]@{ - ServicePrincipalId = $ServicePrincipalId - PrincipalId = $UserId - AppRoleId = $appRoleId - Status = 'Created' - } - } - catch { - Write-Error "Failed to create or verify role assignment: $_" - return $null - } - } - - } - -} - diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 new file mode 100644 index 000000000..aec7a8fbb --- /dev/null +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -0,0 +1,529 @@ +function Set-EntraAppRoleToApplicationUser { + [CmdletBinding(DefaultParameterSetName = 'Default')] + param ( + [Parameter(Mandatory = $true, + HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic'", + ParameterSetName = 'Default')] + [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] + [string]$DataSource, + + [Parameter(Mandatory = $true, + HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv", + ParameterSetName = 'Default')] + [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + [ValidateNotNullOrEmpty()] + [ValidateScript({ Test-Path $_ })] + [string]$FileName, + + [Parameter(Mandatory = $true, + HelpMessage = "Name of the application (Service Principal) to assign roles for", + ParameterSetName = 'Default')] + [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + [ValidateNotNullOrEmpty()] + [string]$ApplicationName, + + [Parameter(Mandatory = $true, + ParameterSetName = 'ExportResults', + HelpMessage = "Switch to enable export of results into a CSV file")] + [switch]$Export, + + [Parameter(Mandatory = $false, + ParameterSetName = 'ExportResults', + HelpMessage = "Path for the export file e.g. EntraAppRoleAssignments_yyyyMMdd.csv . If not specified, uses current location")] + [string]$ExportFileName = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv") + ) + + process { + + + # Custom Verbose Function to Avoid Overriding Built-in Write-ColoredVerbose + function Write-ColoredVerbose { + param ( + [Parameter(Mandatory = $true)] + [string]$Message, + + [ValidateSet("Green", "Yellow", "Red", "Cyan", "Magenta")] + [string]$Color = "Cyan" + ) + + if ($VerbosePreference -eq "Continue") { + Write-Host "VERBOSE: $Message" -ForegroundColor $Color + } + } + + function SanitizeInputParameter { + param ([string]$Value) + + try { + if ([string]::IsNullOrWhiteSpace($Value)) { + Write-Warning "Input is empty or null." + return $null + } + + $cleanValue = $Value -replace "'", "''" + $cleanValue = $cleanValue -replace '\s+', ' ' # Replace multiple spaces with a single space + $cleanValue = $cleanValue.Trim().ToLower() # Trim and convert to lowercase + + if ([string]::IsNullOrWhiteSpace($cleanValue)) { + Write-Warning "Input became empty after sanitization." + return $null + } + + return $cleanValue + } + catch { + Write-Error "Error cleaning value: $_" + return $null + } + } + + function CreateUserIfNotExistsNew { + param ( + [string]$UserPrincipalName, + [string]$DisplayName, + [string]$MailNickname + ) + + Write-ColoredVerbose -Message "User details: DisplayName $DisplayName | UserPrincipalName: $UserPrincipalName | MailNickname: $MailNickname" -Color "Cyan" + + try { + $existingUser = Get-EntraUser -Filter "userPrincipalName eq '$UserPrincipalName'" -ErrorAction SilentlyContinue + if ($existingUser) { + Write-ColoredVerbose -Message "User $UserPrincipalName exists." -Color "Green" + return [PSCustomObject]@{ + Id = $existingUser.Id + UserPrincipalName = $existingUser.UserPrincipalName + DisplayName = $existingUser.DisplayName + MailNickname = $existingUser.MailNickname + Status = 'Exists' + } + } + + $passwordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile + $passwordProfile.EnforceChangePasswordPolicy = $true + $passwordProfile.Password = -join (((48..90) + (96..122)) * 16 | Get-Random -Count 16 | % { [char]$_ }) + $userParams = @{ + DisplayName = $DisplayName + PasswordProfile = $passwordProfile + UserPrincipalName = $UserPrincipalName + AccountEnabled = $false + MailNickName = $MailNickname + } + + $newUser = New-EntraUser @userParams + Write-ColoredVerbose -Message "Created new user: $UserPrincipalName" -Color "Green" + + return [PSCustomObject]@{ + Id = $newUser.Id + UserPrincipalName = $newUser.UserPrincipalName + DisplayName = $newUser.DisplayName + MailNickname = $newUser.MailNickname + Status = 'Created' + } + } + catch { + Write-Error "Failed to create or verify user $($UserPrincipalName) $_" + return $null + } + } + + function CreateUserIfNotExists { + param ( + [string]$UserPrincipalName, + [string]$DisplayName, + [string]$MailNickname + ) + + Write-ColoredVerbose -Message "User details: DisplayName $DisplayName | UserPrincipalName: $UserPrincipalName | MailNickname: $MailNickname" -Color "Cyan" + + try { + $existingUser = Get-EntraUser -Filter "userPrincipalName eq '$($UserPrincipalName)'" -ErrorAction SilentlyContinue + if ($existingUser) { + Write-ColoredVerbose -Message "User $UserPrincipalName exists." -Color "Green" + return $existingUser + } + + $passwordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile + $passwordProfile.EnforceChangePasswordPolicy = $true + $passwordProfile.Password = -join (((48..90) + (96..122)) * 16 | Get-Random -Count 16 | % { [char]$_ }) + $userParams = @{ + DisplayName = $DisplayName + PasswordProfile = $passwordProfile + UserPrincipalName = $UserPrincipalName + AccountEnabled = $false + MailNickName = $MailNickname + } + + $newUser = New-EntraUser @userParams + + Write-ColoredVerbose -Message "Created new user: $UserPrincipalName" -Color "Green" + return $newUser + } + catch { + Write-Error "Failed to create or verify user $($UserPrincipalName) : $_" + return $null + } + } + + function CreateApplicationIfNotExists { + param ([string]$DisplayName) + + try { + # Check if application exists + + $existingApp = Get-EntraApplication -Filter "displayName eq '$DisplayName'" -ErrorAction SilentlyContinue + + if (-not $existingApp) { + # Create new application + $appParams = @{ + DisplayName = $DisplayName + SignInAudience = "AzureADMyOrg" + Web = @{ + RedirectUris = @("https://localhost") + } + } + + $newApp = New-EntraApplication @appParams + Write-ColoredVerbose "Created new application: $DisplayName" + + # Create service principal for the application + $spParams = @{ + AppId = $newApp.AppId + DisplayName = $DisplayName + } + + $newSp = New-EntraServicePrincipal @spParams + Write-ColoredVerbose "Created new service principal for application: $DisplayName" + + [PSCustomObject]@{ + ApplicationId = $newApp.Id + ApplicationDisplayName = $newApp.DisplayName + ServicePrincipalId = $newSp.Id + ServicePrincipalDisplayName = $newSp.DisplayName + AppId = $newApp.AppId + Status = 'Created' + } + } + else { + # Get existing service principal + $existingSp = Get-EntraServicePrincipal -Filter "appId eq '$($existingApp.AppId)'" -ErrorAction SilentlyContinue + + if (-not $existingSp) { + # Create service principal if it doesn't exist + $spParams = @{ + AppId = $existingApp.AppId + DisplayName = $DisplayName + } + + $newSp = New-EntraServicePrincipal @spParams + Write-ColoredVerbose "Created new service principal for existing application: $DisplayName" + } + else { + $newSp = $existingSp + Write-ColoredVerbose "Service principal already exists for application: $DisplayName" + } + + [PSCustomObject]@{ + ApplicationId = $existingApp.Id + ApplicationDisplayName = $existingApp.DisplayName + ServicePrincipalId = $newSp.Id + ServicePrincipalDisplayName = $newSp.DisplayName + AppId = $existingApp.AppId + Status = 'Exists' + } + } + } + catch { + Write-Error "Error in CreateApplicationIfNotExists: $_" + return $null + } + } + + function AssignAppServicePrincipalRoleAssignmentIfNotExists { + + param ( + [string]$ServicePrincipalId, + [string]$UserId, + [string]$ApplicationName, + [string]$RoleDisplayName + ) + + try { + # Check if assignment exists + + $servicePrincipalObject = Get-EntraServicePrincipal -ServicePrincipalId $ServicePrincipalId + $appRoleId = ($servicePrincipalObject.AppRoles | Where-Object { $_.displayName -eq $RoleDisplayName }).Id + + $existingAssignment = Get-EntraServicePrincipalAppRoleAssignedTo -ServicePrincipalId $servicePrincipalObject.Id | Where-Object { $_.AppRoleId -eq $appRoleId } -ErrorAction SilentlyContinue + + if ($existingAssignment) { + Write-ColoredVerbose "Role assignment already exists for user '$ApplicationName' with role '$RoleDisplayName'" -Color "Yellow" + + return [PSCustomObject]@{ + ServicePrincipalId = $ServicePrincipalId + PrincipalId = $UserId + AppRoleId = $appRoleId + AssignmentId = $existingAssignment.Id + Status = 'Exists' + CreatedDateTime = $existingAssignment.CreatedDateTime #?? (Get-Date).ToUniversalTime().ToString("o") + } + } + + # Create new assignment + $newAssignment = New-EntraServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipalObject.Id -ResourceId $servicePrincipalObject.Id -Id $appRoleId -PrincipalId $UserId + Write-ColoredVerbose "Created new role assignment for user '$UserId' - AppName: '$ApplicationName' with role '$RoleDisplayName'" -Color "Green" + + return [PSCustomObject]@{ + ServicePrincipalId = $ServicePrincipalId + PrincipalId = $UserId + AppRoleId = $appRoleId + AssignmentId = $newAssignment.Id + Status = 'Created' + CreatedDateTime = $newAssignment.CreatedDateTime #(Get-Date).ToUniversalTime().ToString("o") # ISO 8601 format + } + } + catch { + Write-Error "Failed to create or verify role assignment: $_)" + return $null + } + } + + function NewAppRoleIfNotExists { + param ( + [Parameter(Mandatory = $true)] + [string[]]$UniqueRoles, + + [Parameter(Mandatory = $true)] + [string]$ApplicationId + ) + + try { + # Get existing application + $application = Get-MgApplication -ApplicationId $ApplicationId -ErrorAction Stop + if (-not $application) { + Write-Error "Application not found with ID: $ApplicationId" + return $null + } + + # Ensure the existing AppRoles are properly formatted + $existingRoles = $application.AppRoles ?? @() + $appRolesList = New-Object 'System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphAppRole]' + + foreach ($role in $existingRoles) { + $appRolesList.Add($role) + } + + $allowedMemberTypes = @("User") # Define allowed member types + $createdRoles = [System.Collections.ArrayList]::new() + + foreach ($roleName in $UniqueRoles) { + # Check if the role already exists + if ($existingRoles | Where-Object { $_.DisplayName -eq $roleName }) { + Write-ColoredVerbose "Role '$roleName' already exists in application" -Color "Yellow" + continue + } + + # Create new AppRole object + $appRole = [Microsoft.Graph.PowerShell.Models.MicrosoftGraphAppRole]@{ + AllowedMemberTypes = $allowedMemberTypes + Description = $roleName + DisplayName = $roleName + Id = [Guid]::NewGuid() + IsEnabled = $true + Value = $roleName + } + + # Add to the typed list + $appRolesList.Add($appRole) + [void]$createdRoles.Add($appRole) + Write-ColoredVerbose "Created new role definition for '$roleName'" -Color "Green" + } + + if ($createdRoles.Count -gt 0) { + # Update application with the new typed list + $params = @{ + ApplicationId = $ApplicationId + AppRoles = $appRolesList + Tags = @("WindowsAzureActiveDirectoryIntegratedApp") + } + + Update-MgApplication @params + Write-ColoredVerbose "Updated application with $($createdRoles.Count) new roles" -Color "Green" + + return $createdRoles | ForEach-Object { + [PSCustomObject]@{ + ApplicationId = $ApplicationId + RoleId = $_.Id + DisplayName = $_.DisplayName + Description = $_.Description + Value = $_.Value + IsEnabled = $true + } + } + } + + Write-ColoredVerbose "No new roles needed to be created" -Color "Yellow" + return $null + } + catch { + Write-Error "Failed to create app roles: $_" + return $null + } + } + + function StartOrchestration { + + try { + # Import users from the CSV file + Write-ColoredVerbose "Importing users from file: $FileName" -Color "Cyan" + $users = Import-Csv -Path $FileName + Write-ColoredVerbose "Imported : $($users.Count) users" -Color "Green" + if (-not $users) { + Write-Error "No users found in the provided file: $FileName" + return + } + + # Define the property name for user lookup based on data source + Write-ColoredVerbose "Using: $DataSource for pattern matching" -Color "Cyan" + $sourceMatchPropertyName = switch ($DataSource) { + "DatabaseorDirectory" { "email" } + "SAPCloudIdentity" { "userName" } + "Generic" { "userPrincipalName" } + } + Write-ColoredVerbose "Column used for lookup in Entra ID : $sourceMatchPropertyName." -Color "Green" + + # Get or create the application and service principal once + Write-ColoredVerbose -Message "Checking if application exists for: $ApplicationName" -Color "Cyan" + $application = CreateApplicationIfNotExists -DisplayName $ApplicationName + if (-not $application) { + Write-Error "Failed to retrieve or create application: $ApplicationName" + return + } + Write-ColoredVerbose "Application $ApplicationName status: $($application.Status) | ApplicationId : $($application.ApplicationId) | AppId : $($application.AppId) | ServicePrincipalId : $($application.ServicePrincipalId)." -Color "Green" + + $uniqueRoles = @() + + # Extract unique roles + $users | ForEach-Object { + $role = SanitizeInputParameter -Value $_.Role + if ($role -and $role -notin $uniqueRoles) { + $uniqueRoles += $role + } + } + + Write-ColoredVerbose "Found $($uniqueRoles.Count) unique roles: $($uniqueRoles -join ', ')" -Color "Green" + # Create new roles if they do not exist + + if ($uniqueRoles.Count -gt 0) { + Write-ColoredVerbose "Creating required roles in application..." -Color "Cyan" + $createdRoles = NewAppRoleIfNotExists -UniqueRoles $uniqueRoles -ApplicationId $application.ApplicationId + if ($createdRoles) { + Write-ColoredVerbose "Successfully created $($createdRoles.Count) new roles" -Color "Green" + } + } + else { + Write-ColoredVerbose "No new roles needed to be created" -Color "Yellow" + } + # Process users in bulk + + Write-ColoredVerbose "Processing users details..." -Color "Cyan" + + $assignmentResults = [System.Collections.ArrayList]::new() + + foreach ($user in $users) { + + #$cleanUserPrincipalName = SanitizeInputParameter($user.$sourceMatchPropertyName) + $cleanUserPrincipalName = SanitizeInputParameter -Value $user.$sourceMatchPropertyName + Write-ColoredVerbose "UPN : $($cleanUserPrincipalName)" -Color "Green" + + if (-not $cleanUserPrincipalName) { + Write-Warning "Skipping user due to invalid userPrincipalName: $($user.$sourceMatchPropertyName)" + continue + } + + $cleanDisplayName = SanitizeInputParameter -Value $user.displayName + Write-ColoredVerbose "DisplayName : $($cleanDisplayName)" -Color "Green" + + if (-not $cleanDisplayName) { + Write-Warning "Skipping user due to invalid displayName: $($user.DisplayName)" + continue + } + $cleanMailNickname = SanitizeInputParameter -Value $user.mailNickname + Write-ColoredVerbose "Mail nickname : $($cleanMailNickname)" -Color "Green" + + if (-not $cleanMailNickname) { + Write-Warning "Skipping user due to invalid mailNickname: $($user.MailNickname)" + continue + } + + # Get the user's role + $userRole = SanitizeInputParameter -Value $user.Role + Write-ColoredVerbose "Role : $($userRole)" -Color "Green" + if (-not $userRole) { + Write-Warning "Skipping user due to invalid Role: $($user.Role)" + continue + } + + + # Create user if they do not exist + Write-ColoredVerbose "Assigning roles to user $($cleanUserPrincipalName) " + $userInfo = CreateUserIfNotExistsNew -UserPrincipalName $cleanUserPrincipalName -DisplayName $cleanDisplayName -MailNickname $cleanMailNickname + + if (-not $userInfo) { continue } + + # Assign roles to the user (Placeholder for role assignment logic) + Write-ColoredVerbose "Start app role assignment with params: ServicePrincipalId - $($application.ServicePrincipalId) | UserId - $($userInfo.Id) | AppName - $($ApplicationName) | Role - $($userRole) " -Color "Cyan" + $assignment = AssignAppServicePrincipalRoleAssignmentIfNotExists -ServicePrincipalId $application.ServicePrincipalId -UserId $userInfo.Id -ApplicationName $ApplicationName -RoleDisplayName $userRole + if (-not $assignment) { continue } + Write-ColoredVerbose "Assigning roles to user $($userInfo.UserPrincipalName) in application $ApplicationName" + + if ($assignment) { + [void]$assignmentResults.Add([PSCustomObject]@{ + UserPrincipalName = $cleanUserPrincipalName + DisplayName = $cleanDisplayName + UserId = $userInfo.Id + Role = $userRole + ApplicationName = $ApplicationName + ApplicationStatus = $application.Status + ServicePrincipalId = $application.ServicePrincipalId + UserCreationStatus = $userInfo.Status + RoleAssignmentStatus = $assignment.Status + AssignmentId = $assignment.AssignmentId + AppRoleId = $assignment.AppRoleId + PrincipalType = "User" # Based on the AllowedMemberTypes in role creation + RoleAssignmentCreatedDateTime = $assignment.CreatedDateTime + ResourceId = $application.ServicePrincipalId # Same as ServicePrincipalId in this context + ProcessedTimestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') + }) + } + } + + # Export results if using ExportResults parameter set + if ($PSCmdlet.ParameterSetName -eq 'ExportResults' -and $assignmentResults.Count -gt 0) { + try { + Write-ColoredVerbose "Exporting results to: $ExportFileName" -Color "Cyan" + $assignmentResults | Export-Csv -Path $ExportFileName -NoTypeInformation -Force + Write-ColoredVerbose "Successfully exported $($assignmentResults.Count) assignments" -Color "Green" + } + catch { + Write-Error "Failed to export results: $_" + } + } + + return $assignmentResults + } + catch { + Write-Error "Error in StartOrchestration: $_)" + } + } + + # Debugging output + Write-ColoredVerbose -Message "Starting orchestration with params: AppName - $ApplicationName | FileName - $FileName | DataSource - $DataSource" -Color "Magenta" + # Start orchestration + StartOrchestration + + } + +} + From 25c9daf0cace6be1a8593013e55a2c7ad870724e Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Fri, 28 Feb 2025 13:44:44 +0300 Subject: [PATCH 03/25] Add docs and bumping up Acrolinx score --- .../Set-EntraAppRoleToApplicationUser.ps1 | 96 ++------ .../Set-EntraAppRoleToApplicationUser.md | 223 ++++++++++++++++++ 2 files changed, 243 insertions(+), 76 deletions(-) create mode 100644 module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index aec7a8fbb..51449f0e8 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -1,8 +1,12 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All Rights Reserved. +# Licensed under the MIT License. See License in the project root for license information. +# ------------------------------------------------------------------------------ function Set-EntraAppRoleToApplicationUser { [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true, - HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic'", + HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic' which determines the column attribute mapping.", ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] @@ -14,7 +18,7 @@ function Set-EntraAppRoleToApplicationUser { [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] - [string]$FileName, + [System.IO.FileInfo]$FileName, [Parameter(Mandatory = $true, HelpMessage = "Name of the application (Service Principal) to assign roles for", @@ -27,11 +31,10 @@ function Set-EntraAppRoleToApplicationUser { ParameterSetName = 'ExportResults', HelpMessage = "Switch to enable export of results into a CSV file")] [switch]$Export, - - [Parameter(Mandatory = $false, - ParameterSetName = 'ExportResults', - HelpMessage = "Path for the export file e.g. EntraAppRoleAssignments_yyyyMMdd.csv . If not specified, uses current location")] - [string]$ExportFileName = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv") + + [Parameter(Mandatory = $false, ParameterSetName = 'ExportResults', + HelpMessage = "Path for the export file. Defaults to current directory.")] + [System.IO.FileInfo]$ExportFileName = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv") ) process { @@ -52,33 +55,13 @@ function Set-EntraAppRoleToApplicationUser { } } - function SanitizeInputParameter { + function SanitizeInput { param ([string]$Value) - - try { - if ([string]::IsNullOrWhiteSpace($Value)) { - Write-Warning "Input is empty or null." - return $null - } - - $cleanValue = $Value -replace "'", "''" - $cleanValue = $cleanValue -replace '\s+', ' ' # Replace multiple spaces with a single space - $cleanValue = $cleanValue.Trim().ToLower() # Trim and convert to lowercase - - if ([string]::IsNullOrWhiteSpace($cleanValue)) { - Write-Warning "Input became empty after sanitization." - return $null - } - - return $cleanValue - } - catch { - Write-Error "Error cleaning value: $_" - return $null - } + if ([string]::IsNullOrWhiteSpace($Value)) { return $null } + return $Value -replace "'", "''" -replace '\s+', ' ' -replace "`n|`r", "" | ForEach-Object { $_.Trim().ToLower() } } - function CreateUserIfNotExistsNew { + function CreateUserIfNotExists { param ( [string]$UserPrincipalName, [string]$DisplayName, @@ -128,44 +111,6 @@ function Set-EntraAppRoleToApplicationUser { } } - function CreateUserIfNotExists { - param ( - [string]$UserPrincipalName, - [string]$DisplayName, - [string]$MailNickname - ) - - Write-ColoredVerbose -Message "User details: DisplayName $DisplayName | UserPrincipalName: $UserPrincipalName | MailNickname: $MailNickname" -Color "Cyan" - - try { - $existingUser = Get-EntraUser -Filter "userPrincipalName eq '$($UserPrincipalName)'" -ErrorAction SilentlyContinue - if ($existingUser) { - Write-ColoredVerbose -Message "User $UserPrincipalName exists." -Color "Green" - return $existingUser - } - - $passwordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile - $passwordProfile.EnforceChangePasswordPolicy = $true - $passwordProfile.Password = -join (((48..90) + (96..122)) * 16 | Get-Random -Count 16 | % { [char]$_ }) - $userParams = @{ - DisplayName = $DisplayName - PasswordProfile = $passwordProfile - UserPrincipalName = $UserPrincipalName - AccountEnabled = $false - MailNickName = $MailNickname - } - - $newUser = New-EntraUser @userParams - - Write-ColoredVerbose -Message "Created new user: $UserPrincipalName" -Color "Green" - return $newUser - } - catch { - Write-Error "Failed to create or verify user $($UserPrincipalName) : $_" - return $null - } - } - function CreateApplicationIfNotExists { param ([string]$DisplayName) @@ -406,7 +351,7 @@ function Set-EntraAppRoleToApplicationUser { # Extract unique roles $users | ForEach-Object { - $role = SanitizeInputParameter -Value $_.Role + $role = SanitizeInput -Value $_.Role if ($role -and $role -notin $uniqueRoles) { $uniqueRoles += $role } @@ -433,8 +378,7 @@ function Set-EntraAppRoleToApplicationUser { foreach ($user in $users) { - #$cleanUserPrincipalName = SanitizeInputParameter($user.$sourceMatchPropertyName) - $cleanUserPrincipalName = SanitizeInputParameter -Value $user.$sourceMatchPropertyName + $cleanUserPrincipalName = SanitizeInput -Value $user.$sourceMatchPropertyName Write-ColoredVerbose "UPN : $($cleanUserPrincipalName)" -Color "Green" if (-not $cleanUserPrincipalName) { @@ -442,14 +386,14 @@ function Set-EntraAppRoleToApplicationUser { continue } - $cleanDisplayName = SanitizeInputParameter -Value $user.displayName + $cleanDisplayName = SanitizeInput -Value $user.displayName Write-ColoredVerbose "DisplayName : $($cleanDisplayName)" -Color "Green" if (-not $cleanDisplayName) { Write-Warning "Skipping user due to invalid displayName: $($user.DisplayName)" continue } - $cleanMailNickname = SanitizeInputParameter -Value $user.mailNickname + $cleanMailNickname = SanitizeInput -Value $user.mailNickname Write-ColoredVerbose "Mail nickname : $($cleanMailNickname)" -Color "Green" if (-not $cleanMailNickname) { @@ -458,7 +402,7 @@ function Set-EntraAppRoleToApplicationUser { } # Get the user's role - $userRole = SanitizeInputParameter -Value $user.Role + $userRole = SanitizeInput -Value $user.Role Write-ColoredVerbose "Role : $($userRole)" -Color "Green" if (-not $userRole) { Write-Warning "Skipping user due to invalid Role: $($user.Role)" @@ -468,7 +412,7 @@ function Set-EntraAppRoleToApplicationUser { # Create user if they do not exist Write-ColoredVerbose "Assigning roles to user $($cleanUserPrincipalName) " - $userInfo = CreateUserIfNotExistsNew -UserPrincipalName $cleanUserPrincipalName -DisplayName $cleanDisplayName -MailNickname $cleanMailNickname + $userInfo = CreateUserIfNotExists -UserPrincipalName $cleanUserPrincipalName -DisplayName $cleanDisplayName -MailNickname $cleanMailNickname if (-not $userInfo) { continue } diff --git a/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md b/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md new file mode 100644 index 000000000..022e8c860 --- /dev/null +++ b/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md @@ -0,0 +1,223 @@ +--- +title: Set-EntraAppRoleToApplicationUser +description: This article provides details on the Set-EntraAppRoleToApplicationUser command. + +ms.topic: reference +ms.date: 02/28/2025 +ms.author: eunicewaweru +ms.reviewer: stevemutungi +manager: CelesteDG +author: msewaweru + +external help file: Microsoft.Entra.Governance-Help.xml +Module Name: Microsoft.Entra +online version: https://learn.microsoft.com/powershell/module/Microsoft.Entra/Set-EntraAppRoleToApplicationUser + +schema: 2.0.0 +--- + +# Set-EntraAppRoleToApplicationUser + +## Synopsis + +Add existing application users to Microsoft Entra ID and assign them roles. + +## Syntax + +### Default + +```powershell +Set-EntraAppRoleToApplicationUser + -DataSource + -FileName + -ApplicationName + [] +``` + +### ExportResults + +```powershell +Set-EntraAppRoleToApplicationUser + -DataSource + -FileName + -ApplicationName + -Export + -ExportFileName + [] +``` + +## Description + +The `Set-EntraAppRoleToApplicationUser` command adds existing users (for example, from a Helpdesk or billing application) to Microsoft Entra ID and assigns them app roles like Admin, Audit, or Reports. This enables the application unlock Microsoft Entra ID Governance features like access reviews. + +This feature requires a Microsoft Entra ID Governance or Microsoft Entra Suite license, see [Microsoft Entra ID Governance licensing fundamentals](https://learn.microsoft.com/entra/id-governance/licensing-fundamentals). + +In delegated scenarios, the signed-in user must have either a supported Microsoft Entra role or a custom role with the necessary permissions. The minimum roles required for this operation are: + +- User Administrator (create users) +- Application Administrator +- Identity Governance Administrator (manage application role assignments) + +## Examples + +### Example 1: Assign application users to app role assignments + +```powershell +Connect-Entra -Scopes 'User.ReadWrite.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'EntitlementManagement.ReadWrite.All' +Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users.csv" -ApplicationName "TestApp" +``` + +This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. + +- `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. +- `-FileName` parameter specifies the path to the input file containing users, for example, C:\temp\users.csv. +- `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. + +### Example 2: Assign application users to app role assignments with verbose mode + +```powershell +Connect-Entra -Scopes 'User.ReadWrite.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'EntitlementManagement.ReadWrite.All' +Set-EntraAppRoleToApplicationUser -DataSource "SAPCloudIdentity" -FileName "C:\temp\users-exported-from-sap.csv" -ApplicationName "TestApp" -Verbose +``` + +This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. + +- `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. +- `-FileName` parameter specifies the path to the input file containing users, for example, C:\temp\users.csv. +- `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. +- `-Verbose` common parameter outputs the execution steps during processing. + +### Example 3: Assign application users to app roles and export to a default location + +```powershell +Connect-Entra -Scopes 'User.ReadWrite.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'EntitlementManagement.ReadWrite.All' +Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users.csv" -ApplicationName "TestApp" -Export -Verbose +``` + +This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. + +- `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. +- `-FileName` parameter specifies the path to the input file containing users, for example, C:\temp\users.csv. +- `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. +- `-Export` switch parameter enables export of results into a CSV file. If `ExportFileName` parameter isn't provided, results are exported in the current location. +- `-Verbose` common parameter outputs the execution steps during processing. + +### Example 4: Assign application users to app roles and export to a specified location + +```powershell +Connect-Entra -Scopes 'User.ReadWrite.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'EntitlementManagement.ReadWrite.All' +Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users.csv" -ApplicationName "TestApp" -Export -ExportFileName "C:\temp\EntraAppRoleAssignments_yyyyMMdd.csv" -Verbose +``` + +This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. + +- `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. +- `-FileName` parameter specifies the path to the input file containing users, for example, C:\temp\users.csv. +- `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. +- `-Export` switch parameter enables export of results into a CSV file. If `ExportFileName` parameter isn't provided, results are exported in the current location. +- `-ExportFileName` parameter specifies a specific filename and location to export results. +- `-Verbose` common parameter outputs the execution steps during processing. + +## Parameters + +### -DataSource + +Specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: +Accept wildcard characters: False +``` + +### -FileName + +Specifies the path to the input file containing users, for example, C:\temp\users.csv. + +```yaml +Type: System.IO.FileInfo +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ApplicationName + +Specifies the application name in Microsoft Entra ID. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Export + +Enables export of results into a CSV file. If `ExportFileName` parameter isn't provided, results are exported in the current location. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (ExportResults) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExportFileName + +Specifies a specific filename and location to export results. + +```yaml +Type: System.IO.FileInfo +Parameter Sets: (ExportResults) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters + +This cmdlet supports the common parameters: `-Debug`, `-ErrorAction`, `-ErrorVariable`, `-InformationAction`, `-InformationVariable`, `-OutVariable`, `-OutBuffer`, `-PipelineVariable`, `-Verbose`, `-WarningAction`, and `-WarningVariable`. For more information, see [about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). + +## Inputs + +### System.String + +## Outputs + +### System.Object + +## Notes + +[Govern an application's existing users](https://learn.microsoft.com/entra/id-governance/identity-governance-applications-existing-users) + +## Related Links + +[Get-EntraServicePrincipalAppRoleAssignedTo](Get-EntraServicePrincipalAppRoleAssignedTo.md) + +[New-EntraServicePrincipalAppRoleAssignment](New-EntraServicePrincipalAppRoleAssignment.md) From 8e63fd6f24e8e4bc1a5045ef7aa55760f2e20e81 Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Fri, 28 Feb 2025 13:50:05 +0300 Subject: [PATCH 04/25] File path fixes --- .../Governance/Set-EntraAppRoleToApplicationUser.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md b/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md index 022e8c860..d0da706f6 100644 --- a/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md +++ b/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md @@ -218,6 +218,6 @@ This cmdlet supports the common parameters: `-Debug`, `-ErrorAction`, `-ErrorVar ## Related Links -[Get-EntraServicePrincipalAppRoleAssignedTo](Get-EntraServicePrincipalAppRoleAssignedTo.md) +[Get-EntraServicePrincipalAppRoleAssignedTo](../Applications/Get-EntraServicePrincipalAppRoleAssignedTo.md) -[New-EntraServicePrincipalAppRoleAssignment](New-EntraServicePrincipalAppRoleAssignment.md) +[New-EntraServicePrincipalAppRoleAssignment](../Applications/New-EntraServicePrincipalAppRoleAssignment.md) From 032595ab1a1885e6f1800c191ad381af576ba1f4 Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Mon, 3 Mar 2025 14:25:27 +0300 Subject: [PATCH 05/25] Adding support for WhatIf --- .../Set-EntraAppRoleToApplicationUser.ps1 | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 51449f0e8..0f54625fc 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License in the project root for license information. # ------------------------------------------------------------------------------ function Set-EntraAppRoleToApplicationUser { - [CmdletBinding(DefaultParameterSetName = 'Default')] + [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true, HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic' which determines the column attribute mapping.", @@ -94,7 +94,9 @@ function Set-EntraAppRoleToApplicationUser { MailNickName = $MailNickname } - $newUser = New-EntraUser @userParams + if ($PSCmdlet.ShouldProcess("User '$UserPrincipalName'", "Create")) { + $newUser = New-EntraUser @userParams + } Write-ColoredVerbose -Message "Created new user: $UserPrincipalName" -Color "Green" return [PSCustomObject]@{ @@ -129,7 +131,10 @@ function Set-EntraAppRoleToApplicationUser { } } - $newApp = New-EntraApplication @appParams + if ($PSCmdlet.ShouldProcess("Application '$DisplayName'", "Create")) { + $newApp = New-EntraApplication @appParams + } + Write-ColoredVerbose "Created new application: $DisplayName" # Create service principal for the application @@ -138,7 +143,11 @@ function Set-EntraAppRoleToApplicationUser { DisplayName = $DisplayName } - $newSp = New-EntraServicePrincipal @spParams + + + if ($PSCmdlet.ShouldProcess("Service principal '$DisplayName'", "Create")) { + $newSp = New-EntraServicePrincipal @spParams + } Write-ColoredVerbose "Created new service principal for application: $DisplayName" [PSCustomObject]@{ @@ -161,7 +170,9 @@ function Set-EntraAppRoleToApplicationUser { DisplayName = $DisplayName } - $newSp = New-EntraServicePrincipal @spParams + if ($PSCmdlet.ShouldProcess("Service principal '$DisplayName'", "Create")) { + $newSp = New-EntraServicePrincipal @spParams + } Write-ColoredVerbose "Created new service principal for existing application: $DisplayName" } else { @@ -216,7 +227,10 @@ function Set-EntraAppRoleToApplicationUser { } # Create new assignment - $newAssignment = New-EntraServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipalObject.Id -ResourceId $servicePrincipalObject.Id -Id $appRoleId -PrincipalId $UserId + if ($PSCmdlet.ShouldProcess("Service Principal App Role assignment: AppRole - '$appRoleId' | UserId - '$UserId' | Service Principal - '$servicePrincipalObject.Id'", "Create")) { + $newAssignment = New-EntraServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipalObject.Id -ResourceId $servicePrincipalObject.Id -Id $appRoleId -PrincipalId $UserId + } + Write-ColoredVerbose "Created new role assignment for user '$UserId' - AppName: '$ApplicationName' with role '$RoleDisplayName'" -Color "Green" return [PSCustomObject]@{ @@ -282,6 +296,7 @@ function Set-EntraAppRoleToApplicationUser { # Add to the typed list $appRolesList.Add($appRole) [void]$createdRoles.Add($appRole) + Write-ColoredVerbose "Created new role definition for '$roleName'" -Color "Green" } @@ -293,7 +308,10 @@ function Set-EntraAppRoleToApplicationUser { Tags = @("WindowsAzureActiveDirectoryIntegratedApp") } - Update-MgApplication @params + + if ($PSCmdlet.ShouldProcess("Update application '$DisplayName' with AppRole list - '$appRolesList'", "Update")) { + Update-MgApplication @params + } Write-ColoredVerbose "Updated application with $($createdRoles.Count) new roles" -Color "Green" return $createdRoles | ForEach-Object { From d6fadd34753938124abb16199ab12de6807af694 Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Mon, 3 Mar 2025 16:16:27 +0300 Subject: [PATCH 06/25] Add WhatIf scenario when creating an app --- .../Set-EntraAppRoleToApplicationUser.ps1 | 84 +++++++++++-------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 0f54625fc..f22963852 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -115,42 +115,49 @@ function Set-EntraAppRoleToApplicationUser { function CreateApplicationIfNotExists { param ([string]$DisplayName) - + try { # Check if application exists - $existingApp = Get-EntraApplication -Filter "displayName eq '$DisplayName'" -ErrorAction SilentlyContinue - + if (-not $existingApp) { - # Create new application - $appParams = @{ - DisplayName = $DisplayName - SignInAudience = "AzureADMyOrg" - Web = @{ - RedirectUris = @("https://localhost") - } - } - if ($PSCmdlet.ShouldProcess("Application '$DisplayName'", "Create")) { + $appParams = @{ + DisplayName = $DisplayName + SignInAudience = "AzureADMyOrg" + Web = @{ RedirectUris = @("https://localhost") } + } $newApp = New-EntraApplication @appParams + Write-ColoredVerbose "Created new application: $DisplayName" } - - Write-ColoredVerbose "Created new application: $DisplayName" - - # Create service principal for the application - $spParams = @{ - AppId = $newApp.AppId - DisplayName = $DisplayName + else { + # Handle -WhatIf scenario by returning a mock object + $newApp = [PSCustomObject]@{ + Id = "WhatIf-AppId" + AppId = "WhatIf-AppId" + DisplayName = $DisplayName + } + Write-ColoredVerbose "WhatIf: Simulating creation of application: $DisplayName" } - - - + if ($PSCmdlet.ShouldProcess("Service principal '$DisplayName'", "Create")) { + $spParams = @{ + AppId = $newApp.AppId + DisplayName = $DisplayName + } $newSp = New-EntraServicePrincipal @spParams + Write-ColoredVerbose "Created new service principal for application: $DisplayName" } - Write-ColoredVerbose "Created new service principal for application: $DisplayName" - - [PSCustomObject]@{ + else { + # Handle -WhatIf scenario + $newSp = [PSCustomObject]@{ + Id = "WhatIf-ServicePrincipalId" + DisplayName = $DisplayName + } + Write-ColoredVerbose "WhatIf: Simulating creation of service principal for application: $DisplayName" + } + + return [PSCustomObject]@{ ApplicationId = $newApp.Id ApplicationDisplayName = $newApp.DisplayName ServicePrincipalId = $newSp.Id @@ -160,27 +167,31 @@ function Set-EntraAppRoleToApplicationUser { } } else { - # Get existing service principal $existingSp = Get-EntraServicePrincipal -Filter "appId eq '$($existingApp.AppId)'" -ErrorAction SilentlyContinue - + if (-not $existingSp) { - # Create service principal if it doesn't exist - $spParams = @{ - AppId = $existingApp.AppId - DisplayName = $DisplayName - } - if ($PSCmdlet.ShouldProcess("Service principal '$DisplayName'", "Create")) { + $spParams = @{ + AppId = $existingApp.AppId + DisplayName = $DisplayName + } $newSp = New-EntraServicePrincipal @spParams + Write-ColoredVerbose "Created new service principal for existing application: $DisplayName" + } + else { + $newSp = [PSCustomObject]@{ + Id = "WhatIf-ServicePrincipalId" + DisplayName = $DisplayName + } + Write-ColoredVerbose "WhatIf: Simulating creation of service principal for existing application: $DisplayName" } - Write-ColoredVerbose "Created new service principal for existing application: $DisplayName" } else { $newSp = $existingSp Write-ColoredVerbose "Service principal already exists for application: $DisplayName" } - - [PSCustomObject]@{ + + return [PSCustomObject]@{ ApplicationId = $existingApp.Id ApplicationDisplayName = $existingApp.DisplayName ServicePrincipalId = $newSp.Id @@ -195,6 +206,7 @@ function Set-EntraAppRoleToApplicationUser { return $null } } + function AssignAppServicePrincipalRoleAssignmentIfNotExists { From 0e3c7de5702931df730001d80f009de90273fc94 Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Mon, 3 Mar 2025 19:38:40 +0300 Subject: [PATCH 07/25] Reverting WhatIf checks --- .../Set-EntraAppRoleToApplicationUser.ps1 | 104 +++++++----------- 1 file changed, 37 insertions(+), 67 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index f22963852..51449f0e8 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License in the project root for license information. # ------------------------------------------------------------------------------ function Set-EntraAppRoleToApplicationUser { - [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Default')] + [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true, HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic' which determines the column attribute mapping.", @@ -94,9 +94,7 @@ function Set-EntraAppRoleToApplicationUser { MailNickName = $MailNickname } - if ($PSCmdlet.ShouldProcess("User '$UserPrincipalName'", "Create")) { - $newUser = New-EntraUser @userParams - } + $newUser = New-EntraUser @userParams Write-ColoredVerbose -Message "Created new user: $UserPrincipalName" -Color "Green" return [PSCustomObject]@{ @@ -115,49 +113,35 @@ function Set-EntraAppRoleToApplicationUser { function CreateApplicationIfNotExists { param ([string]$DisplayName) - + try { # Check if application exists + $existingApp = Get-EntraApplication -Filter "displayName eq '$DisplayName'" -ErrorAction SilentlyContinue - + if (-not $existingApp) { - if ($PSCmdlet.ShouldProcess("Application '$DisplayName'", "Create")) { - $appParams = @{ - DisplayName = $DisplayName - SignInAudience = "AzureADMyOrg" - Web = @{ RedirectUris = @("https://localhost") } - } - $newApp = New-EntraApplication @appParams - Write-ColoredVerbose "Created new application: $DisplayName" - } - else { - # Handle -WhatIf scenario by returning a mock object - $newApp = [PSCustomObject]@{ - Id = "WhatIf-AppId" - AppId = "WhatIf-AppId" - DisplayName = $DisplayName + # Create new application + $appParams = @{ + DisplayName = $DisplayName + SignInAudience = "AzureADMyOrg" + Web = @{ + RedirectUris = @("https://localhost") } - Write-ColoredVerbose "WhatIf: Simulating creation of application: $DisplayName" } - - if ($PSCmdlet.ShouldProcess("Service principal '$DisplayName'", "Create")) { - $spParams = @{ - AppId = $newApp.AppId - DisplayName = $DisplayName - } - $newSp = New-EntraServicePrincipal @spParams - Write-ColoredVerbose "Created new service principal for application: $DisplayName" - } - else { - # Handle -WhatIf scenario - $newSp = [PSCustomObject]@{ - Id = "WhatIf-ServicePrincipalId" - DisplayName = $DisplayName - } - Write-ColoredVerbose "WhatIf: Simulating creation of service principal for application: $DisplayName" + + $newApp = New-EntraApplication @appParams + Write-ColoredVerbose "Created new application: $DisplayName" + + # Create service principal for the application + $spParams = @{ + AppId = $newApp.AppId + DisplayName = $DisplayName } - - return [PSCustomObject]@{ + + $newSp = New-EntraServicePrincipal @spParams + Write-ColoredVerbose "Created new service principal for application: $DisplayName" + + [PSCustomObject]@{ ApplicationId = $newApp.Id ApplicationDisplayName = $newApp.DisplayName ServicePrincipalId = $newSp.Id @@ -167,31 +151,25 @@ function Set-EntraAppRoleToApplicationUser { } } else { + # Get existing service principal $existingSp = Get-EntraServicePrincipal -Filter "appId eq '$($existingApp.AppId)'" -ErrorAction SilentlyContinue - + if (-not $existingSp) { - if ($PSCmdlet.ShouldProcess("Service principal '$DisplayName'", "Create")) { - $spParams = @{ - AppId = $existingApp.AppId - DisplayName = $DisplayName - } - $newSp = New-EntraServicePrincipal @spParams - Write-ColoredVerbose "Created new service principal for existing application: $DisplayName" - } - else { - $newSp = [PSCustomObject]@{ - Id = "WhatIf-ServicePrincipalId" - DisplayName = $DisplayName - } - Write-ColoredVerbose "WhatIf: Simulating creation of service principal for existing application: $DisplayName" + # Create service principal if it doesn't exist + $spParams = @{ + AppId = $existingApp.AppId + DisplayName = $DisplayName } + + $newSp = New-EntraServicePrincipal @spParams + Write-ColoredVerbose "Created new service principal for existing application: $DisplayName" } else { $newSp = $existingSp Write-ColoredVerbose "Service principal already exists for application: $DisplayName" } - - return [PSCustomObject]@{ + + [PSCustomObject]@{ ApplicationId = $existingApp.Id ApplicationDisplayName = $existingApp.DisplayName ServicePrincipalId = $newSp.Id @@ -206,7 +184,6 @@ function Set-EntraAppRoleToApplicationUser { return $null } } - function AssignAppServicePrincipalRoleAssignmentIfNotExists { @@ -239,10 +216,7 @@ function Set-EntraAppRoleToApplicationUser { } # Create new assignment - if ($PSCmdlet.ShouldProcess("Service Principal App Role assignment: AppRole - '$appRoleId' | UserId - '$UserId' | Service Principal - '$servicePrincipalObject.Id'", "Create")) { - $newAssignment = New-EntraServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipalObject.Id -ResourceId $servicePrincipalObject.Id -Id $appRoleId -PrincipalId $UserId - } - + $newAssignment = New-EntraServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipalObject.Id -ResourceId $servicePrincipalObject.Id -Id $appRoleId -PrincipalId $UserId Write-ColoredVerbose "Created new role assignment for user '$UserId' - AppName: '$ApplicationName' with role '$RoleDisplayName'" -Color "Green" return [PSCustomObject]@{ @@ -308,7 +282,6 @@ function Set-EntraAppRoleToApplicationUser { # Add to the typed list $appRolesList.Add($appRole) [void]$createdRoles.Add($appRole) - Write-ColoredVerbose "Created new role definition for '$roleName'" -Color "Green" } @@ -320,10 +293,7 @@ function Set-EntraAppRoleToApplicationUser { Tags = @("WindowsAzureActiveDirectoryIntegratedApp") } - - if ($PSCmdlet.ShouldProcess("Update application '$DisplayName' with AppRole list - '$appRolesList'", "Update")) { - Update-MgApplication @params - } + Update-MgApplication @params Write-ColoredVerbose "Updated application with $($createdRoles.Count) new roles" -Color "Green" return $createdRoles | ForEach-Object { From 54b02e9ae548cd982fb05f729752c5b379e86773 Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Mon, 3 Mar 2025 20:32:12 +0300 Subject: [PATCH 08/25] End of milestone checkin --- .../Governance/Set-EntraAppRoleToApplicationUser.ps1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 51449f0e8..2ab676bcb 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -9,6 +9,7 @@ function Set-EntraAppRoleToApplicationUser { HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic' which determines the column attribute mapping.", ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + [Parameter(Mandatory = $true, ParameterSetName = 'ValidateAction')] [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] [string]$DataSource, @@ -16,6 +17,7 @@ function Set-EntraAppRoleToApplicationUser { HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv", ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + [Parameter(Mandatory = $true, ParameterSetName = 'ValidateAction')] [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] [System.IO.FileInfo]$FileName, @@ -24,6 +26,7 @@ function Set-EntraAppRoleToApplicationUser { HelpMessage = "Name of the application (Service Principal) to assign roles for", ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + [Parameter(Mandatory = $true, ParameterSetName = 'ValidateAction')] [ValidateNotNullOrEmpty()] [string]$ApplicationName, @@ -34,7 +37,10 @@ function Set-EntraAppRoleToApplicationUser { [Parameter(Mandatory = $false, ParameterSetName = 'ExportResults', HelpMessage = "Path for the export file. Defaults to current directory.")] - [System.IO.FileInfo]$ExportFileName = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv") + [System.IO.FileInfo]$ExportFileName = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"), + + [Parameter(Mandatory = $false, ParameterSetName = 'ValidateAction')] + [switch]$Validate ) process { @@ -130,6 +136,7 @@ function Set-EntraAppRoleToApplicationUser { } $newApp = New-EntraApplication @appParams + $validationStatus += "New application will be created with displayName - '$DisplayName'" Write-ColoredVerbose "Created new application: $DisplayName" # Create service principal for the application @@ -320,6 +327,7 @@ function Set-EntraAppRoleToApplicationUser { function StartOrchestration { try { + $validationStatus = @() # Import users from the CSV file Write-ColoredVerbose "Importing users from file: $FileName" -Color "Cyan" $users = Import-Csv -Path $FileName From e1cc171b9107c6c872d6b7a0904b4a9f685860be Mon Sep 17 00:00:00 2001 From: stevemutungi Date: Tue, 4 Mar 2025 15:02:26 +0300 Subject: [PATCH 09/25] Reverts --- .../Governance/Set-EntraAppRoleToApplicationUser.ps1 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 2ab676bcb..51449f0e8 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -9,7 +9,6 @@ function Set-EntraAppRoleToApplicationUser { HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic' which determines the column attribute mapping.", ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] - [Parameter(Mandatory = $true, ParameterSetName = 'ValidateAction')] [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] [string]$DataSource, @@ -17,7 +16,6 @@ function Set-EntraAppRoleToApplicationUser { HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv", ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] - [Parameter(Mandatory = $true, ParameterSetName = 'ValidateAction')] [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] [System.IO.FileInfo]$FileName, @@ -26,7 +24,6 @@ function Set-EntraAppRoleToApplicationUser { HelpMessage = "Name of the application (Service Principal) to assign roles for", ParameterSetName = 'Default')] [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] - [Parameter(Mandatory = $true, ParameterSetName = 'ValidateAction')] [ValidateNotNullOrEmpty()] [string]$ApplicationName, @@ -37,10 +34,7 @@ function Set-EntraAppRoleToApplicationUser { [Parameter(Mandatory = $false, ParameterSetName = 'ExportResults', HelpMessage = "Path for the export file. Defaults to current directory.")] - [System.IO.FileInfo]$ExportFileName = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"), - - [Parameter(Mandatory = $false, ParameterSetName = 'ValidateAction')] - [switch]$Validate + [System.IO.FileInfo]$ExportFileName = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv") ) process { @@ -136,7 +130,6 @@ function Set-EntraAppRoleToApplicationUser { } $newApp = New-EntraApplication @appParams - $validationStatus += "New application will be created with displayName - '$DisplayName'" Write-ColoredVerbose "Created new application: $DisplayName" # Create service principal for the application @@ -327,7 +320,6 @@ function Set-EntraAppRoleToApplicationUser { function StartOrchestration { try { - $validationStatus = @() # Import users from the CSV file Write-ColoredVerbose "Importing users from file: $FileName" -Color "Cyan" $users = Import-Csv -Path $FileName From 241e53d6c6e21112506d50deddac864a5a7f60c7 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Tue, 18 Mar 2025 11:49:23 +0300 Subject: [PATCH 10/25] Update docs to fix build error --- .../Set-EntraAppRoleToApplicationUser.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md b/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md index d0da706f6..2326b93ab 100644 --- a/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md +++ b/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md @@ -70,7 +70,7 @@ Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. - `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. -- `-FileName` parameter specifies the path to the input file containing users, for example, C:\temp\users.csv. +- `-FileName` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. - `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. ### Example 2: Assign application users to app role assignments with verbose mode @@ -83,7 +83,7 @@ Set-EntraAppRoleToApplicationUser -DataSource "SAPCloudIdentity" -FileName "C:\t This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. - `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. -- `-FileName` parameter specifies the path to the input file containing users, for example, C:\temp\users.csv. +- `-FileName` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. - `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. - `-Verbose` common parameter outputs the execution steps during processing. @@ -97,7 +97,7 @@ Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. - `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. -- `-FileName` parameter specifies the path to the input file containing users, for example, C:\temp\users.csv. +- `-FileName` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. - `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. - `-Export` switch parameter enables export of results into a CSV file. If `ExportFileName` parameter isn't provided, results are exported in the current location. - `-Verbose` common parameter outputs the execution steps during processing. @@ -112,7 +112,7 @@ Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. - `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. -- `-FileName` parameter specifies the path to the input file containing users, for example, C:\temp\users.csv. +- `-FileName` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. - `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. - `-Export` switch parameter enables export of results into a CSV file. If `ExportFileName` parameter isn't provided, results are exported in the current location. - `-ExportFileName` parameter specifies a specific filename and location to export results. @@ -132,13 +132,13 @@ Aliases: Required: True Position: Named Default value: None -Accept pipeline input: +Accept pipeline input: False Accept wildcard characters: False ``` ### -FileName -Specifies the path to the input file containing users, for example, C:\temp\users.csv. +Specifies the path to the input file containing users, for example, `C:\temp\users.csv`. ```yaml Type: System.IO.FileInfo @@ -174,7 +174,7 @@ Enables export of results into a CSV file. If `ExportFileName` parameter isn't p ```yaml Type: System.Management.Automation.SwitchParameter -Parameter Sets: (ExportResults) +Parameter Sets: ExportResults Aliases: Required: False @@ -190,7 +190,7 @@ Specifies a specific filename and location to export results. ```yaml Type: System.IO.FileInfo -Parameter Sets: (ExportResults) +Parameter Sets: ExportResults Aliases: Required: False @@ -214,7 +214,7 @@ This cmdlet supports the common parameters: `-Debug`, `-ErrorAction`, `-ErrorVar ## Notes -[Govern an application's existing users](https://learn.microsoft.com/entra/id-governance/identity-governance-applications-existing-users) +How to [Govern an application's existing users](https://learn.microsoft.com/entra/id-governance/identity-governance-applications-existing-users) ## Related Links From 43ed4b2eb77167d4e685140bf3f106436bebe5a5 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Fri, 21 Mar 2025 18:05:34 +0300 Subject: [PATCH 11/25] Add test file --- ...et-EntraAppRoleToApplicationUser.Tests.ps1 | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 test/Entra/Governance/Set-EntraAppRoleToApplicationUser.Tests.ps1 diff --git a/test/Entra/Governance/Set-EntraAppRoleToApplicationUser.Tests.ps1 b/test/Entra/Governance/Set-EntraAppRoleToApplicationUser.Tests.ps1 new file mode 100644 index 000000000..c69eb40df --- /dev/null +++ b/test/Entra/Governance/Set-EntraAppRoleToApplicationUser.Tests.ps1 @@ -0,0 +1,200 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +# ------------------------------------------------------------------------------ +BeforeAll { + if((Get-Module -Name Microsoft.Entra.Governance) -eq $null){ + Import-Module Microsoft.Entra.Governance + } + Import-Module (Join-Path $PSScriptRoot "..\..\Common-Functions.ps1") -Force + + $newUserScriptblock = { + + #Write-Host "Mocking New-EntraUser with parameters: $($args | ConvertTo-Json -Depth 3)" + return @( + [PSCustomObject]@{ + "DisplayName" = "Mock-User" + "AccountEnabled" = $true + "Mail" = "User@aaabbbcccc.OnMicrosoft.com" + "userPrincipalName" = "User@aaabbbcccc.OnMicrosoft.com" + "DeletedDateTime" = $null + "CreatedDateTime" = $null + "EmployeeId" = $null + "Id" = "bbbbbbbb-1111-2222-3333-cccccccccccc" + "Surname" = $null + "MailNickName" = "User" + "OnPremisesDistinguishedName" = $null + "OnPremisesSecurityIdentifier" = $null + "OnPremisesUserPrincipalName" = $null + "OnPremisesSyncEnabled" = $false + "onPremisesImmutableId" = $null + "OnPremisesLastSyncDateTime" = $null + "JobTitle" = $null + "CompanyName" = $null + "Department" = $null + "Country" = $null + "BusinessPhones" = @{} + "OnPremisesProvisioningErrors" = @{} + "ImAddresses" = @{} + "ExternalUserState" = $null + "ExternalUserStateChangeDateTime" = $null + "MobilePhone" = $null + } + ) + } + + $getApplicationScriptblock = { + return @( + [PSCustomObject]@{ + "AppId" = "aaaaaaaa-1111-2222-3333-cccccccccccc" + "DeletedDateTime" = $null + "Id" = "bbbbbbbb-1111-2222-3333-cccccccccccc" + "DisplayName" = "Mock-App" + "Info" = @{LogoUrl = ""; MarketingUrl = ""; PrivacyStatementUrl = ""; SupportUrl = ""; TermsOfServiceUrl = "" } + "IsDeviceOnlyAuthSupported" = $True + "IsFallbackPublicClient" = $true + "KeyCredentials" = @{CustomKeyIdentifier = @(211, 174, 247); DisplayName = ""; Key = ""; KeyId = "pppppppp-1111-2222-3333-cccccccccccc"; Type = "Symmetric"; Usage = "Sign" } + "OptionalClaims" = @{AccessToken = ""; IdToken = ""; Saml2Token = "" } + "ParentalControlSettings" = @{CountriesBlockedForMinors = $null; LegalAgeGroupRule = "Allow" } + "PasswordCredentials" = @{} + "PublicClient" = @{RedirectUris = $null } + "PublisherDomain" = "aaaabbbbbcccc.onmicrosoft.com" + "SignInAudience" = "AzureADandPersonalMicrosoftAccount" + "Web" = @{HomePageUrl = "https://localhost/demoapp"; ImplicitGrantSettings = ""; LogoutUrl = ""; } + "Parameters" = $args + } + ) + } + + $getServicePrincipalScriptblock = { + return @( + [PSCustomObject]@{ + "Id" = "00aa00aa-bb11-cc22-dd33-44ee44ee44ee" + "DisplayName" = "Windows Update for Business Deployment Service" + "AccountEnabled" = $true + "AddIns" = @{} + "AlternativeNames" = @{} + "AppDescription" = "" + "AppDisplayName" = "Windows Update for Business Deployment Service" + "AppId" = "00001111-aaaa-2222-bbbb-3333cccc4444" + "AppManagementPolicies" = "" + "AppOwnerOrganizationId" = "00aa00aa-bb11-cc22-dd33-44ee44ee44ee" + "AppRoleAssignedTo" = "" + "AppRoleAssignmentRequired" = $false + "AppRoleAssignments" = @() + "AppRoles" = @("22223333-cccc-4444-dddd-5555eeee6666", "33334444-dddd-5555-eeee-6666ffff7777", "44445555-eeee-6666-ffff-7777aaaa8888", "55556666-ffff-7777-aaaa-8888bbbb9999") + "ApplicationTemplateId" = "" + "ClaimsMappingPolicies" = "" + "CreatedObjects" = "" + "CustomSecurityAttributes" = "" + "DelegatedPermissionClassifications"= "" + "Description" = "" + "DisabledByMicrosoftStatus" = "" + "Endpoints" = "" + "FederatedIdentityCredentials" = "" + "HomeRealmDiscoveryPolicies" = "" + "Homepage" = "" + "Info" = "" + "KeyCredentials" = @{} + "LoginUrl" = "" + "LogoutUrl" = "https://deploymentscheduler.microsoft.com" + "MemberOf" = "" + "Notes" = "" + "NotificationEmailAddresses" = @{} + "Oauth2PermissionGrants" = "" + "Oauth2PermissionScopes" = @("22223333-cccc-4444-dddd-5555eeee6666", "33334444-dddd-5555-eeee-6666ffff7777", "44445555-eeee-6666-ffff-7777aaaa8888", "55556666-ffff-7777-aaaa-8888bbbb9999") + "OwnedObjects" = "" + "Owners" = "" + "PasswordCredentials" = @{} + "PreferredSingleSignOnMode" = "" + "PreferredTokenSigningKeyThumbprint"= "" + "RemoteDesktopSecurityConfiguration"= "" + "ReplyUrls" = @{} + "ResourceSpecificApplicationPermissions"= @{} + "SamlSingleSignOnSettings" = "" + "ServicePrincipalNames" = @("61ae9cd9-7bca-458c-affc-861e2f24ba3b") + "ServicePrincipalType" = "Application" + "SignInAudience" = "AzureADMultipleOrgs" + "Synchronization" = "" + "Tags" = @{} + "TokenEncryptionKeyId" = "" + "TokenIssuancePolicies" = "" + "TokenLifetimePolicies" = "" + "TransitiveMemberOf" = "" + "VerifiedPublisher" = "" + "AdditionalProperties" = @{ + "@odata.context" = "https://graph.microsoft.com/v1.0/`$metadata#servicePrincipals/`$entity" + "createdDateTime" = "2023-07-07T14:07:33Z" + } + "Parameters" = $args + } + ) + } + + $newServicePrincipalAppRoleAssignmentScriptblock = { + # Write-Host "Mocking New-MgServicePrincipalAppRoleAssignment with parameters: $($args | ConvertTo-Json -Depth 3)" + return @( + [PSCustomObject]@{ + "DeletedDateTime" = $null + "Id" = "00aa00aa-bb11-cc22-dd33-44ee44ee44ee" + "PrincipalDisplayName" = "Mock-App" + "AppRoleId" = "bbbb1b1b-cc2c-dd3d-ee4e-ffffff5f5f5f" + "CreatedDateTime" = "3/12/2024 11:05:29 AM" + "PrincipalId" = "aaaaaaaa-bbbb-cccc-1111-222222222222" + "Parameters" = $args + } + ) + } + + Mock -CommandName Get-EntraUser -MockWith {} -ModuleName Microsoft.Entra.Governance + Mock -CommandName New-EntraUser -MockWith { $newUserScriptblock } -ModuleName Microsoft.Entra.Governance + Mock -CommandName Get-EntraApplication -MockWith { $getApplicationScriptblock } -ModuleName Microsoft.Entra.Governance + Mock -CommandName New-EntraApplication -MockWith {} -ModuleName Microsoft.Entra.Governance + Mock -CommandName New-EntraServicePrincipal -MockWith {} -ModuleName Microsoft.Entra.Governance + Mock -CommandName Get-EntraServicePrincipal -MockWith { $getServicePrincipalScriptblock } -ModuleName Microsoft.Entra.Governance + Mock -CommandName Get-EntraServicePrincipalAppRoleAssignedTo -MockWith {} -ModuleName Microsoft.Entra.Governance + Mock -CommandName New-EntraServicePrincipalAppRoleAssignment -MockWith { $newServicePrincipalAppRoleAssignmentScriptblock } -ModuleName Microsoft.Entra.Governance +} + +Describe "Set-EntraDirectoryRoleDefinition" { + Context "Test for Set-EntraDirectoryRoleDefinition" { + It "Should return empty object" { + $result = Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\Path\To\users.csv" -ApplicationName "TestApp" + $result | Should -BeNullOrEmpty + + Should -Invoke -CommandName New-EntraUser -ModuleName Microsoft.Entra.Governance -Times 1 + Should -Invoke -CommandName Get-EntraApplication -ModuleName Microsoft.Entra.Governance -Times 1 + Should -Invoke -CommandName Get-EntraServicePrincipal -ModuleName Microsoft.Entra.Governance -Times 1 + Should -Invoke -CommandName New-EntraServicePrincipalAppRoleAssignment -ModuleName Microsoft.Entra.Governance -Times 1 + } + # It "Should contain 'User-Agent' header" { + # $userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion Set-EntraDirectoryRoleDefinition" + + # $RolePermissions = New-object Microsoft.Open.MSGraph.Model.RolePermission + # $RolePermissions.AllowedResourceActions = @("microsoft.directory/applications/basic/read") + # Set-EntraDirectoryRoleDefinition -UnifiedRoleDefinitionId "00aa00aa-bb11-cc22-dd33-44ee44ee44ee" -RolePermissions $RolePermissions -IsEnabled $false -DisplayName 'Mock-App' -ResourceScopes "/" -Description "Mock-App" -TemplateId "11bb11bb-cc22-dd33-ee44-55ff55ff55ff" -Version 2 + + # $userAgentHeaderValue = "PowerShell/$psVersion EntraPowershell/$entraVersion Set-EntraDirectoryRoleDefinition" + + # Should -Invoke -CommandName Update-MgRoleManagementDirectoryRoleDefinition -ModuleName Microsoft.Entra.Governance -Times 1 -ParameterFilter { + # $Headers.'User-Agent' | Should -Be $userAgentHeaderValue + # $true + # } + # } + # It "Should execute successfully without throwing an error" { + # # Disable confirmation prompts + # $originalDebugPreference = $DebugPreference + # $DebugPreference = 'Continue' + # $RolePermissions = New-object Microsoft.Open.MSGraph.Model.RolePermission + # $RolePermissions.AllowedResourceActions = @("microsoft.directory/applications/basic/read") + + # try { + # # Act & Assert: Ensure the function doesn't throw an exception + # { Set-EntraDirectoryRoleDefinition -UnifiedRoleDefinitionId "00aa00aa-bb11-cc22-dd33-44ee44ee44ee" -RolePermissions $RolePermissions -IsEnabled $false -DisplayName 'Mock-App' -ResourceScopes "/" -Description "Mock-App" -TemplateId "11bb11bb-cc22-dd33-ee44-55ff55ff55ff" -Version 2 -Debug } | Should -Not -Throw + # } finally { + # # Restore original confirmation preference + # $DebugPreference = $originalDebugPreference + # } + # } + + } +} From 2ffff35082c10ab5b6b689fb843c6ac9c3901b7e Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Tue, 1 Apr 2025 08:36:47 +0300 Subject: [PATCH 12/25] SignInAudience param --- EntraAppRoleAssignments_20250401_082500.csv | 15 +++++++++++ .../Set-EntraAppRoleToApplicationUser.ps1 | 25 +++++++++++-------- 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 EntraAppRoleAssignments_20250401_082500.csv diff --git a/EntraAppRoleAssignments_20250401_082500.csv b/EntraAppRoleAssignments_20250401_082500.csv new file mode 100644 index 000000000..1d819ee5b --- /dev/null +++ b/EntraAppRoleAssignments_20250401_082500.csv @@ -0,0 +1,15 @@ +"UserPrincipalName","DisplayName","UserId","Role","ApplicationName","ApplicationStatus","ServicePrincipalId","UserCreationStatus","RoleAssignmentStatus","AssignmentId","AppRoleId","PrincipalType","RoleAssignmentCreatedDateTime","ResourceId","ProcessedTimestamp" +"user1@3zmb3n.onmicrosoft.com","user one","0c1dd8d1-8c7c-4c57-9e2e-4771412fca99","admin","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","0dgdDHyMV0yeLkdxQS_KmR8WTpgZrvpCrziyklmrZjM","08363d34-3edb-450e-a909-e0f902c645c1","User","01/04/2025 05:25:04","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:04" +"user2@3zmb3n.onmicrosoft.com","user two","43bc4624-8b02-4a0c-b9a9-e5d44ed2a1b2","reader","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","JEa8QwKLDEq5qeXUTtKhsoCEs8q5UXxDkwU_MJ65FY8","861e30b6-0999-4ec7-a94a-ce389a4050a2","User","01/04/2025 05:25:05","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:06" +"user3@3zmb3n.onmicrosoft.com","user three","7f7ba43e-c862-4e5e-be1b-e56acd319833","admin","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Exists","0dgdDHyMV0yeLkdxQS_KmR8WTpgZrvpCrziyklmrZjM","08363d34-3edb-450e-a909-e0f902c645c1","User","01/04/2025 05:25:04","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:07" +"henriettam@3zmb3n.onmicrosoft.com","henrietta mueller","3edc80c8-fabb-40bc-bf91-615c5922d6d8","admin","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Exists","0dgdDHyMV0yeLkdxQS_KmR8WTpgZrvpCrziyklmrZjM","08363d34-3edb-450e-a909-e0f902c645c1","User","01/04/2025 05:25:04","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:08" +"user5@3zmb3n.onmicrosoft.com","user five","7e7cd61d-16fd-4a4a-8fa3-c78e44ab31fc","report","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","HdZ8fv0WSkqPo8eORKsx_J5Hl9k_Hc9NvjNKp4_QP4s","4e7f6d38-0b01-4163-945a-d197d8fb3353","User","01/04/2025 05:25:09","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:09" +"leeg@3zmb3n.onmicrosoft.com","lee gu","ce173514-79af-4cd4-af05-c12c713d9cee","survey","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","FDUXzq951EyvBcEscT2c7opvvx4PVPBJjlj5IVbWmR8","789b722a-1f19-46ef-9018-21ee306b2399","User","01/04/2025 05:25:10","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:10" +"jonis@3zmb3n.onmicrosoft.com","joni sherman","8a3adb6d-a652-43b0-94a3-4d1fe9f23fe7","audit","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","bds6ilKmsEOUo00f6fI_52zf4qLOexJAuMuSNlfxsfI","5ec11943-2bc4-4a0f-bb17-763dc6f9b41f","User","01/04/2025 05:25:11","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:12" +"user6@3zmb3n.onmicrosoft.com","user six","933ea694-b8cc-449f-87fe-d4a2fb9721cd","support","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","lKY-k8y4n0SH_tSi-5chzY8MVrrEJfRNpczgjT-zwtg","5503e7c6-24c4-4527-ae6e-fe748d3bb7dd","User","01/04/2025 05:25:13","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:13" +"pattif@3zmb3n.onmicrosoft.com","patti fuller","740d59d4-057f-4a6d-838b-a30bf3e31ec4","superadmin","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","1FkNdH8FbUqDi6ML8-MexMwSQacQ0jtNpe06Cch8bFk","c6da3282-4c3a-41a0-b165-e792e35a92b9","User","01/04/2025 05:25:14","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:15" +"user10@3zmb3n.onmicrosoft.com","user ten","bef70523-0bee-40be-9078-357025e47a86","helpdesk","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","IwX3vu4LvkCQeDVwJeR6hlZUJWNtsp1KskcY7yTMt0w","44b4c10b-e661-49cd-bf1d-dafe570f5c2c","User","01/04/2025 05:25:15","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:16" +"user11@3zmb3n.onmicrosoft.com","user eleven","debb8674-02bd-4d58-9516-8c8d0a2615b6","security","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","dIa73r0CWE2VFoyNCiYVtgoO-WqjopBOggUIj8A6Sp0","d30598d0-2586-485a-a366-add0e860c917","User","01/04/2025 05:25:17","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:17" +"miriamg@3zmb3n.onmicrosoft.com","miriam girling","56639f12-7bca-46d6-b4e6-075585c36d02","reader","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Exists","JEa8QwKLDEq5qeXUTtKhsoCEs8q5UXxDkwU_MJ65FY8","861e30b6-0999-4ec7-a94a-ce389a4050a2","User","01/04/2025 05:25:05","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:18" +"user20@3zmb3n.onmicrosoft.com","user twenty","bc191592-a347-4a4b-9fd6-c3c9a4067beb","helpdesk","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Exists","IwX3vu4LvkCQeDVwJeR6hlZUJWNtsp1KskcY7yTMt0w","44b4c10b-e661-49cd-bf1d-dafe570f5c2c","User","01/04/2025 05:25:15","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:19" +"derrick@3zmb3n.onmicrosoft.com","derrick mkenya","3b476862-d4c0-4691-a4cb-8ed0c2cfa999","developer","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","YmhHO8DUkUaky47Qws-pmdJnPWde4rVFm8Oqv_he7N8","2aa9c69f-fc4a-446e-b198-e16a652d8603","User","01/04/2025 05:25:20","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:20" diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 51449f0e8..ed0d41d2f 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -3,30 +3,32 @@ # Licensed under the MIT License. See License in the project root for license information. # ------------------------------------------------------------------------------ function Set-EntraAppRoleToApplicationUser { - [CmdletBinding(DefaultParameterSetName = 'Default')] + [CmdletBinding(DefaultParameterSetName = 'ExportResults')] param ( [Parameter(Mandatory = $true, HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic' which determines the column attribute mapping.", - ParameterSetName = 'Default')] - [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + ParameterSetName = 'ExportResults')] [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] [string]$DataSource, [Parameter(Mandatory = $true, HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv", - ParameterSetName = 'Default')] - [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + ParameterSetName = 'ExportResults')] [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] [System.IO.FileInfo]$FileName, [Parameter(Mandatory = $true, HelpMessage = "Name of the application (Service Principal) to assign roles for", - ParameterSetName = 'Default')] - [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults')] + ParameterSetName = 'ExportResults')] [ValidateNotNullOrEmpty()] [string]$ApplicationName, + [Parameter(Mandatory = $false, + HelpMessage = "Specifies what Microsoft accounts are supported for the application", + ParameterSetName = 'ExportResults')] + [string]$SignInAudience = "AzureADMyOrg", + [Parameter(Mandatory = $true, ParameterSetName = 'ExportResults', HelpMessage = "Switch to enable export of results into a CSV file")] @@ -112,7 +114,10 @@ function Set-EntraAppRoleToApplicationUser { } function CreateApplicationIfNotExists { - param ([string]$DisplayName) + param ( + [string]$DisplayName, + [string]$SignInAudience + ) try { # Check if application exists @@ -123,7 +128,7 @@ function Set-EntraAppRoleToApplicationUser { # Create new application $appParams = @{ DisplayName = $DisplayName - SignInAudience = "AzureADMyOrg" + SignInAudience = $SignInAudience Web = @{ RedirectUris = @("https://localhost") } @@ -340,7 +345,7 @@ function Set-EntraAppRoleToApplicationUser { # Get or create the application and service principal once Write-ColoredVerbose -Message "Checking if application exists for: $ApplicationName" -Color "Cyan" - $application = CreateApplicationIfNotExists -DisplayName $ApplicationName + $application = CreateApplicationIfNotExists -DisplayName $ApplicationName -SignInAudience $SignInAudience if (-not $application) { Write-Error "Failed to retrieve or create application: $ApplicationName" return From 346b7ad205344a3a6d6283f8fb346b309b7624a3 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Tue, 1 Apr 2025 13:37:04 +0300 Subject: [PATCH 13/25] Fix PowerShell 5.x issue --- .../Governance/Set-EntraAppRoleToApplicationUser.ps1 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index ed0d41d2f..0af9a4d40 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -11,10 +11,10 @@ function Set-EntraAppRoleToApplicationUser { [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] [string]$DataSource, - [Parameter(Mandatory = $true, + [Parameter(Mandatory = $true, HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv", ParameterSetName = 'ExportResults')] - [ValidateNotNullOrEmpty()] + #[ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] [System.IO.FileInfo]$FileName, @@ -29,7 +29,7 @@ function Set-EntraAppRoleToApplicationUser { ParameterSetName = 'ExportResults')] [string]$SignInAudience = "AzureADMyOrg", - [Parameter(Mandatory = $true, + [Parameter(Mandatory = $false, ParameterSetName = 'ExportResults', HelpMessage = "Switch to enable export of results into a CSV file")] [switch]$Export, @@ -257,7 +257,10 @@ function Set-EntraAppRoleToApplicationUser { } # Ensure the existing AppRoles are properly formatted - $existingRoles = $application.AppRoles ?? @() + $existingRoles = @() + if($null -ne $application.AppRoles){ + $existingRoles = $application.AppRoles + } $appRolesList = New-Object 'System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphAppRole]' foreach ($role in $existingRoles) { From 379365afdc3126d50e746f230bdb8807c9724d8d Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Thu, 3 Apr 2025 11:03:49 +0300 Subject: [PATCH 14/25] Commit changes --- .../Set-EntraAppRoleToApplicationUser.ps1 | 2 +- ...et-EntraAppRoleToApplicationUser.Tests.ps1 | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 0af9a4d40..cd2b7305c 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -14,7 +14,7 @@ function Set-EntraAppRoleToApplicationUser { [Parameter(Mandatory = $true, HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv", ParameterSetName = 'ExportResults')] - #[ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] [System.IO.FileInfo]$FileName, diff --git a/test/Entra/Governance/Set-EntraAppRoleToApplicationUser.Tests.ps1 b/test/Entra/Governance/Set-EntraAppRoleToApplicationUser.Tests.ps1 index c69eb40df..8913bcc24 100644 --- a/test/Entra/Governance/Set-EntraAppRoleToApplicationUser.Tests.ps1 +++ b/test/Entra/Governance/Set-EntraAppRoleToApplicationUser.Tests.ps1 @@ -48,7 +48,7 @@ BeforeAll { "AppId" = "aaaaaaaa-1111-2222-3333-cccccccccccc" "DeletedDateTime" = $null "Id" = "bbbbbbbb-1111-2222-3333-cccccccccccc" - "DisplayName" = "Mock-App" + " DisplayName" = "Mock-App" "Info" = @{LogoUrl = ""; MarketingUrl = ""; PrivacyStatementUrl = ""; SupportUrl = ""; TermsOfServiceUrl = "" } "IsDeviceOnlyAuthSupported" = $True "IsFallbackPublicClient" = $true @@ -144,25 +144,41 @@ BeforeAll { } ) } + $csv = { + $object1 = [PSCustomObject]@{ + userPrincipalName = 'user1@contoso.com' + displayName = 'User 1' + mailNickname = 'user1' + Role = 'Admin' + memberType = 'users+groups' + } + + $arr += $object1 + return $arr + } Mock -CommandName Get-EntraUser -MockWith {} -ModuleName Microsoft.Entra.Governance Mock -CommandName New-EntraUser -MockWith { $newUserScriptblock } -ModuleName Microsoft.Entra.Governance Mock -CommandName Get-EntraApplication -MockWith { $getApplicationScriptblock } -ModuleName Microsoft.Entra.Governance + Mock -CommandName Get-MgApplication -MockWith { $getApplicationScriptblock } -ModuleName Microsoft.Entra.Governance Mock -CommandName New-EntraApplication -MockWith {} -ModuleName Microsoft.Entra.Governance Mock -CommandName New-EntraServicePrincipal -MockWith {} -ModuleName Microsoft.Entra.Governance Mock -CommandName Get-EntraServicePrincipal -MockWith { $getServicePrincipalScriptblock } -ModuleName Microsoft.Entra.Governance Mock -CommandName Get-EntraServicePrincipalAppRoleAssignedTo -MockWith {} -ModuleName Microsoft.Entra.Governance Mock -CommandName New-EntraServicePrincipalAppRoleAssignment -MockWith { $newServicePrincipalAppRoleAssignmentScriptblock } -ModuleName Microsoft.Entra.Governance + Mock Test-Path { return $true } -ModuleName Microsoft.Entra.Governance + Mock Import-Csv { $csv } -ModuleName Microsoft.Entra.Governance } -Describe "Set-EntraDirectoryRoleDefinition" { - Context "Test for Set-EntraDirectoryRoleDefinition" { +Describe "Set-EntraAppRoleToApplicationUser" { + Context "Test for Set-EntraAppRoleToApplicationUser" { It "Should return empty object" { - $result = Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\Path\To\users.csv" -ApplicationName "TestApp" + $path = "C:\Path\To\users.csv" + $result = Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName $path -ApplicationName "Mock-App" $result | Should -BeNullOrEmpty - Should -Invoke -CommandName New-EntraUser -ModuleName Microsoft.Entra.Governance -Times 1 Should -Invoke -CommandName Get-EntraApplication -ModuleName Microsoft.Entra.Governance -Times 1 + Should -Invoke -CommandName New-EntraUser -ModuleName Microsoft.Entra.Governance -Times 1 Should -Invoke -CommandName Get-EntraServicePrincipal -ModuleName Microsoft.Entra.Governance -Times 1 Should -Invoke -CommandName New-EntraServicePrincipalAppRoleAssignment -ModuleName Microsoft.Entra.Governance -Times 1 } From f1315d5363ea2baea8fb8c1ac9faea56c3449461 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Thu, 3 Apr 2025 17:38:38 +0300 Subject: [PATCH 15/25] Get and Create Users via API --- .../Set-EntraAppRoleToApplicationUser.ps1 | 75 ++++++++++++------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index cd2b7305c..8381d52b4 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -73,39 +73,58 @@ function Set-EntraAppRoleToApplicationUser { Write-ColoredVerbose -Message "User details: DisplayName $DisplayName | UserPrincipalName: $UserPrincipalName | MailNickname: $MailNickname" -Color "Cyan" try { - $existingUser = Get-EntraUser -Filter "userPrincipalName eq '$UserPrincipalName'" -ErrorAction SilentlyContinue - if ($existingUser) { + $params = @{} + $baseUri = "/v1.0/users" + $query = "?`$filter=userPrincipalName eq '$UserPrincipalName'&`$select=Id,CreationType,DisplayName,GivenName,Mail,MailNickName,MobilePhone,OtherMails, UserPrincipalName,EmployeeId,JobTitle" + $params["Method"] = "GET" + $params["Uri"] = $baseUri+$query + $getUserResponse = Invoke-GraphRequest @params + + # User doesn't exist + if($null -eq $getUserResponse.value -or $getUserResponse.value.Count -eq 0){ + $passwordProfile = @{ + ForceChangePasswordNextSignIn = $true + Password = -join (((48..90) + (96..122)) * 16 | Get-Random -Count 16 | % { [char]$_ }) + + } + + $userParams = @{ + displayName = $DisplayName + passwordProfile = $passwordProfile + userPrincipalName = $UserPrincipalName + accountEnabled = $false + mailNickName = $MailNickname + mail = $UserPrincipalName + } + + $userParams = $userParams | ConvertTo-Json + + $newUserResponse = Invoke-GraphRequest -Uri 'https://graph.microsoft.com/v1.0/users?$select=*' -Method POST -Body $userParams + $newUserResponse = $newUserResponse | ConvertTo-Json | ConvertFrom-Json + + Write-ColoredVerbose -Message "Created new user: $UserPrincipalName" -Color "Green" + + return [PSCustomObject]@{ + Id = $newUserResponse.Id + UserPrincipalName = $newUserResponse.UserPrincipalName + DisplayName = $newUserResponse.DisplayName + MailNickname = $newUserResponse.MailNickname + Mail = $newUserResponse.Mail + Status = 'Created' + } + } + else{ + # User exists Write-ColoredVerbose -Message "User $UserPrincipalName exists." -Color "Green" return [PSCustomObject]@{ - Id = $existingUser.Id - UserPrincipalName = $existingUser.UserPrincipalName - DisplayName = $existingUser.DisplayName - MailNickname = $existingUser.MailNickname + Id = $getUserResponse.value.id + UserPrincipalName = $getUserResponse.value.userPrincipalName + DisplayName = $getUserResponse.value.displayName + MailNickname = $getUserResponse.value.mailNickname + Mail = $getUserResponse.value.mail Status = 'Exists' } } - - $passwordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile - $passwordProfile.EnforceChangePasswordPolicy = $true - $passwordProfile.Password = -join (((48..90) + (96..122)) * 16 | Get-Random -Count 16 | % { [char]$_ }) - $userParams = @{ - DisplayName = $DisplayName - PasswordProfile = $passwordProfile - UserPrincipalName = $UserPrincipalName - AccountEnabled = $false - MailNickName = $MailNickname - } - - $newUser = New-EntraUser @userParams - Write-ColoredVerbose -Message "Created new user: $UserPrincipalName" -Color "Green" - - return [PSCustomObject]@{ - Id = $newUser.Id - UserPrincipalName = $newUser.UserPrincipalName - DisplayName = $newUser.DisplayName - MailNickname = $newUser.MailNickname - Status = 'Created' - } } catch { Write-Error "Failed to create or verify user $($UserPrincipalName) $_" From 0ebf4d5653e459bb3896ebb68a73eea9377ea485 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Fri, 4 Apr 2025 09:17:19 +0300 Subject: [PATCH 16/25] Move ServicePrincipalRoleAssignment to API calls --- .../Set-EntraAppRoleToApplicationUser.ps1 | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 8381d52b4..909786180 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -99,7 +99,7 @@ function Set-EntraAppRoleToApplicationUser { $userParams = $userParams | ConvertTo-Json - $newUserResponse = Invoke-GraphRequest -Uri 'https://graph.microsoft.com/v1.0/users?$select=*' -Method POST -Body $userParams + $newUserResponse = Invoke-GraphRequest -Uri '/v1.0/users?$select=*' -Method POST -Body $userParams $newUserResponse = $newUserResponse | ConvertTo-Json | ConvertFrom-Json Write-ColoredVerbose -Message "Created new user: $UserPrincipalName" -Color "Green" @@ -220,13 +220,19 @@ function Set-EntraAppRoleToApplicationUser { try { # Check if assignment exists - - $servicePrincipalObject = Get-EntraServicePrincipal -ServicePrincipalId $ServicePrincipalId + + $params = @{} + $params["Uri"] = "/v1.0/servicePrincipals/$ServicePrincipalId" + $params["Method"] = "GET" + $servicePrincipalObject = Invoke-GraphRequest @params $appRoleId = ($servicePrincipalObject.AppRoles | Where-Object { $_.displayName -eq $RoleDisplayName }).Id - - $existingAssignment = Get-EntraServicePrincipalAppRoleAssignedTo -ServicePrincipalId $servicePrincipalObject.Id | Where-Object { $_.AppRoleId -eq $appRoleId } -ErrorAction SilentlyContinue - - if ($existingAssignment) { + + $params = @{} + $params["Uri"] = "/v1.0/servicePrincipals/$ServicePrincipalId/appRoleAssignedTo" + $params["Method"] = "GET" + $existingAssignment = (Invoke-GraphRequest @params).value | Where-Object { $_.AppRoleId -eq $appRoleId } -ErrorAction SilentlyContinue + + if($existingAssignment){ Write-ColoredVerbose "Role assignment already exists for user '$ApplicationName' with role '$RoleDisplayName'" -Color "Yellow" return [PSCustomObject]@{ @@ -240,16 +246,27 @@ function Set-EntraAppRoleToApplicationUser { } # Create new assignment - $newAssignment = New-EntraServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipalObject.Id -ResourceId $servicePrincipalObject.Id -Id $appRoleId -PrincipalId $UserId + + $assignmentParams = @{ + appRoleId = $appRoleId + principalId = $UserId + resourceId = $ServicePrincipalId + } + + $assignmentParams = $assignmentParams | ConvertTo-Json + + $assignmentResponse = Invoke-GraphRequest -Uri "/v1.0/servicePrincipals/$ServicePrincipalId/appRoleAssignments" -Method POST -Body $assignmentParams + $assignmentResponse = $assignmentResponse | ConvertTo-Json | ConvertFrom-Json + Write-ColoredVerbose "Created new role assignment for user '$UserId' - AppName: '$ApplicationName' with role '$RoleDisplayName'" -Color "Green" return [PSCustomObject]@{ ServicePrincipalId = $ServicePrincipalId PrincipalId = $UserId AppRoleId = $appRoleId - AssignmentId = $newAssignment.Id + AssignmentId = $assignmentResponse.value.Id Status = 'Created' - CreatedDateTime = $newAssignment.CreatedDateTime #(Get-Date).ToUniversalTime().ToString("o") # ISO 8601 format + CreatedDateTime = $assignmentResponse.value.CreatedDateTime #(Get-Date).ToUniversalTime().ToString("o") # ISO 8601 format } } catch { From bdc3d722d29a6db2371986b721a4859a14f9818b Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Fri, 4 Apr 2025 10:14:58 +0300 Subject: [PATCH 17/25] Move Applications and SP to API calls --- .../Set-EntraAppRoleToApplicationUser.ps1 | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 909786180..9737b7f66 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -77,7 +77,7 @@ function Set-EntraAppRoleToApplicationUser { $baseUri = "/v1.0/users" $query = "?`$filter=userPrincipalName eq '$UserPrincipalName'&`$select=Id,CreationType,DisplayName,GivenName,Mail,MailNickName,MobilePhone,OtherMails, UserPrincipalName,EmployeeId,JobTitle" $params["Method"] = "GET" - $params["Uri"] = $baseUri+$query + $params["Uri"] = "$baseUri{0}" -f $query $getUserResponse = Invoke-GraphRequest @params # User doesn't exist @@ -140,65 +140,102 @@ function Set-EntraAppRoleToApplicationUser { try { # Check if application exists + + $params = @{} + $baseUri = "/v1.0/applications" + $query = "?`$filter=displayName eq '$DisplayName'" + $params["Method"] = "GET" + $params["Uri"] = "$baseUri{0}" -f $query + $getAppResponse = Invoke-GraphRequest @params - $existingApp = Get-EntraApplication -Filter "displayName eq '$DisplayName'" -ErrorAction SilentlyContinue + #$existingApp = Get-EntraApplication -Filter "displayName eq '$DisplayName'" -ErrorAction SilentlyContinue - if (-not $existingApp) { + if ($null -eq $getAppResponse.value -or $getAppResponse.value.Count -eq 0) { # Create new application $appParams = @{ - DisplayName = $DisplayName - SignInAudience = $SignInAudience - Web = @{ + displayName = $DisplayName + signInAudience = $SignInAudience + web = @{ RedirectUris = @("https://localhost") } } - $newApp = New-EntraApplication @appParams + #$newApp = New-EntraApplication @appParams -debug + + $appParams = $appParams | ConvertTo-Json + + $newAppResponse = Invoke-GraphRequest -Uri '/v1.0/applications' -Method POST -Body $appParams + $newAppResponse = $newAppResponse | ConvertTo-Json | ConvertFrom-Json Write-ColoredVerbose "Created new application: $DisplayName" # Create service principal for the application $spParams = @{ - AppId = $newApp.AppId - DisplayName = $DisplayName + appId = $newAppResponse.AppId + displayName = $DisplayName } + + $spParams = $spParams | ConvertTo-Json + + $newSPResponse = Invoke-GraphRequest -Uri "/v1.0/servicePrincipals" -Method POST -Body $spParams + $newSPResponse = $newSPResponse | ConvertTo-Json | ConvertFrom-Json + - $newSp = New-EntraServicePrincipal @spParams + #$newSp = New-EntraServicePrincipal @spParams Write-ColoredVerbose "Created new service principal for application: $DisplayName" [PSCustomObject]@{ - ApplicationId = $newApp.Id - ApplicationDisplayName = $newApp.DisplayName - ServicePrincipalId = $newSp.Id - ServicePrincipalDisplayName = $newSp.DisplayName - AppId = $newApp.AppId + ApplicationId = $newAppResponse.Id + ApplicationDisplayName = $newAppResponse.DisplayName + ServicePrincipalId = $newSPResponse.Id + ServicePrincipalDisplayName = $newSPResponse.DisplayName + AppId = $newAppResponse.AppId Status = 'Created' } } else { # Get existing service principal - $existingSp = Get-EntraServicePrincipal -Filter "appId eq '$($existingApp.AppId)'" -ErrorAction SilentlyContinue + # $existingSp = Get-EntraServicePrincipal -Filter "appId eq '$($existingApp.AppId)'" -ErrorAction SilentlyContinue + + $params = @{} + $baseUri = "/v1.0/servicePrincipals" + $query = "?`$filter=appId eq '$($getAppResponse.value.AppId)'" + $params["Method"] = "GET" + $params["Uri"] = "$baseUri{0}" -f $query + $getSPResponse = Invoke-GraphRequest @params + $sp = $null - if (-not $existingSp) { + if ($null -eq $getSPResponse.value -or $getSPResponse.value.Count -eq 0) { # Create service principal if it doesn't exist + # $spParams = @{ + # AppId = $existingApp.AppId + # DisplayName = $DisplayName + # } + $spParams = @{ - AppId = $existingApp.AppId - DisplayName = $DisplayName + appId = $getAppResponse.value.AppId + displayName = $DisplayName } + + $spParams = $spParams | ConvertTo-Json + + $newSPResponse = Invoke-GraphRequest -Uri "/v1.0/servicePrincipals" -Method POST -Body $spParams + $newSPResponse = $newSPResponse | ConvertTo-Json | ConvertFrom-Json + $sp = $newSPResponse - $newSp = New-EntraServicePrincipal @spParams + #$newSp = New-EntraServicePrincipal @spParams Write-ColoredVerbose "Created new service principal for existing application: $DisplayName" } else { - $newSp = $existingSp + $sp = $getSPResponse.value Write-ColoredVerbose "Service principal already exists for application: $DisplayName" } [PSCustomObject]@{ - ApplicationId = $existingApp.Id - ApplicationDisplayName = $existingApp.DisplayName - ServicePrincipalId = $newSp.Id - ServicePrincipalDisplayName = $newSp.DisplayName - AppId = $existingApp.AppId + ApplicationId = $getAppResponse.value.Id + ApplicationDisplayName = $getAppResponse.value.DisplayName + ServicePrincipalId = $sp.Id + ServicePrincipalDisplayName = $sp.DisplayName + AppId = $getAppResponse.value.AppId Status = 'Exists' } } @@ -264,9 +301,9 @@ function Set-EntraAppRoleToApplicationUser { ServicePrincipalId = $ServicePrincipalId PrincipalId = $UserId AppRoleId = $appRoleId - AssignmentId = $assignmentResponse.value.Id + AssignmentId = $assignmentResponse.Id Status = 'Created' - CreatedDateTime = $assignmentResponse.value.CreatedDateTime #(Get-Date).ToUniversalTime().ToString("o") # ISO 8601 format + CreatedDateTime = $assignmentResponse.CreatedDateTime #(Get-Date).ToUniversalTime().ToString("o") # ISO 8601 format } } catch { From 4da23964ac4f02606840c35c59ca649f786e07da Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Fri, 4 Apr 2025 10:26:12 +0300 Subject: [PATCH 18/25] Remove unused comments --- .../Set-EntraAppRoleToApplicationUser.ps1 | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 9737b7f66..dcf7738eb 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -147,8 +147,6 @@ function Set-EntraAppRoleToApplicationUser { $params["Method"] = "GET" $params["Uri"] = "$baseUri{0}" -f $query $getAppResponse = Invoke-GraphRequest @params - - #$existingApp = Get-EntraApplication -Filter "displayName eq '$DisplayName'" -ErrorAction SilentlyContinue if ($null -eq $getAppResponse.value -or $getAppResponse.value.Count -eq 0) { # Create new application @@ -159,8 +157,6 @@ function Set-EntraAppRoleToApplicationUser { RedirectUris = @("https://localhost") } } - - #$newApp = New-EntraApplication @appParams -debug $appParams = $appParams | ConvertTo-Json @@ -179,8 +175,6 @@ function Set-EntraAppRoleToApplicationUser { $newSPResponse = Invoke-GraphRequest -Uri "/v1.0/servicePrincipals" -Method POST -Body $spParams $newSPResponse = $newSPResponse | ConvertTo-Json | ConvertFrom-Json - - #$newSp = New-EntraServicePrincipal @spParams Write-ColoredVerbose "Created new service principal for application: $DisplayName" [PSCustomObject]@{ @@ -194,7 +188,6 @@ function Set-EntraAppRoleToApplicationUser { } else { # Get existing service principal - # $existingSp = Get-EntraServicePrincipal -Filter "appId eq '$($existingApp.AppId)'" -ErrorAction SilentlyContinue $params = @{} $baseUri = "/v1.0/servicePrincipals" @@ -206,10 +199,6 @@ function Set-EntraAppRoleToApplicationUser { if ($null -eq $getSPResponse.value -or $getSPResponse.value.Count -eq 0) { # Create service principal if it doesn't exist - # $spParams = @{ - # AppId = $existingApp.AppId - # DisplayName = $DisplayName - # } $spParams = @{ appId = $getAppResponse.value.AppId @@ -221,8 +210,6 @@ function Set-EntraAppRoleToApplicationUser { $newSPResponse = Invoke-GraphRequest -Uri "/v1.0/servicePrincipals" -Method POST -Body $spParams $newSPResponse = $newSPResponse | ConvertTo-Json | ConvertFrom-Json $sp = $newSPResponse - - #$newSp = New-EntraServicePrincipal @spParams Write-ColoredVerbose "Created new service principal for existing application: $DisplayName" } else { From fa8c14a738cf1cf8d5b5b5edc572088c7e11f80f Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Fri, 4 Apr 2025 11:46:14 +0300 Subject: [PATCH 19/25] Move AppRole to API Call --- .../Set-EntraAppRoleToApplicationUser.ps1 | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index dcf7738eb..d5abb2969 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -310,7 +310,12 @@ function Set-EntraAppRoleToApplicationUser { try { # Get existing application - $application = Get-MgApplication -ApplicationId $ApplicationId -ErrorAction Stop + + $params = @{} + $params["Uri"] = "/v1.0/applications/$ApplicationId" + $params["Method"] = "GET" + $application = Invoke-GraphRequest @params + if (-not $application) { Write-Error "Application not found with ID: $ApplicationId" return $null @@ -321,13 +326,13 @@ function Set-EntraAppRoleToApplicationUser { if($null -ne $application.AppRoles){ $existingRoles = $application.AppRoles } - $appRolesList = New-Object 'System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphAppRole]' + $appRolesList = New-Object System.Collections.ArrayList foreach ($role in $existingRoles) { $appRolesList.Add($role) } - $allowedMemberTypes = @("User") # Define allowed member types + $allowedMemberTypes = @("User","Application") # Define allowed member types $createdRoles = [System.Collections.ArrayList]::new() foreach ($roleName in $UniqueRoles) { @@ -338,13 +343,13 @@ function Set-EntraAppRoleToApplicationUser { } # Create new AppRole object - $appRole = [Microsoft.Graph.PowerShell.Models.MicrosoftGraphAppRole]@{ - AllowedMemberTypes = $allowedMemberTypes - Description = $roleName - DisplayName = $roleName - Id = [Guid]::NewGuid() - IsEnabled = $true - Value = $roleName + $appRole = @{ + allowedMemberTypes = $allowedMemberTypes + description = $roleName + displayName = $roleName + id = [Guid]::NewGuid() + isEnabled = $true + value = $roleName } # Add to the typed list @@ -356,21 +361,24 @@ function Set-EntraAppRoleToApplicationUser { if ($createdRoles.Count -gt 0) { # Update application with the new typed list $params = @{ - ApplicationId = $ApplicationId - AppRoles = $appRolesList - Tags = @("WindowsAzureActiveDirectoryIntegratedApp") + appRoles = $appRolesList + tags = @("WindowsAzureActiveDirectoryIntegratedApp") } + + $params = $params | ConvertTo-Json -Depth 10 + + $patchResponse = Invoke-GraphRequest -Uri "/v1.0/applications/$ApplicationId" -Method PATCH -Body $params + $patchResponse = $patchResponse | ConvertTo-Json | ConvertFrom-Json - Update-MgApplication @params Write-ColoredVerbose "Updated application with $($createdRoles.Count) new roles" -Color "Green" return $createdRoles | ForEach-Object { [PSCustomObject]@{ ApplicationId = $ApplicationId - RoleId = $_.Id - DisplayName = $_.DisplayName - Description = $_.Description - Value = $_.Value + RoleId = $_.id + DisplayName = $_.displayName + Description = $_.description + Value = $_.value IsEnabled = $true } } From 9269864ff8832abe7ea5c9fddb1b90b45987bd67 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Tue, 8 Apr 2025 10:11:04 +0300 Subject: [PATCH 20/25] Add AllowedMemberTypes param --- .../Set-EntraAppRoleToApplicationUser.ps1 | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index d5abb2969..d61f98816 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -29,6 +29,12 @@ function Set-EntraAppRoleToApplicationUser { ParameterSetName = 'ExportResults')] [string]$SignInAudience = "AzureADMyOrg", + [Parameter(Mandatory = $false, + ParameterSetName = 'ExportResults', + HelpMessage = "Specifies whether this app role can be assigned to users and groups (by setting to ['User']), + to other application's (by setting to ['Application'], or both (by setting to ['User', 'Application']).")] + [string[]]$AllowedMemberTypes = @("User"), + [Parameter(Mandatory = $false, ParameterSetName = 'ExportResults', HelpMessage = "Switch to enable export of results into a CSV file")] @@ -305,7 +311,10 @@ function Set-EntraAppRoleToApplicationUser { [string[]]$UniqueRoles, [Parameter(Mandatory = $true)] - [string]$ApplicationId + [string]$ApplicationId, + + [Parameter(Mandatory = $true)] + [string[]]$AllowedMemberTypes ) try { @@ -331,8 +340,7 @@ function Set-EntraAppRoleToApplicationUser { foreach ($role in $existingRoles) { $appRolesList.Add($role) } - - $allowedMemberTypes = @("User","Application") # Define allowed member types + $createdRoles = [System.Collections.ArrayList]::new() foreach ($roleName in $UniqueRoles) { @@ -344,7 +352,7 @@ function Set-EntraAppRoleToApplicationUser { # Create new AppRole object $appRole = @{ - allowedMemberTypes = $allowedMemberTypes + allowedMemberTypes = $AllowedMemberTypes description = $roleName displayName = $roleName id = [Guid]::NewGuid() @@ -438,7 +446,7 @@ function Set-EntraAppRoleToApplicationUser { if ($uniqueRoles.Count -gt 0) { Write-ColoredVerbose "Creating required roles in application..." -Color "Cyan" - $createdRoles = NewAppRoleIfNotExists -UniqueRoles $uniqueRoles -ApplicationId $application.ApplicationId + $createdRoles = NewAppRoleIfNotExists -UniqueRoles $uniqueRoles -ApplicationId $application.ApplicationId -AllowedMemberTypes $AllowedMemberTypes if ($createdRoles) { Write-ColoredVerbose "Successfully created $($createdRoles.Count) new roles" -Color "Green" } @@ -511,7 +519,7 @@ function Set-EntraAppRoleToApplicationUser { RoleAssignmentStatus = $assignment.Status AssignmentId = $assignment.AssignmentId AppRoleId = $assignment.AppRoleId - PrincipalType = "User" # Based on the AllowedMemberTypes in role creation + PrincipalType = $AllowedMemberTypes RoleAssignmentCreatedDateTime = $assignment.CreatedDateTime ResourceId = $application.ServicePrincipalId # Same as ServicePrincipalId in this context ProcessedTimestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') From d98968797f152b52352e31279f4ed7490ca73e6d Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Wed, 9 Apr 2025 10:28:41 +0300 Subject: [PATCH 21/25] Refactor allowedMemberTypes --- .../Set-EntraAppRoleToApplicationUser.ps1 | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index d61f98816..3e21b8d27 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -29,12 +29,6 @@ function Set-EntraAppRoleToApplicationUser { ParameterSetName = 'ExportResults')] [string]$SignInAudience = "AzureADMyOrg", - [Parameter(Mandatory = $false, - ParameterSetName = 'ExportResults', - HelpMessage = "Specifies whether this app role can be assigned to users and groups (by setting to ['User']), - to other application's (by setting to ['Application'], or both (by setting to ['User', 'Application']).")] - [string[]]$AllowedMemberTypes = @("User"), - [Parameter(Mandatory = $false, ParameterSetName = 'ExportResults', HelpMessage = "Switch to enable export of results into a CSV file")] @@ -311,10 +305,7 @@ function Set-EntraAppRoleToApplicationUser { [string[]]$UniqueRoles, [Parameter(Mandatory = $true)] - [string]$ApplicationId, - - [Parameter(Mandatory = $true)] - [string[]]$AllowedMemberTypes + [string]$ApplicationId ) try { @@ -349,10 +340,13 @@ function Set-EntraAppRoleToApplicationUser { Write-ColoredVerbose "Role '$roleName' already exists in application" -Color "Yellow" continue } + + $memberTypes = $roleToMemberTypeMapping[$roleName] + $allowedMemberTypes = $memberTypes -split "," # Create array from comma separated string # Create new AppRole object $appRole = @{ - allowedMemberTypes = $AllowedMemberTypes + allowedMemberTypes = $allowedMemberTypes description = $roleName displayName = $roleName id = [Guid]::NewGuid() @@ -432,12 +426,15 @@ function Set-EntraAppRoleToApplicationUser { Write-ColoredVerbose "Application $ApplicationName status: $($application.Status) | ApplicationId : $($application.ApplicationId) | AppId : $($application.AppId) | ServicePrincipalId : $($application.ServicePrincipalId)." -Color "Green" $uniqueRoles = @() + $roleToMemberTypeMapping = @{} # Extract unique roles $users | ForEach-Object { $role = SanitizeInput -Value $_.Role if ($role -and $role -notin $uniqueRoles) { $uniqueRoles += $role + $memberType = SanitizeInput -Value $_.memberType + $roleToMemberTypeMapping[$role] = $memberType } } @@ -446,7 +443,7 @@ function Set-EntraAppRoleToApplicationUser { if ($uniqueRoles.Count -gt 0) { Write-ColoredVerbose "Creating required roles in application..." -Color "Cyan" - $createdRoles = NewAppRoleIfNotExists -UniqueRoles $uniqueRoles -ApplicationId $application.ApplicationId -AllowedMemberTypes $AllowedMemberTypes + $createdRoles = NewAppRoleIfNotExists -UniqueRoles $uniqueRoles -ApplicationId $application.ApplicationId if ($createdRoles) { Write-ColoredVerbose "Successfully created $($createdRoles.Count) new roles" -Color "Green" } @@ -487,6 +484,7 @@ function Set-EntraAppRoleToApplicationUser { # Get the user's role $userRole = SanitizeInput -Value $user.Role + $userRoleType = SanitizeInput -Value $user.memberType Write-ColoredVerbose "Role : $($userRole)" -Color "Green" if (-not $userRole) { Write-Warning "Skipping user due to invalid Role: $($user.Role)" @@ -519,7 +517,7 @@ function Set-EntraAppRoleToApplicationUser { RoleAssignmentStatus = $assignment.Status AssignmentId = $assignment.AssignmentId AppRoleId = $assignment.AppRoleId - PrincipalType = $AllowedMemberTypes + PrincipalType = $userRoleType RoleAssignmentCreatedDateTime = $assignment.CreatedDateTime ResourceId = $application.ServicePrincipalId # Same as ServicePrincipalId in this context ProcessedTimestamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') From be5639c84d9c8b6f2b12071cd80471674e7acd65 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Wed, 9 Apr 2025 10:40:00 +0300 Subject: [PATCH 22/25] Refactor filename --- .../Governance/Set-EntraAppRoleToApplicationUser.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 3e21b8d27..7e741d12a 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -16,7 +16,7 @@ function Set-EntraAppRoleToApplicationUser { ParameterSetName = 'ExportResults')] [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] - [System.IO.FileInfo]$FileName, + [System.IO.FileInfo]$FilePath, [Parameter(Mandatory = $true, HelpMessage = "Name of the application (Service Principal) to assign roles for", @@ -399,11 +399,11 @@ function Set-EntraAppRoleToApplicationUser { try { # Import users from the CSV file - Write-ColoredVerbose "Importing users from file: $FileName" -Color "Cyan" - $users = Import-Csv -Path $FileName + Write-ColoredVerbose "Importing users from file: $FilePath" -Color "Cyan" + $users = Import-Csv -Path $FilePath Write-ColoredVerbose "Imported : $($users.Count) users" -Color "Green" if (-not $users) { - Write-Error "No users found in the provided file: $FileName" + Write-Error "No users found in the provided file: $FilePath" return } @@ -545,7 +545,7 @@ function Set-EntraAppRoleToApplicationUser { } # Debugging output - Write-ColoredVerbose -Message "Starting orchestration with params: AppName - $ApplicationName | FileName - $FileName | DataSource - $DataSource" -Color "Magenta" + Write-ColoredVerbose -Message "Starting orchestration with params: AppName - $ApplicationName | FilePath - $FilePath | DataSource - $DataSource" -Color "Magenta" # Start orchestration StartOrchestration From 6db8ea6cfaa8419361a2bc4db59cbe585104c890 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Wed, 9 Apr 2025 10:46:01 +0300 Subject: [PATCH 23/25] Fix Export --- .../Governance/Set-EntraAppRoleToApplicationUser.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 7e741d12a..199f17516 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -36,7 +36,7 @@ function Set-EntraAppRoleToApplicationUser { [Parameter(Mandatory = $false, ParameterSetName = 'ExportResults', HelpMessage = "Path for the export file. Defaults to current directory.")] - [System.IO.FileInfo]$ExportFileName = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv") + [System.IO.FileInfo]$ExportFilePath = (Join-Path (Get-Location) "EntraAppRoleAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv") ) process { @@ -526,10 +526,10 @@ function Set-EntraAppRoleToApplicationUser { } # Export results if using ExportResults parameter set - if ($PSCmdlet.ParameterSetName -eq 'ExportResults' -and $assignmentResults.Count -gt 0) { + if ($Export -and $assignmentResults.Count -gt 0) { try { - Write-ColoredVerbose "Exporting results to: $ExportFileName" -Color "Cyan" - $assignmentResults | Export-Csv -Path $ExportFileName -NoTypeInformation -Force + Write-ColoredVerbose "Exporting results to: $ExportFilePath" -Color "Cyan" + $assignmentResults | Export-Csv -Path $ExportFilePath -NoTypeInformation -Force Write-ColoredVerbose "Successfully exported $($assignmentResults.Count) assignments" -Color "Green" } catch { @@ -550,6 +550,5 @@ function Set-EntraAppRoleToApplicationUser { StartOrchestration } - } From 8ae9914a4771d8f551101f3dd6590bb9eef2f2a0 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Wed, 9 Apr 2025 10:48:12 +0300 Subject: [PATCH 24/25] Remove unnecessary file --- EntraAppRoleAssignments_20250401_082500.csv | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 EntraAppRoleAssignments_20250401_082500.csv diff --git a/EntraAppRoleAssignments_20250401_082500.csv b/EntraAppRoleAssignments_20250401_082500.csv deleted file mode 100644 index 1d819ee5b..000000000 --- a/EntraAppRoleAssignments_20250401_082500.csv +++ /dev/null @@ -1,15 +0,0 @@ -"UserPrincipalName","DisplayName","UserId","Role","ApplicationName","ApplicationStatus","ServicePrincipalId","UserCreationStatus","RoleAssignmentStatus","AssignmentId","AppRoleId","PrincipalType","RoleAssignmentCreatedDateTime","ResourceId","ProcessedTimestamp" -"user1@3zmb3n.onmicrosoft.com","user one","0c1dd8d1-8c7c-4c57-9e2e-4771412fca99","admin","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","0dgdDHyMV0yeLkdxQS_KmR8WTpgZrvpCrziyklmrZjM","08363d34-3edb-450e-a909-e0f902c645c1","User","01/04/2025 05:25:04","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:04" -"user2@3zmb3n.onmicrosoft.com","user two","43bc4624-8b02-4a0c-b9a9-e5d44ed2a1b2","reader","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","JEa8QwKLDEq5qeXUTtKhsoCEs8q5UXxDkwU_MJ65FY8","861e30b6-0999-4ec7-a94a-ce389a4050a2","User","01/04/2025 05:25:05","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:06" -"user3@3zmb3n.onmicrosoft.com","user three","7f7ba43e-c862-4e5e-be1b-e56acd319833","admin","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Exists","0dgdDHyMV0yeLkdxQS_KmR8WTpgZrvpCrziyklmrZjM","08363d34-3edb-450e-a909-e0f902c645c1","User","01/04/2025 05:25:04","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:07" -"henriettam@3zmb3n.onmicrosoft.com","henrietta mueller","3edc80c8-fabb-40bc-bf91-615c5922d6d8","admin","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Exists","0dgdDHyMV0yeLkdxQS_KmR8WTpgZrvpCrziyklmrZjM","08363d34-3edb-450e-a909-e0f902c645c1","User","01/04/2025 05:25:04","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:08" -"user5@3zmb3n.onmicrosoft.com","user five","7e7cd61d-16fd-4a4a-8fa3-c78e44ab31fc","report","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","HdZ8fv0WSkqPo8eORKsx_J5Hl9k_Hc9NvjNKp4_QP4s","4e7f6d38-0b01-4163-945a-d197d8fb3353","User","01/04/2025 05:25:09","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:09" -"leeg@3zmb3n.onmicrosoft.com","lee gu","ce173514-79af-4cd4-af05-c12c713d9cee","survey","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","FDUXzq951EyvBcEscT2c7opvvx4PVPBJjlj5IVbWmR8","789b722a-1f19-46ef-9018-21ee306b2399","User","01/04/2025 05:25:10","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:10" -"jonis@3zmb3n.onmicrosoft.com","joni sherman","8a3adb6d-a652-43b0-94a3-4d1fe9f23fe7","audit","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","bds6ilKmsEOUo00f6fI_52zf4qLOexJAuMuSNlfxsfI","5ec11943-2bc4-4a0f-bb17-763dc6f9b41f","User","01/04/2025 05:25:11","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:12" -"user6@3zmb3n.onmicrosoft.com","user six","933ea694-b8cc-449f-87fe-d4a2fb9721cd","support","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","lKY-k8y4n0SH_tSi-5chzY8MVrrEJfRNpczgjT-zwtg","5503e7c6-24c4-4527-ae6e-fe748d3bb7dd","User","01/04/2025 05:25:13","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:13" -"pattif@3zmb3n.onmicrosoft.com","patti fuller","740d59d4-057f-4a6d-838b-a30bf3e31ec4","superadmin","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","1FkNdH8FbUqDi6ML8-MexMwSQacQ0jtNpe06Cch8bFk","c6da3282-4c3a-41a0-b165-e792e35a92b9","User","01/04/2025 05:25:14","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:15" -"user10@3zmb3n.onmicrosoft.com","user ten","bef70523-0bee-40be-9078-357025e47a86","helpdesk","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","IwX3vu4LvkCQeDVwJeR6hlZUJWNtsp1KskcY7yTMt0w","44b4c10b-e661-49cd-bf1d-dafe570f5c2c","User","01/04/2025 05:25:15","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:16" -"user11@3zmb3n.onmicrosoft.com","user eleven","debb8674-02bd-4d58-9516-8c8d0a2615b6","security","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","dIa73r0CWE2VFoyNCiYVtgoO-WqjopBOggUIj8A6Sp0","d30598d0-2586-485a-a366-add0e860c917","User","01/04/2025 05:25:17","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:17" -"miriamg@3zmb3n.onmicrosoft.com","miriam girling","56639f12-7bca-46d6-b4e6-075585c36d02","reader","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Exists","JEa8QwKLDEq5qeXUTtKhsoCEs8q5UXxDkwU_MJ65FY8","861e30b6-0999-4ec7-a94a-ce389a4050a2","User","01/04/2025 05:25:05","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:18" -"user20@3zmb3n.onmicrosoft.com","user twenty","bc191592-a347-4a4b-9fd6-c3c9a4067beb","helpdesk","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Exists","IwX3vu4LvkCQeDVwJeR6hlZUJWNtsp1KskcY7yTMt0w","44b4c10b-e661-49cd-bf1d-dafe570f5c2c","User","01/04/2025 05:25:15","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:19" -"derrick@3zmb3n.onmicrosoft.com","derrick mkenya","3b476862-d4c0-4691-a4cb-8ed0c2cfa999","developer","TestApp","Exists","50df5c9c-5963-4e8e-a260-61448cc93a82","Exists","Created","YmhHO8DUkUaky47Qws-pmdJnPWde4rVFm8Oqv_he7N8","2aa9c69f-fc4a-446e-b198-e16a652d8603","User","01/04/2025 05:25:20","50df5c9c-5963-4e8e-a260-61448cc93a82","2025-04-01 08:25:20" From bbbd8033801554cad06afd75d44b399f0c43eee0 Mon Sep 17 00:00:00 2001 From: Kennedy Kangethe Munga Date: Wed, 9 Apr 2025 11:17:00 +0300 Subject: [PATCH 25/25] Update params --- .../Set-EntraAppRoleToApplicationUser.ps1 | 15 +++--- .../Set-EntraAppRoleToApplicationUser.md | 54 ++++++++++++------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 index 199f17516..0a10df581 100644 --- a/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 +++ b/module/Entra/Microsoft.Entra/Governance/Set-EntraAppRoleToApplicationUser.ps1 @@ -3,30 +3,27 @@ # Licensed under the MIT License. See License in the project root for license information. # ------------------------------------------------------------------------------ function Set-EntraAppRoleToApplicationUser { - [CmdletBinding(DefaultParameterSetName = 'ExportResults')] + [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true, - HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic' which determines the column attribute mapping.", - ParameterSetName = 'ExportResults')] + HelpMessage = "Specify the data source type: 'DatabaseorDirectory', 'SAPCloudIdentity', or 'Generic' which determines the column attribute mapping.")] [ValidateSet("DatabaseorDirectory", "SAPCloudIdentity", "Generic")] [string]$DataSource, [Parameter(Mandatory = $true, - HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv", - ParameterSetName = 'ExportResults')] + HelpMessage = "Path to the input file containing users, e.g., C:\temp\users.csv")] [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path $_ })] [System.IO.FileInfo]$FilePath, [Parameter(Mandatory = $true, - HelpMessage = "Name of the application (Service Principal) to assign roles for", - ParameterSetName = 'ExportResults')] + HelpMessage = "Name of the application (Service Principal) to assign roles for")] [ValidateNotNullOrEmpty()] [string]$ApplicationName, [Parameter(Mandatory = $false, - HelpMessage = "Specifies what Microsoft accounts are supported for the application", - ParameterSetName = 'ExportResults')] + HelpMessage = "Specifies what Microsoft accounts are supported for the application. Options are 'AzureADMyOrg', 'AzureADMultipleOrgs', 'AzureADandPersonalMicrosoftAccount', 'PersonalMicrosoftAccount'")] + [ValidateSet("AzureADMyOrg", "AzureADMultipleOrgs", "AzureADandPersonalMicrosoftAccount", "PersonalMicrosoftAccount")] [string]$SignInAudience = "AzureADMyOrg", [Parameter(Mandatory = $false, diff --git a/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md b/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md index 2326b93ab..6698b35d2 100644 --- a/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md +++ b/module/docs/entra-powershell-v1.0/Governance/Set-EntraAppRoleToApplicationUser.md @@ -29,8 +29,9 @@ Add existing application users to Microsoft Entra ID and assign them roles. ```powershell Set-EntraAppRoleToApplicationUser -DataSource - -FileName + -FilePath -ApplicationName + [-SignInAudience ] [] ``` @@ -39,10 +40,11 @@ Set-EntraAppRoleToApplicationUser ```powershell Set-EntraAppRoleToApplicationUser -DataSource - -FileName + -FilePath -ApplicationName - -Export - -ExportFileName + [-SignInAudience ] + [-Export] + [-ExportFilePath ] [] ``` @@ -64,26 +66,26 @@ In delegated scenarios, the signed-in user must have either a supported Microsof ```powershell Connect-Entra -Scopes 'User.ReadWrite.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'EntitlementManagement.ReadWrite.All' -Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users.csv" -ApplicationName "TestApp" +Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FilePath "C:\temp\users.csv" -ApplicationName "TestApp" ``` This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. - `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. -- `-FileName` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. +- `-FilePath` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. - `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. ### Example 2: Assign application users to app role assignments with verbose mode ```powershell Connect-Entra -Scopes 'User.ReadWrite.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'EntitlementManagement.ReadWrite.All' -Set-EntraAppRoleToApplicationUser -DataSource "SAPCloudIdentity" -FileName "C:\temp\users-exported-from-sap.csv" -ApplicationName "TestApp" -Verbose +Set-EntraAppRoleToApplicationUser -DataSource "SAPCloudIdentity" -FilePath "C:\temp\users-exported-from-sap.csv" -ApplicationName "TestApp" -Verbose ``` This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. - `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. -- `-FileName` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. +- `-FilePath` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. - `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. - `-Verbose` common parameter outputs the execution steps during processing. @@ -91,31 +93,31 @@ This example assigns users to app roles. It creates missing users and app roles. ```powershell Connect-Entra -Scopes 'User.ReadWrite.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'EntitlementManagement.ReadWrite.All' -Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users.csv" -ApplicationName "TestApp" -Export -Verbose +Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FilePath "C:\temp\users.csv" -ApplicationName "TestApp" -Export -Verbose ``` This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. - `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. -- `-FileName` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. +- `-FilePath` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. - `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. -- `-Export` switch parameter enables export of results into a CSV file. If `ExportFileName` parameter isn't provided, results are exported in the current location. +- `-Export` switch parameter enables export of results into a CSV file. If `ExportFilePath` parameter isn't provided, results are exported in the current location. - `-Verbose` common parameter outputs the execution steps during processing. ### Example 4: Assign application users to app roles and export to a specified location ```powershell Connect-Entra -Scopes 'User.ReadWrite.All', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All', 'EntitlementManagement.ReadWrite.All' -Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FileName "C:\temp\users.csv" -ApplicationName "TestApp" -Export -ExportFileName "C:\temp\EntraAppRoleAssignments_yyyyMMdd.csv" -Verbose +Set-EntraAppRoleToApplicationUser -DataSource "Generic" -FilePath "C:\temp\users.csv" -ApplicationName "TestApp" -Export -ExportFilePath "C:\temp\EntraAppRoleAssignments_yyyyMMdd.csv" -Verbose ``` This example assigns users to app roles. It creates missing users and app roles. If a role assignment doesn't exist, it's created; otherwise, it's skipped. - `-DataSource` parameter specifies the source of the data, for example, SAP Identity, database, or directory. The value determines the attribute matching. For example, For SAP Cloud Identity Services, the default mapping is `userName` (SAP SCIM) to `userPrincipalName` (Microsoft Entra ID). For databases or directories, the `Email` column value might match the `userPrincipalName` in Microsoft Entra ID. -- `-FileName` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. +- `-FilePath` parameter specifies the path to the input file containing users, for example, `C:\temp\users.csv`. - `-ApplicationName` parameter specifies the application name in Microsoft Entra ID. -- `-Export` switch parameter enables export of results into a CSV file. If `ExportFileName` parameter isn't provided, results are exported in the current location. -- `-ExportFileName` parameter specifies a specific filename and location to export results. +- `-Export` switch parameter enables export of results into a CSV file. If `ExportFilePath` parameter isn't provided, results are exported in the current location. +- `-ExportFilePath` parameter specifies a specific filename and location to export results. - `-Verbose` common parameter outputs the execution steps during processing. ## Parameters @@ -136,7 +138,7 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -FileName +### -FilePath Specifies the path to the input file containing users, for example, `C:\temp\users.csv`. @@ -168,9 +170,25 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -SignInAudience + +Specifies what Microsoft accounts are supported for the application. Options are "AzureADMyOrg", "AzureADMultipleOrgs", "AzureADandPersonalMicrosoftAccount" and "PersonalMicrosoftAccount". + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: AzureADMyOrg +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Export -Enables export of results into a CSV file. If `ExportFileName` parameter isn't provided, results are exported in the current location. +Enables export of results into a CSV file. If `ExportFilePath` parameter isn't provided, results are exported in the current location. ```yaml Type: System.Management.Automation.SwitchParameter @@ -184,7 +202,7 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -ExportFileName +### -ExportFilePath Specifies a specific filename and location to export results.