Skip to content

Commit 7680d78

Browse files
authored
Merge branch 'main' into UpdateCheckSharing
2 parents 355af0a + c7cb6b5 commit 7680d78

24 files changed

Lines changed: 2886 additions & 400 deletions

Hybrid/ConfigureExchangeHybridApplication/ConfigureExchangeHybridApplication.ps1

Lines changed: 434 additions & 117 deletions
Large diffs are not rendered by default.

Security/src/CVE-2023-23397/CVE-2023-23397.ps1

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,9 @@ begin {
243243
. $PSScriptRoot\..\..\..\Shared\AzureFunctions\Invoke-GraphApiRequest.ps1
244244
. $PSScriptRoot\..\..\..\Shared\GraphApiFunctions\Remove-AzureApplication.ps1
245245
. $PSScriptRoot\..\..\..\Shared\GraphApiFunctions\Get-AzureApplication.ps1
246-
. $PSScriptRoot\..\..\..\Shared\GraphApiFunctions\New-EwsAzureApplication.ps1
246+
. $PSScriptRoot\..\..\..\Shared\GraphApiFunctions\New-ApiPermissionObject.ps1
247247
. $PSScriptRoot\..\..\..\Shared\GraphApiFunctions\New-AzureApplicationAppSecret.ps1
248+
. $PSScriptRoot\..\..\..\Shared\GraphApiFunctions\New-ExchangeAzureApplication.ps1
248249
. $PSScriptRoot\..\..\..\Shared\Show-Disclaimer.ps1
249250

250251
$loggerParams = @{
@@ -635,11 +636,12 @@ begin {
635636
}
636637

637638
$createAzureApplicationParams = @{
638-
AzAccountsObject = $graphAccessToken
639-
AzureApplicationName = $AzureApplicationName
640-
GraphApiUrl = $graphApiEndpoint
639+
AzAccountsObject = $graphAccessToken
640+
AzureApplicationName = $AzureApplicationName
641+
GraphApiUrl = $graphApiEndpoint
642+
RequestedApiPermissions = (New-ApiPermissionObject -ApiType "EWS" -Permissions "full_access_as_app" -PermissionType "Application")
641643
}
642-
$return = New-EwsAzureApplication @createAzureApplicationParams
644+
$return = New-ExchangeAzureApplication @createAzureApplicationParams
643645

644646
if ($null -ne $return.AppId) {
645647
Write-Host "The application was successfully created" -ForegroundColor Green

Shared/Get-ExchangeBuildVersionInformation.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ function Get-ExchangeBuildVersionInformation {
131131
$cuReleaseDate = "07/01/2025"
132132
$supportedBuildNumber = $true
133133
}
134+
(GetBuildVersion $exSE "RTM" -SU "May26HU") { $latestSUBuild = $true }
134135
(GetBuildVersion $exSE "RTM" -SU "Feb26SU") { $latestSUBuild = $true }
135136
}
136137
} elseif ($exchangeVersion.Major -eq 15 -and $exchangeVersion.Minor -eq 2) {
@@ -881,6 +882,7 @@ function GetExchangeBuildDictionary {
881882
"Oct25SU" = "15.2.2562.29"
882883
"Dec25SU" = "15.2.2562.35"
883884
"Feb26SU" = "15.2.2562.37"
885+
"May26HU" = "15.2.2562.41"
884886
})
885887
}
886888
}

Shared/GraphApiFunctions/Add-AzureApplicationOwner.ps1

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,64 @@
44
. $PSScriptRoot\..\AzureFunctions\Invoke-GraphApiRequest.ps1
55

