Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save zjorz/ad253c009b080c91494e2a64981aca6b to your computer and use it in GitHub Desktop.
Save zjorz/ad253c009b080c91494e2a64981aca6b to your computer and use it in GitHub Desktop.
Creating An Application In Entra ID And Exchange Online To Be Used As A "Proxy" To Send Emails From On-Premises PowerShell Scripts
$tenantFQDN = "<TENANT NAME>.ONMICROSOFT.COM" # <= CONFIGURE THIS!!!!!
$appRegDisplayName = "<APPLICATION DISPLAY NAME>" # <= CONFIGURE THIS!!!!!
$credentialType = "<CREDENTIAL TYPE>" # "Secret" OR "Certificate" <= CONFIGURE THIS!!!!!
$lifetimeSecretInDays = 365 # <= CONFIGURE THIS!!!!!
$certCERFilePath = "<CERTIFICATE CER FILE PATH>" # <= CONFIGURE THIS!!!!!
$mailboxMailAddress = "<MAIL ADDRESS OF MAILBOX TO ALLOW TO SEND MAIL FROM>" # <= CONFIGURE THIS!!!!!
Invoke-Command -ArgumentList $tenantFQDN,$appRegDisplayName,$credentialType,$lifetimeSecretInDays,$certCERFilePath,$mailboxMailAddress -ScriptBlock {
Param (
$tenantFQDN,
$appRegDisplayName,
$credentialType,
$lifetimeSecretInDays,
$certCERFilePath,
$mailboxMailAddress
)
<#
.SYNOPSIS
This PoSH Code Creates And Configures And Application In Both Entra ID And Exchange Online That Can Be Used By On-Premises PowerShell Scripts To Send E-mails
.DESCRIPTION
This PoSH code provides the following functions in the order listed:
- Authenticate and get an access token for MSFT Graph API
- Authenticate and get an access token for Office 365 Exchange Online
- Creation of a Application Registration in Entra ID WITHOUT any API permissions
- Creation of a Service Principal in Entra ID
- Addition of either a Client Secret or a certificate as client credentials
- Creation of a Service Principal in Office 365 Exchange Online referencing the Service Principal in Entra ID
- Creation of a management scope in Office 365 Exchange Online referencing scoping only the referenced mailbox to send mails from
- Creation of a management role assignement in Office 365 Exchange Online, granting Mail.Send to the Service Principal for the management scope
.NOTES
- By using this PowerShell code and its configuration, the PowerShell script will only be able to send mail from the mailbox referenced in the management scope
- The permissions in this scenario are configured on the Office 365 Exchange Online side using management scope and management role assignment. This is the
recommended way of doing this
- When configuring the permissions on the Entra ID side through API permission "Mail.Send" on the application registration , the application will be able to send
any mail from ANY mailbox in the organization. This is a security issues and very dangerous. With that in mind it IS NOT RECOMMENDED to do it like that!
- Requires => PSMSALNet Module (https://github.com/SCOMnewbie/PSMSALNet) to authenticate against Entra ID for all actions performed by this script/code
- Requires PowerShell v7.4 or higher
- For Entra ID Related Tasks/Actions..........: membership of the "Application Administrator", "Cloud Application Administrator" OR "Global Administrator" built-in role is required
- For Exchange Online Related Tasks/Actions...: membership of the "Exchange Administrator" OR "Global Administrator" built-in role is required
- The PowerShell code requires the permissions to execute all actions during the same run
-
#>
Write-Host ""
Write-Host "###############################################################################" -Foregroundcolor Yellow
Write-Host "### Creating A Send Mail Application In Entra ID ###" -Foregroundcolor Yellow
Write-Host "###############################################################################" -Foregroundcolor Yellow
################################################################
### FUNCTIONS ###
################################################################
# FUNCTION: Get The OpenID Configuration For The Specified Entra ID Tenant FQDN.
Function Get-MSFTApiOpenIDConfiguration {
<#
.SYNOPSIS
Get The OpenID Configuration For The Specified Entra ID Tenant FQDN.
.DESCRIPTION
Get The OpenID Configuration For The Specified Entra ID Tenant FQDN.
.PARAMETER tenantFQDN
The FQDN Of The Entra ID Tenant Being Targeted.
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE, ValueFromPipeline = $TRUE, ValueFromPipelineByPropertyName = $TRUE, HelpMessage = 'Please Specify The Entra ID Tenant FQDN')]
[ValidateNotNullOrEmpty()]
[String]$tenantFQDN
)
Begin {
# Specify The Tenant Specific Discovery Endpoint URL
$oidcConfigDiscoveryURL = "https://login.microsoftonline.com/$tenantFQDN/v2.0/.well-known/openid-configuration"
}
Process {
$oidcTenantConfig = Invoke-RestMethod -Uri $oidcConfigDiscoveryURL
Return $oidcTenantConfig
}
}
# FUNCTION: Get The Entra ID Tenant ID From The OIDC Configuration.
Function Get-MSFTTenantID {
<#
.SYNOPSIS
Get The Entra ID Tenant ID From The OIDC Configuration.
.DESCRIPTION
Get The Entra ID Tenant ID From The OIDC Configuration.
.PARAMETER oidcTenantConfig
The OIDC Configuration Of The Entra ID Tenant Being Targeted.
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[System.Management.Automation.PSObject]$oidcTenantConfig
)
Begin {
$tenantID = $null
}
Process {
$tenantID = $oidcTenantConfig.authorization_endpoint.Split("/")[3]
Return $tenantID
}
}
# FUNCTION: Create The Application Registration With Specific Details
Function New-GraphApiApplicationRegistration {
<#
.SYNOPSIS
Create The Application Registration With Specific Details
.DESCRIPTION
Create The Application Registration With Specific Details
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER appRegDisplayName
The Application Registration Display Name
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$appRegDisplayName
)
Begin {
$requestHeader = $null
$requestBody = $null
$response = $null
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Define The Request Body To Execute The Required Action
$requestBody = @{
displayName = $appRegDisplayName
description = "Application Used To Send E-mail sing On-Prem PowerShell Scripts"
signInAudience = "AzureADMyOrg"
servicePrincipalLockConfiguration = @{"isEnabled" = $true; "allProperties" = $true} # App Instance Property Lock
web = @{"redirectUris" = @("http://localhost")}
}
Write-Verbose "Request Body.............................: $($requestBody | Out-String)"
# Get The Response From The Microsoft Graph
Try {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/applications" -Method POST -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Created The Application Registration '$($response.displayName)' Object Id '$($response.id)' And With App Id '$($response.appId)'"
} Catch {
If ($_.ErrorDetails.Message) {
$errorMessage = $_.ErrorDetails.Message
} Else {
$errorMessage = $_
}
Throw $errorMessage
}
# Return
Return $response
}
}
# FUNCTION: Add A New Client Secret To An Existing Application Registration With A Specific Application Registration Object Id or A Specific Application Id.
Function New-GraphApiApplicationRegistrationClientSecret {
<#
.SYNOPSIS
Add A New Client Secret To An Existing Application Registration With A Specific Application Registration Object Id or A Specific Application Id.
.DESCRIPTION
Add A New Client Secret To An Existing Application Registration With A Specific Application Registration Object Id or A Specific Application Id.
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER applicationId
The Application ID Assigned And Configured On The Application Registration To Process
.PARAMETER applicationRegistrationObjectId
The Object Id Of The Application Registration To Process
.PARAMETER customLifetimeSecretInDays
The Custom Lifetime In Days For The New Secret To Be Added To The Existing Application Registration. When Nothing Is Specified The Default Will Be 1 Hour.
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $FALSE)]
[string]$applicationId,
[Parameter(Mandatory = $FALSE)]
[string]$applicationRegistrationObjectId,
[Parameter(Mandatory = $FALSE)]
[string]$customLifetimeSecretInDays
)
Begin {
# Making Sure The Correct Combination Of Parameters Are Used
If ([string]::IsNullOrEmpty($applicationId) -And [string]::IsNullOrEmpty($applicationRegistrationObjectId)) {
Write-Error "The Correct Combination Of Parameters Is Not Being Used"
Write-Host " * You Need To Either Specify '-applicationId' And Specify An Application Id Or '-applicationRegistrationObjectId' And Specify The Application Registration Object Id" -ForegroundColor red
Write-Host ""
BREAK
}
$internetIP = (Invoke-WebRequest -uri "http://ifconfig.me/ip").Content
$geoLocationInfo = Invoke-RestMethod -Method Get -Uri "http://ip-api.com/json/$internetIP"
$geoLocationTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($($geoLocationInfo.timezone))
$geoLocationDateTime = [System.TimeZoneInfo]::ConvertTimeFromUtc((Get-Date).ToUniversalTime(), $geoLocationTimeZone)
$defaultInHours = 1
$startDate = $geoLocationDateTime # Start Date/Time Is Generated Based On The Internet Connected GeoLocation, And Not The Start/Time Of The Local Computer!
If ([string]::IsNullOrEmpty($customLifetimeSecretInDays)) {
$endDate = $startDate.AddHours($defaultInHours)
} Else {
$endDate = $startDate.AddDays($customLifetimeSecretInDays)
}
$requestHeader = $null
$requestBody = $null
$response = $null
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Define The Request Body To Execute The Required Action
$requestBody = @{
passwordCredential = @{
customKeyIdentifier = $null;
displayName = "Client Secret (Generated $(Get-Date $startDate -Format 'yyyy-MM-dd HH:mm:ss'))";
startDateTime = $(Get-Date $startDate -Format "yyyy-MM-ddTHH:mm:ss.0Z")
endDateTime = $(Get-Date $endDate -Format "yyyy-MM-ddTHH:mm:ss.0Z")
}
}
Write-Verbose "Request Body.............................: $($requestBody | ConvertTo-Json)"
# Get The Response From The Microsoft Graph
Try {
If (-not [string]::IsNullOrEmpty($applicationId)) {
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/applications(appId='{$applicationId}')/addPassword" -Method POST -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Added A New Client Secret To The Application Registration With Application Id '$applicationId' With A Lifetime Of $(If ([string]::IsNullOrEmpty($customLifetimeSecretInDays)) {"$defaultInHours Hour(s)"} Else {"$customLifetimeSecretInDays Day(s)"})"
}
If (-not [string]::IsNullOrEmpty($applicationRegistrationObjectId)) {
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/applications/$applicationRegistrationObjectId/addPassword" -Method POST -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Added A New Client Secret To The Application Registration With Object Id '$applicationRegistrationObjectId' With A Lifetime Of $(If ([string]::IsNullOrEmpty($customLifetimeSecretInDays)) {"$defaultInHours Hour(s)"} Else {"$customLifetimeSecretInDays Day(s)"})"
}
} Catch {
If ($_.ErrorDetails.Message) {
$errorMessage = $_.ErrorDetails.Message
} Else {
$errorMessage = $_
If ($_ -like "*(401) Unauthorized*") {
Write-Verbose "Error: (401) Unauthorized - Not Authenticated, Or Access Token Has Expired"
}
If ($_ -like "*(404) Not Found*") {
Write-Verbose "Error: (404) Not Found - The Application Registration With $(If (-not [string]::IsNullOrEmpty($applicationId)) {"Application Id '$applicationId'"} ElseIf (-not [string]::IsNullOrEmpty($applicationRegistrationObjectId)) {"Object Id '$applicationRegistrationObjectId'"}) Does Not Exist In This Tenant"
}
}
Throw $errorMessage
}
# Return
Return $response
}
}
# FUNCTION: Add A Public Key Certificate To An Existing Application Registration With A Specific Application Registration Object Id or A Specific Application Id.
Function New-GraphApiApplicationRegistrationPublicKeyCertificate {
<#
.SYNOPSIS
Add A New Client Secret To An Existing Application Registration With A Specific Application Registration Object Id or A Specific Application Id.
.DESCRIPTION
Add A New Client Secret To An Existing Application Registration With A Specific Application Registration Object Id or A Specific Application Id.
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER applicationId
The Application ID Assigned And Configured On The Application Registration To Process
.PARAMETER applicationRegistrationObjectId
The Object Id Of The Application Registration To Process
.PARAMETER customKeyIdentifier
A 40-Character Binary Type That Can Be Used To Identify The Credential
(https://learn.microsoft.com/en-us/graph/api/resources/keycredential?view=graph-rest-1.0)
.PARAMETER displayName
The Friendly Name For The Key, With A Maximum Length Of 90 Characters. Longer Values Are Accepted But Shortened
(https://learn.microsoft.com/en-us/graph/api/resources/keycredential?view=graph-rest-1.0)
.PARAMETER endDateTime
The Date And Time At Which The Credential Expires (UTC time In ISO 8601 Format)
(https://learn.microsoft.com/en-us/graph/api/resources/keycredential?view=graph-rest-1.0)
.PARAMETER key
The Certificate's Raw Data In Byte Array Converted To Base64 String
(https://learn.microsoft.com/en-us/graph/api/resources/keycredential?view=graph-rest-1.0)
.PARAMETER keyId
The Unique Identifier (GUID) For The Key.
(https://learn.microsoft.com/en-us/graph/api/resources/keycredential?view=graph-rest-1.0)
.PARAMETER startDateTime
The Date And Time At Which The Credential Becomes Valid (UTC time In ISO 8601 Format)
(https://learn.microsoft.com/en-us/graph/api/resources/keycredential?view=graph-rest-1.0)
.PARAMETER type
The Type Of Key Credential: Symmetric, AsymmetricX509Cert.
(https://learn.microsoft.com/en-us/graph/api/resources/keycredential?view=graph-rest-1.0)
.PARAMETER usage
A String That Describes The Purpose For Which The Key Can Be Used: Verify
(https://learn.microsoft.com/en-us/graph/api/resources/keycredential?view=graph-rest-1.0)
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $FALSE)]
[string]$applicationId,
[Parameter(Mandatory = $FALSE)]
[string]$applicationRegistrationObjectId,
[Parameter(Mandatory = $FALSE)]
[string]$customKeyIdentifier,
[Parameter(Mandatory = $FALSE)]
[string]$displayName,
[Parameter(Mandatory = $FALSE)]
[string]$endDateTime,
[Parameter(Mandatory = $FALSE)]
[string]$key,
[Parameter(Mandatory = $FALSE)]
[string]$keyId,
[Parameter(Mandatory = $FALSE)]
[string]$startDateTime,
[Parameter(Mandatory = $FALSE)]
[string]$type,
[Parameter(Mandatory = $FALSE)]
[string]$usage
)
Begin {
# Making Sure The Correct Combination Of Parameters Are Used Targeting The Application
If ([string]::IsNullOrEmpty($applicationId) -And [string]::IsNullOrEmpty($applicationRegistrationObjectId)) {
Write-Error "The Correct Combination Of Parameters Is Not Being Used"
Write-Host " * You Need To Either Specify '-applicationId' And Specify An Application Id Or '-applicationRegistrationObjectId' And Specify The Application Registration Object Id" -ForegroundColor red
Write-Host ""
BREAK
}
# Making Sure The Correct Combination Of Parameters Are Used To Define A Key Credential
If ([string]::IsNullOrEmpty($customKeyIdentifier) -And [string]::IsNullOrEmpty($displayName) -And [string]::IsNullOrEmpty($endDateTime) -And [string]::IsNullOrEmpty($key) -And [string]::IsNullOrEmpty($keyId) -And [string]::IsNullOrEmpty($startDateTime) -And [string]::IsNullOrEmpty($type) -And [string]::IsNullOrEmpty($usage)) {
Write-Error "The Correct Combination Of Parameters Is Not Being Used"
Write-Host " * You Need To Specify '-customKeyIdentifier' And '-displayName' And '-endDateTime' And '-key' And '-keyId' And '-startDateTime' And '-type' And '-usage' And The Corresponding Values" -ForegroundColor red
Write-Host ""
BREAK
}
$requestHeader = $null
$requestBody = $null
$response = $null
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Define The Request Body To Execute The Required Action
$requestBody = @(
[PSCustomObject]@{
keyCredentials = @(
[PSCustomObject]@{
customKeyIdentifier = $customKeyIdentifier
type = $type
usage = $usage
displayName = $displayName
startDateTime = $startDateTime
endDateTime = $endDateTime
key = $key
}
)
}
)
Write-Verbose "Request Body.............................: $($requestBody | ConvertTo-Json)"
# Get The Response From The Microsoft Graph
Try {
If (-not [string]::IsNullOrEmpty($applicationId)) {
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/applications(appId='{$applicationId}')" -Method PATCH -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Added A New Public Key Certificate To The Application Registration With Application Id '$applicationId'"
}
If (-not [string]::IsNullOrEmpty($applicationRegistrationObjectId)) {
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/applications/$applicationRegistrationObjectId" -Method PATCH -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Added A New Public Key Certificate To The Application Registration With Object Id '$applicationRegistrationObjectId'"
}
} Catch {
If ($_.ErrorDetails.Message) {
$errorMessage = $_.ErrorDetails.Message
} Else {
$errorMessage = $_
If ($_ -like "*(401) Unauthorized*") {
Write-Verbose "Error: (401) Unauthorized - Not Authenticated, Or Access Token Has Expired"
}
If ($_ -like "*(404) Not Found*") {
Write-Verbose "Error: (404) Not Found - The Application Registration With $(If (-not [string]::IsNullOrEmpty($applicationId)) {"Application Id '$applicationId'"} ElseIf (-not [string]::IsNullOrEmpty($applicationRegistrationObjectId)) {"Object Id '$applicationRegistrationObjectId'"}) Does Not Exist In This Tenant"
}
}
Throw $errorMessage
}
# Return
Return $response
}
}
# FUNCTION: Get The Service Principal With A Specific Application ID Or Specific Service Principal Display Name
Function Get-GraphApiServicePrincipal {
<#
.SYNOPSIS
Get The Service Principal With A Specific Application ID Or Specific Service Principal Display Name
.DESCRIPTION
Get The Service Principal With A Specific Application ID Or Specific Service Principal Display Name
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER applicationId
The Application ID Assigned And Configured On The Service Principal To Process
.PARAMETER svcPrincDisplayName
The Application Display Name Assigned And Configured On The Service Principal To Process
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $FALSE)]
[string]$applicationId,
[Parameter(Mandatory = $FALSE)]
[string]$svcPrincDisplayName
)
Begin {
# Making Sure The Correct Combination Of Parameters Are Used
If ([string]::IsNullOrEmpty($applicationId) -And [string]::IsNullOrEmpty($svcPrincDisplayName)) {
Write-Error "The Correct Combination Of Parameters Is Not Being Used"
Write-Host " * You Need To Either Specify '-applicationId' And Specify An Application Id Or '-svcPrincDisplayName' And Specify The Service Principal Display Name" -ForegroundColor red
Write-Host ""
BREAK
}
$requestHeader = $null
$response = $null
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Get The Response From The Microsoft Graph
Try {
If (-not [string]::IsNullOrEmpty($applicationId)) {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=appId+eq+'$applicationId'" -Method GET -Headers $requestHeader -ErrorAction Stop
If ([string]::IsNullOrEmpty($($response.value | Out-String))) {
Write-Verbose "Unable To Find The Service Principal With App Id '$applicationId' Because It Does Not Exist In This Tenant"
} Else {
Write-Verbose "Found The Service Principal '$($response.value.displayName)' With Object Id '$($response.value.id)' And App Id '$($response.value.appId)'"
}
}
If (-not [string]::IsNullOrEmpty($svcPrincDisplayName)) {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=displayName+eq+'$svcPrincDisplayName'" -Method GET -Headers $requestHeader -ErrorAction Stop
If ([string]::IsNullOrEmpty($($response.value | Out-String))) {
Write-Verbose "Unable To Find The Service Principal With Display Name '$svcPrincDisplayName' Because It Does Not Exist In This Tenant"
} Else {
Write-Verbose "Found The Service Principal '$($response.value.displayName)' With Object Id '$($response.value.id)' And App Id '$($response.value.appId)'"
}
}
} Catch {
If ($_.ErrorDetails.Message) {
$errorMessage = $_.ErrorDetails.Message
} Else {
$errorMessage = $_
If ($_ -like "*(401) Unauthorized*") {
Write-Verbose "Error: (401) Unauthorized - Not Authenticated, Or Access Token Has Expired"
}
}
Throw $errorMessage
}
Return $response.value
}
}
# FUNCTION: Create The Service Principal With A Specific Application ID
Function New-GraphApiServicePrincipal {
<#
.SYNOPSIS
Create The Service Principal With A Specific Application ID
.DESCRIPTION
Create The Service Principal With A Specific Application ID
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER applicationId
The Application ID Assigned And Configured On The Service Principal
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$applicationId
)
Begin {
$requestHeader = $null
$requestBody = $null
$response = $null
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Define The Request Body To Execute The Required Action
$requestBody = @{
appId = $applicationId
}
Write-Verbose "Request Body.............................: $($requestBody | Out-String)"
# Get The Response From The Microsoft Graph
Try {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/servicePrincipals" -Method POST -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Created The Service Principal '$($response.displayName)' With App Id '$($response.appId)' And Object Id '$($response.id)'"
} Catch {
If ($_.ErrorDetails.Message) {
$errorMessage = $_.ErrorDetails.Message
} Else {
$errorMessage = $_
If ($_ -like "*(400) Bad Request*") {
Write-Verbose "Error: (400) Bad Request - The Application Registration With Application Id '$applicationId' Does Not Exist In This Tenant"
}
If ($_ -like "*(401) Unauthorized*") {
Write-Verbose "Error: (401) Unauthorized - Not Authenticated, Or Access Token Has Expired"
}
If ($_ -like "*(409) Conflict*") {
Write-Verbose "Error: (409) Conflict - The Service Principal With Application Id '$applicationId' Already Exists In This Tenant"
}
}
Throw $errorMessage
}
# Return
Return $response
}
}
# FUNCTION: Create The Service Principal With A Specific Application ID In Office 365 Exchange Online
Function New-EXOApiServicePrincipal {
<#
.SYNOPSIS
Create The Service Principal With A Specific Application ID In Office 365 Exchange Online
.DESCRIPTION
Create The Service Principal With A Specific Application ID In Office 365 Exchange Online
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER applicationName
The Application Name Assigned And Configured On The Service Principal In Entra ID
.PARAMETER applicationID
The Application ID Assigned And Configured On The Service Principal In Entra ID
.PARAMETER servicePrincipalObjectID
The Object ID From The Service Principal In Entra ID
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$applicationName,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$applicationID,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$servicePrincipalObjectID
)
Begin {
$requestHeader = $null
$requestBody = $null
$response = $null
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Define The Request Body To Execute The Required Action
$requestBody = [PSCustomObject]@{
CmdletInput = [PSCustomObject]@{
CmdletName = "New-ServicePrincipal"
Parameters = [PSCustomObject]@{
ObjectId = $servicePrincipalObjectId
DisplayName = $applicationName
AppId = $applicationId
}
}
}
Write-Verbose "Request Body.............................: $($requestBody | Out-String)"
# Get The Response From The Office 365 Exchange Online API
Try {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://outlook.office365.com/adminapi/beta/$tenantID/InvokeCommand" -Method POST -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Created The Exchange Online Service Principal '$applicationName' With App Id '$applicationId' And Object Id '$servicePrincipalObjectId'"
} Catch {
$err = $_.ErrorDetails.Message | ConvertFrom-Json
$errorMessage = $err.error.details.message
Throw $errorMessage
}
# Return
Return $response.value
}
}
# FUNCTION: Get The Mailbox Details In Office 365 Exchange Online
Function Get-EXOApiMailboxDetails {
<#
.SYNOPSIS
Get The Mailbox Details In Office 365 Exchange Online
.DESCRIPTION
Get The Mailbox Details In Office 365 Exchange Online
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER emailAddress
The E-mail Address Of The Mailbox To Get The Details For
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$emailAddress
)
Begin {
$requestHeader = $null
$requestBody = $null
$response = $null
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Define The Request Body To Execute The Required Action
$requestBody = [PSCustomObject]@{
CmdletInput = [PSCustomObject]@{
CmdletName = "Get-Mailbox"
Parameters = [PSCustomObject]@{
Identity = $mailboxMailAddress
}
}
}
Write-Verbose "Request Body.............................: $($requestBody | Out-String)"
# Get The Response From The Office 365 Exchange Online API
Try {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://outlook.office365.com/adminapi/beta/$tenantID/InvokeCommand" -Method POST -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Retrieved The Details From The Mailbox With E-mail Address '$mailboxMailAddress'"
} Catch {
$err = $_.ErrorDetails.Message | ConvertFrom-Json
$errorMessage = $err.error.details.message
Throw $errorMessage
}
# Return
Return $response.value
}
}
# FUNCTION: Create The Management Scope For A Specific Mailbox In Office 365 Exchange Online
Function New-EXOApiManagementScopeForMailbox {
<#
.SYNOPSIS
Create The Management Scope For A Specific Mailbox In Office 365 Exchange Online
.DESCRIPTION
Create The Management Scope For A Specific Mailbox In Office 365 Exchange Online
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER applicationName
The Application Name Assigned And Configured On The Service Principal In Entra ID
.PARAMETER mailboxGuid
The Guid Of The Mailbox To Create The Management Scope For
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$applicationName,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$mailboxGuid
)
Begin {
$requestHeader = $null
$requestBody = $null
$response = $null
If ("RBAC-SCOPE-APP_$applicationName".Length -gt 64) {
$managementScopeName = "RBAC-SCOPE-APP_$applicationName".Substring(0,64)
} Else{
$managementScopeName = "RBAC-SCOPE-APP_$applicationName"
}
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Define The Request Body To Execute The Required Action
$requestBody = [PSCustomObject]@{
CmdletInput = [PSCustomObject]@{
CmdletName = "New-ManagementScope"
Parameters = [PSCustomObject]@{
Name = $managementScopeName
RecipientRestrictionFilter = "Guid -eq '$mailboxGuid'"
}
}
}
Write-Verbose "Request Body.............................: $($requestBody | Out-String)"
# Get The Response From The Office 365 Exchange Online API
Try {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://outlook.office365.com/adminapi/beta/$tenantID/InvokeCommand" -Method POST -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Created The Exchange Online Management Scope '$($response.value.Name)' With Filter '$($response.value.Filter)'"
} Catch {
$err = $_.ErrorDetails.Message | ConvertFrom-Json
$errorMessage = $err.error.details.message
Throw $errorMessage
}
# Return
Return $response.value
}
}
# FUNCTION: Create The Management Role Assignment For The RBAC Role And Scope In Office 365 Exchange Online
Function New-EXOApiManagementRoleAssignment {
<#
.SYNOPSIS
Create The Management Role Assignment For The RBAC Role And Scope In Office 365 Exchange Online
.DESCRIPTION
Create The Management Role Assignment For The RBAC Role And Scope In Office 365 Exchange Online
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER applicationName
The Application Name Assigned And Configured On The Service Principal In Entra ID
.PARAMETER applicationID
The Application ID Assigned And Configured On The Service Principal In Entra ID
.PARAMETER exoAppRole
The Office 365 Exchange Online Application Role (https://learn.microsoft.com/en-us/exchange/permissions-exo/application-rbac#supported-application-roles)
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$applicationName,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$applicationID,
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$exoAppRole
)
Begin {
$requestHeader = $null
$requestBody = $null
$response = $null
If ("RBAC-ASSIGN-APP_$applicationName".Length -gt 64) {
$managementRoleAssignmentName = "RBAC-ASSIGN-APP_$applicationName".Substring(0,64)
} Else{
$managementRoleAssignmentName = "RBAC-ASSIGN-APP_$applicationName"
}
If ("RBAC-SCOPE-APP_$applicationName".Length -gt 64) {
$managementScopeName = "RBAC-SCOPE-APP_$applicationName".Substring(0,64)
} Else{
$managementScopeName = "RBAC-SCOPE-APP_$applicationName"
}
}
Process {
# Define The Request Header To Execute The Required Action
$requestHeader = @{
Authorization = "Bearer $accessToken"
}
Write-Verbose "Request Header...........................: $($requestHeader | Out-String)"
# Define The Request Body To Execute The Required Action
$requestBody = [PSCustomObject]@{
CmdletInput = [PSCustomObject]@{
CmdletName = "New-ManagementRoleAssignment"
Parameters = [PSCustomObject]@{
Name = $managementRoleAssignmentName
App = $applicationID
Role = $exoAppRole
CustomResourceScope = $managementScopeName
}
}
}
Write-Verbose "Request Body.............................: $($requestBody | Out-String)"
# Get The Response From The Office 365 Exchange Online API
Try {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://outlook.office365.com/adminapi/beta/$tenantID/InvokeCommand" -Method POST -Headers $requestHeader -Body $($requestBody | ConvertTo-Json) -ContentType "application/json; charset=UTF-8" -ErrorAction Stop
Write-Verbose "Created The Exchange Online Management Role Assignment '$($response.value.Name)' For The Application '$applicationName' With Scope Definition '$($response.value.CustomResourceScope)' And Exo Role '$($response.value.Role)'"
} Catch {
$err = $_.ErrorDetails.Message | ConvertFrom-Json
$errorMessage = $err.error.details.message
Throw $errorMessage
}
# Return
Return $response.value
}
}
Clear-Host
################################################################
### CONSTANTS ###
################################################################
# If A Certificate Must Be Added To The Application, Let's Check The File Path Of The CER Has Been Specified
If (-not [String]::IsNullOrEmpty($certCERFilePath) -And $certCERFilePath -ne "<CERTIFICATE CER FILE PATH>") {
If (!(Test-Path $certCERFilePath)) {
Write-Host ""
Write-Host "The CER File '$certCERFilePath' DOES NOT Exist..." -Foregroundcolor Red
Write-Host ""
Write-Host " => Aborting Script..." -ForegroundColor Red
Write-Host ""
BREAK
}
}
################################################################
### CHECKING REQUIREMENTS ###
################################################################
# Due To The Use Of The PowerShell Module "PSMSALNet" For Authentication Against Entra ID, The Minimum PowerShell Version Is 7.4, So Let's Check If That Requirement Is Met.
If ($psVersionTable.PSVersion -lt [version]"7.4") {
Write-Host ""
Write-Host "This Script Depends On The PowerShell Module 'PSMSALNet' Which Has A minum Requirement Of PowerShell Version 7.4." -Foregroundcolor Red
Write-Host ""
Write-Host "Please Visit 'https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4'..." -Foregroundcolor Red
Write-Host ""
Write-Host ""
Write-Host " => Aborting Script..." -ForegroundColor Red
Write-Host ""
BREAK
}
# The Module "PSMSALNet" Is Used For Authentication Against Entra ID, So Let's Check If That Is Installed Or Not. If It Is Load The Most Recent Module
$moduleName = "PSMSALNet"
$psMSALNetModule = Get-Module -Name $moduleName -ListAvailable
If ([String]::IsNullOrEmpty($psMSALNetModule)) {
Write-Host ""
Write-Host "The $moduleName PowerShell Module IS NOT Installed" -ForegroundColor Red
Write-Host ""
Write-Host " => The $moduleName PowerShell Module Can Be Installed From An Elevated PowerShell Command Prompt By Running:" -ForegroundColor Red
Write-Host " - 'Install-Module $moduleName'" -ForegroundColor Red
Write-Host ""
Write-Host " => Aborting Script..." -ForegroundColor Red
Write-Host ""
BREAK
} Else {
$psMSALNetModuleVersionInstalled = $psMSALNetModule.Version
$psMSALNetModuleVersionAvailable = (Find-Module -Name $moduleName).Version
If ([version]$psMSALNetModuleVersionAvailable -gt $psMSALNetModuleVersionInstalled) {
Write-Host ""
Write-Host "A Newer Version Of The '$moduleName' PowerShell Module Is Available For Download/Install..." -ForegroundColor Red
Write-Host ""
}
}
If ($psMSALNetModule.count -gt 1) {
$latestVersionPSMSALNetModule = ($psMSALNetModule | Select-Object version | Sort-Object)[-1]
$psMSALNetModule = $psMSALNetModule | Where-Object {$_.version -eq $latestVersionMSFTGraphModule.version}
}
Try {
Import-Module $psMSALNetModule
} Catch {
$_
}
################################################################
### MAIN CODE ###
################################################################
# Using The Tenant FQDN, Determine The Tenant ID
$oidcConfig = Get-MSFTApiOpenIDConfiguration -tenantFQDN $tenantFQDN
$tenantID = Get-MSFTTenantID -oidcTenantConfig $oidcConfig
If (!$([guid]::TryParse($tenantID, $([ref][guid]::Empty)))) {
Write-Host ""
Write-Host "Specified Tenant '$tenantFQDN' DOES NOT Exist..." -ForegroundColor Red
Write-Host ""
Write-Host " => Aborting Script..." -ForegroundColor Red
Write-Host ""
BREAK
}
Write-Host ""
Write-Host "Tenant FQDN.........................: $tenantFQDN"
Write-Host "Tenant ID...........................: $tenantID"
Write-Host ""
# Authenticate And Get An Access Token For MSFT Graph API To Execute Actions In Entra ID
$clientId1 = "14d82eec-204b-4c2f-b7e8-296a70dab67e" # "Microsoft Graph Command Line Tools"
Write-Host ""
Write-Host "### Getting Access Token For Client App 'Microsoft Graph Command Line Tools' And The MSFT Graph API Resource In Entra ID Tenant '$tenantFQDN'..." -ForegroundColor Cyan
Write-Host ""
Try {
$entraTokenParametersHT1 = @{
ClientId = $clientId1
TenantId = $tenantID
RedirectUri = "http://localhost"
Resource = "GraphAPI"
Permissions = @("Directory.AccessAsUser.All")
verbose = $false
ErrorAction = "Stop"
}
$entraTokenMSGraphAPI = Get-EntraToken -PublicAuthorizationCodeFlow @entraTokenParametersHT1
} Catch {
Write-Host ""
Write-Host " => Getting Access Token For Client App 'Microsoft Graph Command Line Tools' And The MSFT Graph API Resource In Entra ID Tenant '$tenantFQDN' Failed..." -ForegroundColor Red
Write-Host ""
Write-Host " - Exception Type......: $($_.Exception.GetType().FullName)" -ForegroundColor Red
Write-Host " - Exception Message...: $($_.Exception.Message)" -ForegroundColor Red
Write-Host " - Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" -ForegroundColor Red
Write-Host ""
Write-Host " => Aborting Script..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
$accessTokenMSGraphAPI = $entraTokenMSGraphAPI.AccessToken
# Authenticate And Get An Access Token For Office 365 Exchange Online To Execute Actions In Exchange Online
$clientId3 = "fb78d390-0c51-40cd-8e17-fdbfab77341b" # "Microsoft Exchange REST API Based Powershell"
Write-Host ""
Write-Host "### Getting Access Token For Client App 'Microsoft Exchange REST API Based Powershell' And The Office 365 Exchange Online Resource In Entra ID Tenant '$tenantFQDN'..." -ForegroundColor Cyan
Write-Host ""
Try {
$entraTokenParametersHT3 = @{
ClientId = $clientId3
TenantId = $tenantID
RedirectUri = "http://localhost"
Resource = "Custom"
CustomResource = "00000002-0000-0ff1-ce00-000000000000" # Resource ID For Office 365 Exchange Online (=> https://outlook.office365.com)
Permissions = @("AdminApi.AccessAsUser.All","FfoPowerShell.AccessAsUser.All","RemotePowerShell.AccessAsUser.All")
verbose = $false
ErrorAction = "Stop"
}
$entraTokenO365EXO = Get-EntraToken -PublicAuthorizationCodeFlow @entraTokenParametersHT3
} Catch {
Write-Host ""
Write-Host " => Getting Access Token For Client App 'Microsoft Exchange REST API Based Powershell' And The Office 365 Exchange Online Resource Resource In Entra ID Tenant '$tenantFQDN' Failed..." -ForegroundColor Red
Write-Host ""
Write-Host " - Exception Type......: $($_.Exception.GetType().FullName)" -ForegroundColor Red
Write-Host " - Exception Message...: $($_.Exception.Message)" -ForegroundColor Red
Write-Host " - Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" -ForegroundColor Red
Write-Host ""
Write-Host " => Aborting Script..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
$accessTokenO365EXO = $entraTokenO365EXO.AccessToken
Write-Host ""
Write-Host "### Creating And Configuring The '$appRegDisplayName' App In Entra ID..." -ForegroundColor Cyan
Write-Host ""
# Creating The Application Registration In Entra ID
$eidAppReg = New-GraphApiApplicationRegistration -accessToken $accessTokenMSGraphAPI -appRegDisplayName $appRegDisplayName
If (-not [String]::IsNullOrEmpty($eidAppReg)) {
$eidAppRegObjectID = $eidAppReg.Id
$eidAppRegApplicationID = $eidAppReg.AppId
Write-Host " => Entra ID: Application Registration '$appRegDisplayName' Has Been Created Successfully..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Entra ID: Application Registration '$appRegDisplayName' Failed To Be Created..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
# Creating The Service Principal In Entra ID
Write-Host " => Waiting A Few Seconds Before Continuing..." -ForegroundColor Yellow
Write-Host ""
Start-Sleep -s 10
$eidSvcPrinc = New-GraphApiServicePrincipal -accessToken $accessTokenMSGraphAPI -applicationId $eidAppRegApplicationID
If (-not [String]::IsNullOrEmpty($eidSvcPrinc)) {
$eidSvcPrincObjectID = $eidSvcPrinc.Id
$eidSvcPrincApplicationID = $eidSvcPrinc.AppId
Write-Host " => Entra ID: Service Principal '$appRegDisplayName' Has Been Created Successfully..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Entra ID: Service Principal '$appRegDisplayName' Failed To Be Created..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
# Creating A Client Secret For The App In Entra ID
If ($credentialType.ToUpper() -eq "SECRET") {
$responseNewAppRegClntScrt = New-GraphApiApplicationRegistrationClientSecret -accessToken $accessTokenMSGraphAPI -applicationId $eidAppRegApplicationID -customLifetimeSecretInDays $lifetimeSecretInDays
If ($responseNewAppRegClntScrt.StatusCode -eq 200) {
$eidClientSecret = $responseNewAppRegClntScrt.Content | ConvertFrom-Json
Write-Host " => Entra ID: Client Secret For Application Registration '$appRegDisplayName' Has Been Created..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Entra ID: Client Secret For Application Registration '$appRegDisplayName' Failed To Be Created..." -ForegroundColor Red
Write-Host ""
}
}
# Uploading A Certificate For The App In Entra ID
If ($credentialType.ToUpper() -eq "CERTIFICATE") {
# Import The CER File Into A Certificate Object And Get The Base Details
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($certCERFilePath)
$certFriendlyName = $cert.FriendlyName
$certSubjectName = $cert.Subject
$certThumbprint = $cert.Thumbprint
$certNotBefore = $cert.NotBefore
$certNotAfter = $cert.NotAfter
# Get The Certificate RAWData
$certRawData = $cert.GetRawCertData()
# Base64 Encode The Public Key
$certCERBase64 = [System.Convert]::ToBase64String($certRawData)
# Get The Custom Key Identifier
$certHash = $cert.GetCertHash()
$certCustomKeyIdentifier = [System.Convert]::ToBase64String($certHash)
# Get The NotBefore And NotAfter Dates Of The Certificate In The ISO8601Format
$certNotBeforeISO8601Format = (Get-Date $certNotBefore).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$certNotAfterISO8601Format = (Get-Date $certNotAfter).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
# Define The Key ID
$keyID = (New-Guid).Guid
$responseNewAppRegCertificate = New-GraphApiApplicationRegistrationPublicKeyCertificate -accessToken $accessTokenMSGraphAPI -applicationId $eidAppRegApplicationID -customKeyIdentifier $certCustomKeyIdentifier -keyId $keyID -displayName $certSubjectName -startDateTime $certNotBeforeISO8601Format -endDateTime $certNotAfterISO8601Format -type "AsymmetricX509Cert" -usage "Verify" -key $certCERBase64
If ($responseNewAppRegCertificate.StatusCode -eq 204) {
Write-Host " => Entra ID: Certificate For Application Registration '$appRegDisplayName' Was Uploaded Successfully..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Entra ID: Certificate For Application Registration '$appRegDisplayName' Failed To Be Uploaded..." -ForegroundColor Red
Write-Host ""
}
}
# Creating The Service Principal In Office 365 Exchange Online
Write-Host " => Waiting A Few Seconds Before Continuing..." -ForegroundColor Yellow
Write-Host ""
Start-Sleep -s 30
$exoSvcPrinc = New-EXOApiServicePrincipal -accessToken $accessTokenO365EXO -applicationName $appRegDisplayName -applicationID $eidAppRegApplicationID -servicePrincipalObjectID $eidSvcPrincObjectID
If (-not [String]::IsNullOrEmpty($exoSvcPrinc)) {
$exoSvcPrincName = $exoSvcPrinc.DisplayName
$exoSvcPrincGuid = $exoSvcPrinc.Guid
Write-Host " => Office 365 Exchange Online: Service Principal '$exoSvcPrincName' Has Been Created Successfully..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Office 365 Exchange Online: Service Principal For '$appRegDisplayName' Failed To Be Created..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
# Getting The Mailbox Details For Which The Application Requires Access
$mbxDetails = Get-EXOApiMailboxDetails -accessToken $accessTokenO365EXO -emailAddress $mailboxMailAddress
If (-not [String]::IsNullOrEmpty($mbxDetails)) {
$mbxDisplayName = $mbxDetails.DisplayName
$mbxUserPrincipalName = $mbxDetails.UserPrincipalName
$mbxGuid = $mbxDetails.Guid
Write-Host " => Retrieve The Mailbox Details For The Mailbox '$mbxDisplayName' With E-mail Address '$mbxUserPrincipalName'..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Failed To Retrieve The Mailbox Details For The Mailbox With E-mail Address '$mailboxMailAddress'..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
# Creating The Management Scope For The Mailbox In Office 365 Exchange Online
$exoMgmtScope = New-EXOApiManagementScopeForMailbox -accessToken $accessTokenO365EXO -applicationName $appRegDisplayName -mailboxGuid $mbxGuid
If (-not [String]::IsNullOrEmpty($exoMgmtScope)) {
$exoMgmtScopeName = $exoMgmtScope.Name
Write-Host " => Office 365 Exchange Online: Management Scope '$exoMgmtScopeName' Has Been Created Successfully..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Office 365 Exchange Online: Management Scope For '$appRegDisplayName' Failed To Be Created..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
# Creating The Management Role Assignment For The Mailbox In Office 365 Exchange Online
$exoMgmtScopeAssignment = New-EXOApiManagementRoleAssignment -accessToken $accessTokenO365EXO -applicationName $appRegDisplayName -applicationID $eidAppRegApplicationID -exoAppRole "Application Mail.Send"
If (-not [String]::IsNullOrEmpty($exoMgmtScopeAssignment)) {
$exoMgmtRoleAssignmentName = $exoMgmtScopeAssignment.Name
Write-Host " => Office 365 Exchange Online: Management Role Assignment '$exoMgmtRoleAssignmentName' Has Been Created Successfully..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Office 365 Exchange Online: Management Role Assignment For '$appRegDisplayName' Failed To Be Created..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
# Displaying The Required Data For The App In Entra ID
Write-Host "### Displaying The Required Data For The App In Entra ID..." -ForegroundColor Cyan
Write-Host ""
Write-Host " => Tenant FQDN....................: '$tenantFQDN' (Required For 'Reset-KrbTgt-Password-For-RWDCs-And-RODCs.xml')" -ForegroundColor Yellow
Write-Host " => Tenant ID......................: '$tenantID'" -ForegroundColor Yellow
Write-Host " => Application Name...............: '$appRegDisplayName'" -ForegroundColor Yellow
Write-Host " => Service Principal Object ID....: '$eidSvcPrincObjectID'" -ForegroundColor Yellow
Write-Host " => App Registration Object ID.....: '$eidAppRegObjectID'" -ForegroundColor Yellow
Write-Host " => App Registration App ID........: '$eidAppRegApplicationID' (Required For 'Reset-KrbTgt-Password-For-RWDCs-And-RODCs.xml')" -ForegroundColor Yellow
Write-Host " => Service Principal Guid (EXO)...: '$exoSvcPrincGuid'" -ForegroundColor Yellow
Write-Host " => Mailbox Name...................: '$mbxDisplayName'" -ForegroundColor Yellow
Write-Host " => Mailbox Mail Address...........: '$mailboxMailAddress'" -ForegroundColor Yellow
Write-Host " => Mailbox Guid...................: '$mbxGuid'" -ForegroundColor Yellow
Write-Host " => Management Scope Name..........: '$exoMgmtScopeName'" -ForegroundColor Yellow
Write-Host " => Mgmt Role Assignment Name......: '$exoMgmtRoleAssignmentName'" -ForegroundColor Yellow
If ($credentialType.ToUpper() -eq "SECRET") {
Write-Host " => Credential Type................: SECRET" -ForegroundColor Yellow
Write-Host " => Credential Key ID..............: '$($eidClientSecret.keyId)' (Required For 'Reset-KrbTgt-Password-For-RWDCs-And-RODCs.xml')" -ForegroundColor Yellow
Write-Host " => Client Secret..................: '$($eidClientSecret.SecretText)' (Required For 'Reset-KrbTgt-Password-For-RWDCs-And-RODCs.xml')" -ForegroundColor Yellow
Write-Host " => Lifetime Client Secret.........: '$lifetimeSecretInDays Days'" -ForegroundColor Yellow
Write-Host " => Start Date.....................: '$($eidClientSecret.StartDateTime)'" -ForegroundColor Yellow
Write-Host " => End Date.......................: '$($eidClientSecret.EndDateTime)'" -ForegroundColor Yellow
Write-Host ""
Write-Host "WARNING: Make Sure To Store The Client Secret Specified Above In A Secure Location Like E.g. A Password Vault And Restrict Access To That Credential!!!" -ForegroundColor Red
}
Write-Host ""
If ($credentialType.ToUpper() -eq "CERTIFICATE") {
Write-Host " => Credential Type................: CERTIFICATE" -ForegroundColor Yellow
Write-Host " => Credential Display Name........: '$certSubjectName' (Required For 'Reset-KrbTgt-Password-For-RWDCs-And-RODCs.xml')" -ForegroundColor Yellow
Write-Host " => Friendly Name..................: '$certFriendlyName'" -ForegroundColor Yellow
Write-Host " => Subject Name...................: '$certSubjectName'" -ForegroundColor Yellow
Write-Host " => Thumbprint.....................: '$certThumbprint'" -ForegroundColor Yellow
Write-Host " => 'Not Before' Date (yyyy-MM-dd).: '$(Get-Date $certNotBefore -Format "yyyy-MM-dd HH:mm:ss")'" -ForegroundColor Yellow
Write-Host " => 'Not After' Date (yyyy-MM-dd)..: '$(Get-Date $certNotAfter -Format "yyyy-MM-dd HH:mm:ss")'" -ForegroundColor Yellow
}
Write-Host ""
###
# THE END OF THE SCRIPT
###
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
$_
}
Write-Host ""
Write-Host " +++ DONE +++ " -ForegroundColor Cyan
Write-Host ""
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment