Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save zjorz/b5849c77ef68aefbaab5807786b07ff3 to your computer and use it in GitHub Desktop.
Save zjorz/b5849c77ef68aefbaab5807786b07ff3 to your computer and use it in GitHub Desktop.
Deleting A Specific Application From Entra ID And From Exchange Online
$tenantFQDN = "<TENANT NAME>.ONMICROSOFT.COM" # <= CONFIGURE THIS!!!!!
$applicationName = "<APPLICATION DISPLAY NAME>" # <= CONFIGURE THIS!!!!!
Invoke-Command -ArgumentList $tenantFQDN,$applicationName -ScriptBlock {
Param (
$tenantFQDN,
$applicationName
)
<#
.SYNOPSIS
This PoSH Code The Application In Both Entra ID And Exchange Online That Could 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
- Deletion of a targeted Management Role Assignment in Office 365 Exchange Online, if the Office 365 Exchange Online Service Principal, linked to the Entra ID Service Principal, is assigned to it
- Deletion of a targeted Management Scope in Office 365 Exchange Online, if it has been linked to the targeted Management Role Assignment, and it is the only Management Role Assignment using the it
- Deletion of a targeted Service Principal in Office 365 Exchange Online, that is linked to Entra ID Service Principal
- Deletion of a targeted Service Principal in Entra ID, if it exists
- Deletion of a targeted Application Registration in Entra ID, if it exists
.NOTES
- 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
- Through Exchange Online PoSH CMDlets
- Install-Module -Name ExchangeOnlineManagement
- Import-module ExchangeOnlineManagement
- Connect-ExchangeOnline -Organization XXXXXX.ONMICROSOFT.COM
- Get-ManagementRoleAssignment | Sort-Object -Property Name
- Get-ManagementScope
- Get-ServicePrincipal
#>
Write-Host ""
Write-Host "###############################################################################" -Foregroundcolor Yellow
Write-Host "### Deleting 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: Get All Management Role Assignments In Office 365 Exchange Online
Function Get-EXOApiManagementRoleAssignments {
<#
.SYNOPSIS
Get All Management Role Assignments In Office 365 Exchange Online
.DESCRIPTION
Get All Management Role Assignments In Office 365 Exchange Online
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken
)
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-ManagementRoleAssignment"
}
}
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 All Exchange Online Management Scopes"
} Catch {
$err = $_.ErrorDetails.Message | ConvertFrom-Json
$errorMessage = $err.error.details.message
Throw $errorMessage
}
# Return
Return $response.value
}
}
# FUNCTION: Remove The Management Role Assignment In Office 365 Exchange Online
Function Remove-EXOApiManagementRoleAssignment {
<#
.SYNOPSIS
Remove The Management Role Assignment In Office 365 Exchange Online
.DESCRIPTION
Remove The Management Role Assignment In Office 365 Exchange Online
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER managementScopeAssignmentName
The Management Role Assignment Name
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $FALSE)]
[ValidateNotNullOrEmpty()]
[string]$managementRoleAssignmentName
)
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 = "Remove-ManagementRoleAssignment"
Parameters = [PSCustomObject]@{
Identity = $managementRoleAssignmentName
Confirm = $false
}
}
}
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 "Removed The Exchange Online Management Role Assignment '$managementScopeAssignmentName'"
} Catch {
If ($_.ErrorDetails.Message) {
$err = $_.ErrorDetails.Message | ConvertFrom-Json
$errorMessage = $err.error.details.message
} Else {
$errorMessage = $_
}
Throw $errorMessage
}
# Return
Return $response
}
}
# FUNCTION: Remove The Management Scope In Office 365 Exchange Online
Function Remove-EXOApiManagementScope {
<#
.SYNOPSIS
Remove The Management Scope In Office 365 Exchange Online
.DESCRIPTION
Remove The Management Scope In Office 365 Exchange Online
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
.PARAMETER managementScopeName
The Management Scope Name
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $FALSE)]
[ValidateNotNullOrEmpty()]
[string]$managementScopeName
)
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 = "Remove-ManagementScope"
Parameters = [PSCustomObject]@{
Identity = $managementScopeName
Confirm = $false
}
}
}
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 "Removed The Exchange Online Management Scope '$managementScopeName'"
} Catch {
If ($_.ErrorDetails.Message) {
$err = $_.ErrorDetails.Message | ConvertFrom-Json
$errorMessage = $err.error.details.message
} Else {
$errorMessage = $_
}
Throw $errorMessage
}
# Return
Return $response
}
}
# FUNCTION: Get The Service Principals In Office 365 Exchange Online
Function Get-EXOApiServicePrincipals {
<#
.SYNOPSIS
Get The Service Principals In Office 365 Exchange Online
.DESCRIPTION
Get The Service Principals In Office 365 Exchange Online
.PARAMETER accessToken
The Access Token To Be Used To Execute The Operation
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken
)
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-ServicePrincipal"
}
}
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: Remove The Service Principal With A Specific Application Name Or Service Principal Object ID From Office 365 Exchange Online
Function Remove-EXOApiServicePrincipal {
<#
.SYNOPSIS
Remove The Service Principal With A Specific Application Name Or Service Principal Object ID From Office 365 Exchange Online
.DESCRIPTION
Remove The Service Principal With A Specific Application Name Or Service Principal Object ID From 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 servicePrincipalObjectID
The Object ID From The Service Principal In Entra ID
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $FALSE)]
[ValidateNotNullOrEmpty()]
[string]$applicationName,
[Parameter(Mandatory = $FALSE)]
[ValidateNotNullOrEmpty()]
[string]$servicePrincipalObjectID
)
Begin {
$requestHeader = $null
$requestBody = $null
$response = $null
# Making Sure The Correct Combination Of Parameters Are Used
If (-not [string]::IsNullOrEmpty($applicationName) -And [string]::IsNullOrEmpty($servicePrincipalObjectID)) {
$identity = $applicationName
} ElseIf ([string]::IsNullOrEmpty($applicationName) -And -not [string]::IsNullOrEmpty($servicePrincipalObjectID)) {
$identity = $servicePrincipalObjectID
} ElseIf (-not [string]::IsNullOrEmpty($applicationName) -And -not [string]::IsNullOrEmpty($servicePrincipalObjectID)) {
$identity = $servicePrincipalObjectID
} Else {
Write-Error "The Correct Combination Of Parameters Is Not Being Used"
Write-Host " * You Need To Either Specify '-applicationName' And Specify An Application Name Or '-servicePrincipalObjectID' And Specify The Service Principal Object ID" -ForegroundColor red
Write-Host ""
BREAK
}
}
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 = "Remove-ServicePrincipal"
Parameters = [PSCustomObject]@{
Identity = $identity
Confirm = $false
}
}
}
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 "Removed The Exchange Online Service Principal '$identity'"
} Catch {
If ($_.ErrorDetails.Message) {
$err = $_.ErrorDetails.Message | ConvertFrom-Json
$errorMessage = $err.error.details.message
} Else {
$errorMessage = $_
}
Throw $errorMessage
}
# Return
Return $response
}
}
# FUNCTION: Get The Service Principal With A Specific Application ID Or Specific Service Principal Object 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 svcPrincObjectId
The Object 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]$svcPrincObjectId,
[Parameter(Mandatory = $FALSE)]
[string]$svcPrincDisplayName
)
Begin {
# Making Sure The Correct Combination Of Parameters Are Used
If ([string]::IsNullOrEmpty($applicationId) -And [string]::IsNullOrEmpty($svcPrincObjectId) -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 '-svcPrincObjectId' And Specify An Service Principal Object 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($svcPrincObjectId)) {
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=Id+eq+'$svcPrincObjectId'" -Method GET -Headers $requestHeader -ErrorAction Stop
If ([string]::IsNullOrEmpty($($response.value | Out-String))) {
Write-Verbose "Unable To Find The Service Principal With Object ID '$svcPrincObjectId' 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: Remove An Existing Service Principal With A Specific Service Principal Object Id or A Specific Application Id.
Function Remove-GraphApiServicePrincipal {
<#
.SYNOPSIS
Remove An Existing Service Principal With A Specific Service Principal Object Id or A Specific Application Id.
.DESCRIPTION
Remove An Existing Service Principal With A Specific Service Principal 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 Service Principal To Process
.PARAMETER servicePrincipalId
The Object Id Of The Service Principal To Process
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $FALSE)]
[string]$applicationId,
[Parameter(Mandatory = $FALSE)]
[string]$servicePrincipalId
)
Begin {
# Making Sure The Correct Combination Of Parameters Are Used
If ([string]::IsNullOrEmpty($applicationId) -And [string]::IsNullOrEmpty($servicePrincipalId)) {
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 '-servicePrincipalId' And Specify The Service Principal Object Id" -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-WebRequest -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/servicePrincipals(appId='{$applicationId}')" -Method DELETE -Headers $requestHeader -ErrorAction Stop
Write-Verbose "Deleted Service Principal With Application Id '$applicationId'"
}
If (-not [string]::IsNullOrEmpty($servicePrincipalId)) {
$response = Invoke-WebRequest -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$servicePrincipalId" -Method DELETE -Headers $requestHeader -ErrorAction Stop
Write-Verbose "Deleted Service Principal With Object Id '$servicePrincipalId'"
}
} 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 Service Principal With $(If (-not [string]::IsNullOrEmpty($applicationId)) {"Application Id '$applicationId'"} ElseIf (-not [string]::IsNullOrEmpty($servicePrincipalId)) {"Object Id '$servicePrincipalId'"}) Does Not Exist In This Tenant"
}
}
Throw $errorMessage
}
# Return
Return $response
}
}
# FUNCTION: Remove An Existing Application Registration With A Specific Application Registration Object Id or A Specific Application Id.
Function Remove-GraphApiApplicationRegistration {
<#
.SYNOPSIS
Remove An Existing Application Registration With A Specific Application Registration Object Id or A Specific Application Id.
.DESCRIPTION
Remove 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
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory = $TRUE)]
[ValidateNotNullOrEmpty()]
[string]$accessToken,
[Parameter(Mandatory = $FALSE)]
[string]$applicationId,
[Parameter(Mandatory = $FALSE)]
[string]$applicationRegistrationObjectId
)
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
}
$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-WebRequest -UseBasicParsing -Uri "https://graph.microsoft.com/v1.0/applications(appId='{$applicationId}')" -Method DELETE -Headers $requestHeader -ErrorAction Stop
Write-Verbose "Deleted 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 DELETE -Headers $requestHeader -ErrorAction Stop
Write-Verbose "Deleted 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
}
}
Clear-Host
################################################################
### CONSTANTS ###
################################################################
# N.A.
################################################################
### 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}
}
Import-Module $psMSALNetModule
################################################################
### 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"
$clientName1 = "Microsoft Graph Command Line Tools"
Write-Host ""
Write-Host "### Getting Access Token For Client App '$clientName1' 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 '$clientName1' 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
$clientId2 = "fb78d390-0c51-40cd-8e17-fdbfab77341b" # "Microsoft Exchange REST API Based Powershell"
$clientName2 = "Microsoft Exchange REST API Based Powershell"
Write-Host ""
Write-Host "### Getting Access Token For Client App '$clientName2' And The Office 365 Exchange Online Resource In Entra ID Tenant '$tenantFQDN'..." -ForegroundColor Cyan
Write-Host ""
Try {
$entraTokenParametersHT3 = @{
ClientId = $clientId2
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 '$clientName2' 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 "### Getting Information From Entra ID And Office 365 Exchange Online..." -ForegroundColor Cyan
Write-Host ""
# Getting Service Principal From Entra ID
$eidSvcPrinc = $null
$eidSvcPrincApplicationID = $null
$eidSvcPrinc = Get-GraphApiServicePrincipal -accessToken $accessTokenMSGraphAPI -svcPrincDisplayName $applicationName
If (-not [String]::IsNullOrEmpty($eidSvcPrinc)) {
If (($eidSvcPrinc | Measure-Object).Count -eq 1) {
$eidSvcPrincApplicationID = $eidSvcPrinc.AppId
Write-Host " => Entra ID: Service Principal '$applicationName' Was Retrieved Successfully..." -ForegroundColor Green
} Else {
$eidSvcPrincApplicationID = $eidSvcPrinc[0].AppId
Write-Host " => Entra ID: One Of The Service Principals With Name '$applicationName' Was Retrieved Successfully (Multiple Exist With The Same Name!)..." -ForegroundColor Green
}
Write-Host ""
} Else {
Write-Host " => Entra ID: Service Principal '$applicationName' Failed To Be Retrieved..." -ForegroundColor Red
Write-Host ""
Write-Host ""
Try {
Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
} Catch {
}
BREAK
}
# Getting Service Principal From Office 365 Exchange Online
$exoSvcPrincs = $null
$exoSvcPrinc = $null
$exoSvcPrincName = $null
$exoSvcPrincObjectID = $null
$exoSvcPrincs = Get-EXOApiServicePrincipals -accessToken $accessTokenO365EXO
If (-not [String]::IsNullOrEmpty($exoSvcPrincs)) {
$exoSvcPrinc = $exoSvcPrincs | Where-Object {$_.AppId -eq $eidSvcPrincApplicationID}
If (-not [String]::IsNullOrEmpty($exoSvcPrinc)) {
$exoSvcPrincName = $exoSvcPrinc.DisplayName
$exoSvcPrincObjectID = $exoSvcPrinc.Id
Write-Host " => Office 365 Exchange Online: Service Principal '$exoSvcPrincName' Was Retrieved Successfully..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Office 365 Exchange Online: Unable To Find The Service Principal With Application ID '$eidSvcPrincApplicationID'..." -ForegroundColor Red
Write-Host ""
#Write-Host ""
#Try {
# Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
#} Catch {
#}
#BREAK
}
} Else {
Write-Host " => Office 365 Exchange Online: No Service Principals Were Discovered..." -ForegroundColor Red
Write-Host ""
#Write-Host ""
#Try {
# Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
#} Catch {
#}
#BREAK
}
# Getting The Management Role Assignment From Office 365 Exchange Online
$exoMgmtRoleAssgnmnts = $null
$exoMgmtRoleAssgnmnt = $null
$exoMgmtRoleAssgnmntName = $null
$exoMgmtScopeName = $null
$exoMgmtRoleAssgnmnts = Get-EXOApiManagementRoleAssignments -accessToken $accessTokenO365EXO
If (-not [String]::IsNullOrEmpty($exoMgmtRoleAssgnmnts)) {
$exoMgmtRoleAssgnmnt = $exoMgmtRoleAssgnmnts | Where-Object {$_.RoleAssigneeName -eq $exoSvcPrincObjectID}
If (-not [String]::IsNullOrEmpty($exoMgmtRoleAssgnmnt)) {
$exoMgmtRoleAssgnmntName = $exoMgmtRoleAssgnmnt.Name
$exoMgmtScopeName = $exoMgmtRoleAssgnmnt.CustomResourceScope
Write-Host " => Office 365 Exchange Online: Management Role Assignment '$exoMgmtRoleAssgnmntName' Was Retrieved Successfully..." -ForegroundColor Green
Write-Host " => Office 365 Exchange Online: Discovered The Assigned Management Scope '$exoMgmtScopeName' Being Used By the Management Role Assignment..." -ForegroundColor Green
Write-Host ""
} Else {
Write-Host " => Office 365 Exchange Online: Unable To Find The Management Role Assignment For Service Principal Object ID '$exoSvcPrincObjectID'..." -ForegroundColor Red
Write-Host ""
#Write-Host ""
#Try {
# Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
#} Catch {
#}
#BREAK
}
} Else {
Write-Host " => Office 365 Exchange Online: No Management Role Assignments Were Discovered..." -ForegroundColor Red
Write-Host ""
#Write-Host ""
#Try {
# Remove-Module $psMSALNetModule -ErrorAction SilentlyContinue
#} Catch {
#}
#BREAK
}
# Getting The Management Role Assignments From Office 365 Exchange Online Using The Discovered Management Scope
$exoMgmtRoleAssgnmntsUsingMgmtScope = $null
$exoMgmtRoleAssgnmntsUsingMgmtScopeCount = $null
If (-not [String]::IsNullOrEmpty($exoMgmtScopeName)) {
$exoMgmtRoleAssgnmntsUsingMgmtScope = $exoMgmtRoleAssgnmnts | Where-Object {$_.CustomResourceScope -eq $exoMgmtScopeName}
$exoMgmtRoleAssgnmntsUsingMgmtScopeCount = ($exoMgmtRoleAssgnmntsUsingMgmtScope | Measure-Object).Count
If ($exoMgmtRoleAssgnmntsUsingMgmtScopeCount -eq 1) {
Write-Host " => Office 365 Exchange Online: Only 1 Management Role Assignment Is Using The Management Scope '$exoMgmtScopeName'..." -ForegroundColor Yellow
Write-Host ""
} ElseIf ($exoMgmtRoleAssgnmntsUsingMgmtScopeCount -ge 2) {
Write-Host " => Office 365 Exchange Online: Multiple Management Role Assignments Are Using The Management Scope '$exoMgmtScopeName'..." -ForegroundColor Yellow
Write-Host ""
} Else {
Write-Host " => Office 365 Exchange Online: No Management Role Assignments Are Using The Management Scope '$exoMgmtScopeName'..." -ForegroundColor Yellow
Write-Host ""
}
}
Write-Host ""
Write-Host "### Deleting Objects From Office 365 Exchange Online..." -ForegroundColor Cyan
Write-Host ""
# Deleting The Management Role Assignment In Office 365 Exchange Online
If (-not [String]::IsNullOrEmpty($exoMgmtRoleAssgnmntName)) {
Try {
$result = Remove-EXOApiManagementRoleAssignment -accessToken $accessTokenO365EXO -managementRoleAssignmentName $exoMgmtRoleAssgnmntName
Write-Host " => Office 365 Exchange Online: Management Role Assignment '$exoMgmtRoleAssgnmntName' Has Been Deleted Successfully..." -ForegroundColor Green
Write-Host ""
} catch {
$_
}
}
# Deleting The Management Scope In Office 365 Exchange Online
If (-not [String]::IsNullOrEmpty($exoMgmtScopeName)) {
If ($exoMgmtRoleAssgnmntsUsingMgmtScopeCount -eq 1) {
Try {
$result = Remove-EXOApiManagementScope -accessToken $accessTokenO365EXO -managementScopeName $exoMgmtScopeName
Write-Host " => Office 365 Exchange Online: Management Scope '$exoMgmtScopeName' Has Been Deleted Successfully..." -ForegroundColor Green
Write-Host ""
} catch {
$_
}
} Else {
Write-Host " => Office 365 Exchange Online: The Management Scope '$exoMgmtScopeName' Will Not Be Deleted As Multiple Management Role Assignments Are Using It..." -ForegroundColor Yellow
Write-Host ""
}
}
# Deleting The Service Principal In Office 365 Exchange Online
If (-not [String]::IsNullOrEmpty($exoSvcPrincObjectID)) {
Try {
$result = Remove-EXOApiServicePrincipal -accessToken $accessTokenO365EXO -servicePrincipalObjectID $exoSvcPrincObjectID
Write-Host " => Office 365 Exchange Online: Service Principal '$exoSvcPrincName' Has Been Deleted Successfully..." -ForegroundColor Green
Write-Host ""
} catch {
$_
}
}
Write-Host ""
Write-Host "### Deleting Objects From Entra ID..." -ForegroundColor Cyan
Write-Host ""
# Deleting The Service Principal In Entra ID
Try {
$result = Remove-GraphApiServicePrincipal -accessToken $accessTokenMSGraphAPI -applicationId $eidSvcPrincApplicationID
Write-Host " => Entra ID: Service Principal '$applicationName' Has Been Deleted Successfully..." -ForegroundColor Green
Write-Host ""
} catch {
$_
}
# Deleting The Application Registration In Entra ID
Try {
$result = Remove-GraphApiApplicationRegistration -accessToken $accessTokenMSGraphAPI -applicationId $eidSvcPrincApplicationID
Write-Host " => Entra ID: Application Registration '$applicationName' In Has Been Deleted Successfully..." -ForegroundColor Green
Write-Host ""
} catch {
$_
}
###
# 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