66
<#
7-
Adds a new owner to an existing Azure Application
8-
Get application information: https://learn.microsoft.com/graph/api/application-get
9-
Add owner: https://learn.microsoft.com/graph/api/application-post-owners
7+
.SYNOPSIS
8+
Adds a user as an owner to an existing Azure AD application.
9+
10+
.DESCRIPTION
11+
This function adds a specified user as an owner of an Azure AD application using the
12+
Microsoft Graph API. Application owners have full control over the application registration,
13+
including the ability to modify its configuration, credentials, and permissions.
14+
15+
The function performs the following operations:
16+
1. Queries the existing owners of the Azure application
17+
2. Checks if the specified user is already an owner
18+
3. If not already an owner, adds the user as a new owner via Graph API
19+
4. Returns a result object indicating success and the reason
20+
21+
The function is idempotent - if the user is already an owner, it returns success
22+
without making any changes.
23+
24+
.PARAMETER AzAccountsObject
25+
The Azure accounts object containing authentication context (AccessToken) for Graph API calls.
26+
27+
.PARAMETER ApplicationId
28+
The Object ID (not AppId) of the Azure AD application to add the owner to.
29+
This is the unique identifier of the application object in Azure AD.
30+
31+
.PARAMETER NewOwnerUserId
32+
The Object ID of the user to add as an owner of the application.
33+
This must be a valid directory object ID for a user in the tenant.
34+
35+
.PARAMETER GraphApiUrl
36+
The Microsoft Graph API endpoint URL to use for API requests (e.g., "https://graph.microsoft.com").
37+
38+
.OUTPUTS
39+
PSCustomObject with the following properties:
40+
- IsOwner: Boolean indicating whether the user is now an owner (true if added or already was an owner)
41+
- Reason: String indicating the result:
42+
- "Successful": User was successfully added as an owner
43+
- "AlreadyAnOwner": User was already an owner, no changes made
44+
- "UnableToQueryExistingOwners": Failed to query the current owners list
45+
- "AddFailed": Failed to add the user as an owner
46+
47+
.EXAMPLE
48+
$result = Add-AzureApplicationOwner -AzAccountsObject $azContext -ApplicationId "12345678-1234-1234-1234-123456789012" -NewOwnerUserId "87654321-4321-4321-4321-210987654321" -GraphApiUrl "https://graph.microsoft.com"
49+
50+
if ($result.IsOwner) {
51+
Write-Host "User is now an owner. Reason: $($result.Reason)"
52+
} else {
53+
Write-Host "Failed to add owner. Reason: $($result.Reason)"
54+
}
55+
56+
.NOTES
57+
Required Graph API permissions:
58+
- Application.ReadWrite.All (to read and modify application owners)
59+
60+
API References:
61+
- Get application owners: https://learn.microsoft.com/graph/api/application-list-owners
62+
- Add application owner: https://learn.microsoft.com/graph/api/application-post-owners
63+
64+
This function supports -WhatIf and -Confirm through ShouldProcess.
1065
#>
1166
function Add-AzureApplicationOwner {
1267
[CmdletBinding(SupportsShouldProcess)]

Shared/GraphApiFunctions/Add-AzureApplicationRole.ps1

Lines changed: 136 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,52 @@
44
. $PSScriptRoot\..\AzureFunctions\Invoke-GraphApiRequest.ps1
55

66
<#
7-
Assigns permission to an Azure Application
7+
.SYNOPSIS
8+
Assigns API permissions to an Azure AD application.
89
9-
The resourceAccessObject which is passed in the body of the Graph API call
10-
It specifies the resources that the application needs to access
11-
resourceAppId specifies the resources that the application needs to access and also the set of delegated permissions and application roles that it needs for each of those resources
12-
resourceAccess id is the unique identifier of an app role or delegated permission exposed by the resource application
13-
resourceAccess type specifies whether the id property references a delegated permission or an app role (application permission)
10+
.DESCRIPTION
11+
This function adds required resource access permissions to an Azure AD application using the Microsoft Graph API.
12+
It constructs a requiredResourceAccess object containing:
13+
- resourceAppId: The identifier of the resource application (e.g., Microsoft Graph) that the app needs access to.
14+
- resourceAccess: An array of permissions, where each entry includes:
15+
- id: The unique identifier of an app role or delegated permission exposed by the resource application.
16+
- type: Either "Role" (application permission) or "Scope" (delegated permission).
1417
15-
See:
18+
The function supports two modes of operation:
19+
1. Merge mode (KeepExistingPermissions = $true, default):
20+
- Retrieves existing permissions from the application
21+
- Preserves permissions for other resource applications (e.g., EWS when adding Graph permissions)
22+
- Merges new permissions with existing ones for the same resource application, avoiding duplicates
23+
24+
2. Overwrite mode (KeepExistingPermissions = $false):
25+
- Replaces all existing requiredResourceAccess entries with only the new permissions
26+
- Use with caution as this removes permissions for other resource applications
27+
28+
.PARAMETER AzAccountsObject
29+
An object containing Azure account information, including the AccessToken for authentication.
30+
31+
.PARAMETER ApplicationId
32+
The object ID of the Azure AD application to update (not the AppId/ClientId).
33+
34+
.PARAMETER ResourceId
35+
The AppId of the resource application providing the permissions (e.g., "00000003-0000-0000-c000-000000000000" for Microsoft Graph).
36+
37+
.PARAMETER ApiPermissions
38+
An array of permission objects, each containing:
39+
- AppRole: An object with an 'id' property representing the permission GUID
40+
- PermissionType: Either "Application" (for app-only permissions) or "Delegated" (for user-delegated permissions)
41+
42+
.PARAMETER KeepExistingPermissions
43+
When $true (default), preserves existing permissions and merges new ones.
44+
When $false, replaces all permissions with only the specified new permissions.
45+
46+
.PARAMETER GraphApiUrl
47+
The base URL for Microsoft Graph API calls (e.g., "https://graph.microsoft.com/v1.0").
48+
49+
.OUTPUTS
50+
System.Boolean - Returns $true if permissions were successfully added, $false otherwise.
51+
52+
.LINK
1653
https://learn.microsoft.com/graph/api/application-update
1754
https://learn.microsoft.com/graph/api/resources/requiredresourceaccess
1855
https://learn.microsoft.com/graph/api/resources/resourceaccess
@@ -31,32 +68,108 @@ function Add-AzureApplicationRole {
3168
$ResourceId,
3269

3370
[ValidateNotNullOrEmpty()]
34-
$AppRoleId,
71+
[System.Object[]]$ApiPermissions,
3572

36-
[ValidateSet("Scope", "Role")]
37-
$Type = "Role",
73+
$KeepExistingPermissions = $true,
3874

3975
[ValidateNotNullOrEmpty()]
4076
$GraphApiUrl
4177
)
4278

4379
Write-Verbose "Adding permission to Azure Application: $ApplicationId via Graph Api: $GraphApiUrl"
44-
Write-Verbose "ResourceId: $ResourceId - AppRoleId: $AppRoleId - Type: $Type"
80+
Write-Verbose "ResourceId: $ResourceId - Permissions to be added: $($ApiPermissions | ConvertTo-Json -Depth 4)"
81+
82+
# This list will hold all requiredResourceAccess entries for the final PATCH request
83+
# Each entry represents permissions for a specific resource application (e.g., Graph, EWS)
84+
$requiredResourceAccessList = New-Object System.Collections.Generic.List[object]
85+
86+
# Transform the input ApiPermissions into the resourceAccess format expected by Graph API
87+
# Each entry maps to: { id: <permission GUID>, type: "Role" or "Scope" }
88+
# "Role" = Application permission (app-only), "Scope" = Delegated permission (user context)
89+
$newResourceAccessEntries = foreach ($entry in $ApiPermissions) {
90+
[PSCustomObject]@{
91+
id = $entry.AppRole.id
92+
type = if ($entry.PermissionType -eq "Application") { "Role" } else { "Scope" }
93+
}
94+
}
4595

46-
$resourceAccessObject = [PSCustomObject]@{
47-
requiredResourceAccess = @(
48-
[PSCustomObject]@{
49-
resourceAppId = $ResourceId
50-
resourceAccess = @(
51-
[PSCustomObject]@{
52-
id = $AppRoleId
53-
type = $Type
96+
if ($KeepExistingPermissions) {
97+
Write-Verbose "Retrieving existing requiredResourceAccess from the application"
98+
$getApplicationParams = @{
99+
Query = "applications/$ApplicationId" + '?$select=requiredResourceAccess'
100+
AccessToken = $AzAccountsObject.AccessToken
101+
GraphApiUrl = $GraphApiUrl
102+
}
103+
104+
$getApplicationResponse = Invoke-GraphApiRequest @getApplicationParams
105+
106+
if ($getApplicationResponse.Successful -eq $false) {
107+
Write-Verbose "Failed to retrieve existing application permissions"
108+
return $false
109+
}
110+
111+
$existingRequiredResourceAccess = $getApplicationResponse.Content.requiredResourceAccess
112+
113+
if ($null -ne $existingRequiredResourceAccess) {
114+
# Iterate through all existing resource access entries
115+
# We need to preserve permissions for other resources while merging permissions for our target resource
116+
foreach ($existingResource in $existingRequiredResourceAccess) {
117+
if ($existingResource.resourceAppId -eq $ResourceId) {
118+
# Found the target resource - merge existing permissions with new ones
119+
# Start with existing permissions and add new ones that don't already exist
120+
$mergedResourceAccess = New-Object System.Collections.Generic.List[object]
121+
$mergedResourceAccess.AddRange(@($existingResource.resourceAccess))
122+
123+
# Add each new permission only if it doesn't already exist (avoid duplicates)
124+
# Comparing by id alone is sufficient because appRoles and oauth2PermissionScopes
125+
# use independently assigned GUIDs - the same id never appears as both Role and Scope
126+
foreach ($newEntry in $newResourceAccessEntries) {
127+
$existingEntry = $mergedResourceAccess | Where-Object { $_.id -eq $newEntry.id }
128+
if ($null -eq $existingEntry) {
129+
$mergedResourceAccess.Add($newEntry)
130+
}
54131
}
55-
)
132+
$requiredResourceAccessList.Add([PSCustomObject]@{
133+
resourceAppId = $ResourceId
134+
resourceAccess = $mergedResourceAccess.ToArray()
135+
})
136+
} else {
137+
# Keep existing resource access for other resourceAppIds
138+
$requiredResourceAccessList.Add($existingResource)
139+
}
56140
}
57-
)
141+
142+
# If ResourceId was not found in existing permissions, add it as new
143+
$resourceIdExists = $existingRequiredResourceAccess | Where-Object { $_.resourceAppId -eq $ResourceId }
144+
if ($null -eq $resourceIdExists) {
145+
$requiredResourceAccessList.Add([PSCustomObject]@{
146+
resourceAppId = $ResourceId
147+
resourceAccess = @($newResourceAccessEntries)
148+
})
149+
}
150+
} else {
151+
# No existing permissions, add new ones
152+
$requiredResourceAccessList.Add([PSCustomObject]@{
153+
resourceAppId = $ResourceId
154+
resourceAccess = @($newResourceAccessEntries)
155+
})
156+
}
157+
} else {
158+
# Overwrite mode - only include new permissions
159+
$requiredResourceAccessList.Add([PSCustomObject]@{
160+
resourceAppId = $ResourceId
161+
resourceAccess = @($newResourceAccessEntries)
162+
})
163+
}
164+
165+
# Use .ToArray() instead of @() to avoid "Argument types do not match" exception
166+
# when converting System.Collections.Generic.List[object] to an array
167+
$resourceAccessObject = [PSCustomObject]@{
168+
requiredResourceAccess = $requiredResourceAccessList.ToArray()
58169
}
59170

171+
# Prepare the PATCH request to update the application's requiredResourceAccess property
172+
# Depth 4 is required for proper JSON serialization of nested resourceAccess arrays
60173
$updateApplicationParams = @{
61174
Query = "applications/$ApplicationId"
62175
AccessToken = $AzAccountsObject.AccessToken
@@ -66,12 +179,12 @@ function Add-AzureApplicationRole {
66179
GraphApiUrl = $GraphApiUrl
67180
}
68181

69-
# Graph API call to add permissions to the Azure Application
182+
# Execute the Graph API PATCH request to update the application's permissions
70183
if ($PSCmdlet.ShouldProcess("PATCH $ResourceId", "Invoke-GraphApiRequest")) {
71184
$updateApplicationResponse = Invoke-GraphApiRequest @updateApplicationParams
72185

73186
if ($updateApplicationResponse.Successful -eq $false) {
74-
Write-Verbose "Something went wrong while adding permissions this Azure Application"
187+
Write-Verbose "Something went wrong while adding permissions to this Azure Application"
75188
return $false
76189
}
77190

0 commit comments

Comments
 (0)