Skip to content

Commit 85687cc

Browse files
committed
Use RBAC for Apps in EXO (instead of impersonation role)
1 parent 821bff5 commit 85687cc

File tree

1 file changed

+54
-21
lines changed

1 file changed

+54
-21
lines changed

Get-MailboxOWAStorageProvider.ps1

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,49 +16,64 @@
1616
Get the configured third-party storage providers for a given mailbox.
1717
.Description
1818
Using Exchange Web Services, any third-party storage providers configured for a mailbox (which can be
19-
configured via Outlook on the web) will be retrieved, including the account name
20-
configured for a given provider.
21-
22-
Important: Requires the EWS Managed API to be installed on the local machine.
19+
configured via Outlook on the web) will be retrieved, including the account name configured
20+
for a given provider. Supports client secret and certificate authentication.
21+
22+
* Important: Requires the EWS Managed API to be installed on the local machine.
2323
1. Run PowerShell as Administrator
2424
2. Run the following: Install-Package Microsoft.Exchange.Webservices
25-
Note: If NuGet is not a registered package source: https://learn.microsoft.com/en-us/powershell/gallery/powershellget/supported-repositories
26-
Important: Requires the authenticated account to have impersonation access to the mailbox: https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-configure-impersonation
27-
Important: Requires an Entra ID app registration with delegated permission for EWS.AccessAsUser.All.
28-
Details for registering an app and adding the delegated permission:
25+
Note: If NuGet is not a registered package source:
26+
https://learn.microsoft.com/en-us/powershell/gallery/powershellget/supported-repositories
27+
* Important: Requires an Entra ID app registration configured for app-only authentication
28+
with EWS.AccessAsApp (full_access_as_app).
29+
Details for registering an app and adding the role to the manifest:
2930
https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth#register-your-application
3031
Note: Enter the application ID and tenant's default routing domain in the variables at the top of the begin block.
32+
* Important: Requires the corresponding enterprise application (service principal) to have a role assignment
33+
with the "Application EWS.AccessAsApp" role (and a scope that includes the desired mailboxes).
34+
Details for creating the service principal link and management role assignment in EXO:
35+
https://learn.microsoft.com/en-us/exchange/permissions-exo/application-rbac
3136
.Parameter EmailAddress
3237
Email address of the mailbox from which to retrieve the configuration. Supports pipeline input
3338
of email addresses or objects with an EmailAddress or PrimarySMTPAddress property, such as
3439
with Get-Mailbox.
3540
.Parameter Cloud
3641
Office 365 environment which hosts the mailboxes. Valid values are Commercial, USGovGCC, China.
3742
Default value is Commercial. The feature is not available in GCC High and DoD. Unknown if available in China.
43+
.Parameter CertificateAuthentication
44+
Use a certificate for authentication instead of a client secret. The app registration must have a
45+
certificate uploaded, installed on the local machine in Current User\Personal\Certificates,
46+
and the thumbprint specified in the variables region below.
3847
.Example
39-
Get-MailboxOWAStorageProvider johndoe@contoso.com
40-
Get-Mailbox -RecipientTypeDetails UserMailbox -ResultSize unlimited | Get-MailboxOWAStorageProvider
48+
.\Get-MailboxOWAStorageProvider johndoe@contoso.com
49+
Get-Mailbox -RecipientTypeDetails UserMailbox -ResultSize unlimited | .\Get-MailboxOWAStorageProvider
4150
.Notes
42-
Version: 1.1
43-
Date: January 7, 2025
51+
Version: 1.2
52+
Date: July 30, 2025
4453
#>
4554

4655
[CmdletBinding()]
4756
param (
4857
[Parameter(Mandatory=$true,ValueFromPipelinebyPropertyName=$true,Position=0)][Alias('PrimarySMTPAddress')][string]$EmailAddress,
49-
[ValidateSet('Commercial','USGovGCC','China')][string]$Cloud = 'Commercial'
58+
[ValidateSet('Commercial','USGovGCC','China')][string]$Cloud = 'Commercial',
59+
[switch]$CertificateAuthentication
5060
)
5161

5262
begin {
5363
# Variables
54-
$tenantDomain = 'tenantname.onmicrosoft.com' #Default routing domain of the tenant
55-
$appId = '00000000-0000-0000-0000-000000000000' #Application ID of the app registration in Entra ID with EWS permission
64+
$tenantDomain = 'tenantname.onmicrosoft.com' # Default routing domain of the tenant
65+
$appId = '00000000-0000-0000-0000-000000000000' # Application ID of the app registration in Entra ID with EWS permission
66+
$certThumbprint = '' # Thumbprint of the certificate to use for authentication if CertificateAuthentication switch is used
5667
# End variables
5768

5869
if ($tenantDomain -like "tenantname*" -or $appId -like "00000000*") {
5970
Write-Error "The tenant domain or application ID has not been specified in the Variables section of the `"begin`" block."
6071
break
6172
}
73+
if ($CertificateAuthentication -and -not $certThumbprint) {
74+
Write-Error "The certificate thumbprint has not been specified in the Variables section of the `"begin`" block."
75+
break
76+
}
6277

6378
# Check for EWS API installed via NuGet
6479
# If already loaded, save time by reusing the loaded type
@@ -87,7 +102,7 @@ begin {
87102
}
88103

89104
# Import MSAL from Exchange Online module
90-
if (-not('Microsoft.Identity.Client.PublicClientApplicationBuilder' -as [type])) {
105+
if (-not('Microsoft.Identity.Client.ConfidentialClientApplicationBuilder' -as [type])) {
91106
Write-Verbose 'Loading the MSAL from EXO module installation...'
92107
if ($PSEdition -eq 'Core') {$folder = 'netCore'} else { $folder = 'NetFramework'}
93108
$ExoModule = Get-Module -Name ExchangeOnlineManagement -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1
@@ -103,17 +118,32 @@ begin {
103118
'USGovGCC' { $base = 'https://login.microsoftonline.com/';$ewsUrl = 'https://outlook.office365.com'}
104119
'China' { $base = 'https://login.partner.microsoftonline.cn/';$ewsUrl = 'https://partner.outlook.cn'}
105120
}
106-
# Build public client app and get access token
121+
# Build client app and get access token
107122
$replyUri = $base + 'common/oauth2/nativeclient'
108-
$authority = $base + $tenantDomain
109123
$capabilities = New-Object System.Collections.Generic.List[string]
110124
# cp1 indicates support for CAE, which will result in an access token that is valid for 29 hours
111125
# (This helps collecting from all mailboxes in a large org without needing to include support for token expiration.)
112126
$capabilities.Add('cp1')
113-
$publicClient = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($appId).WithRedirectUri($replyUri).WithAuthority($authority).WithClientCapabilities($capabilities).Build()
127+
if ($CertificateAuthentication) {
128+
# Use certificate authentication
129+
$cert = Get-Item "Cert:\CurrentUser\My\$certThumbprint"
130+
if (-not $cert) {
131+
Write-Error "The certificate with thumbprint $certThumbprint was not found in the CurrentUser\My store."
132+
break
133+
}
134+
$confidentialClient = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($appId).WithRedirectUri($replyUri).WithCertificate($cert).WithTenantId($tenantDomain).WithClientCapabilities($capabilities).Build()
135+
}
136+
else {
137+
# Use client secret authentication
138+
$ssAppSecret = (Get-Credential -Message "Enter the app registration's client secret in the password field." -UserName "EWS Application").Password
139+
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ssAppSecret)
140+
$appSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
141+
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
142+
$confidentialClient = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($appId).WithRedirectUri($replyUri).WithClientSecret($appSecret).WithTenantId($tenantDomain).WithClientCapabilities($capabilities).Build()
143+
}
114144
$scope = New-Object System.Collections.Generic.List[string]
115-
$scope.Add("$ewsUrl/EWS.AccessAsUser.All")
116-
$token = $publicClient.AcquireTokenInteractive($scope).ExecuteAsync().GetAwaiter().GetResult()
145+
$scope.Add("$ewsUrl/.default")
146+
$token = $confidentialClient.AcquireTokenForClient($scope).ExecuteAsync().GetAwaiter().GetResult()
117147
}
118148

119149
process {
@@ -154,5 +184,8 @@ process {
154184
}
155185
}
156186
end {
187+
if (-not $CertificateAuthentication) {
188+
Remove-Variable -Name appSecret,ssAppSecret -ErrorAction SilentlyContinue
189+
}
157190
Write-Progress -Activity 'Getting OWA storage provider settings' -Completed
158191
}

0 commit comments

Comments
 (0)