cross-tenant/VerifySetupDeprecated.ps1

343 строки
15 KiB
PowerShell

#################################################################################
#
# The sample scripts are not supported under any Microsoft standard support
# program or service. The sample scripts are provided AS IS without warranty
# of any kind. Microsoft further disclaims all implied warranties including, without
# limitation, any implied warranties of merchantability or of fitness for a particular
# purpose. The entire risk arising out of the use or performance of the sample scripts
# and documentation remains with you. In no event shall Microsoft, its authors, or
# anyone else involved in the creation, production, or delivery of the scripts be liable
# for any damages whatsoever (including, without limitation, damages for loss of business
# profits, business interruption, loss of business information, or other pecuniary loss)
# arising out of the use of or inability to use the sample scripts or documentation,
# even if Microsoft has been advised of the possibility of such damages.
#
#################################################################################
<# .SYNOPSIS
This script can be used by a tenant that wishes to validate the setup required for cross-tenant mailbox migration.
This script performs the following checks when run with -Context Target parameter:
1. Validates the following on the AAD application:
a. Is registered in the target tenant directory
b. Is setup with right permissions on MSGraph and Exchange
c. Is consented by an administrator
2. Validates the following in KeyVault:
a. The KeyVault url is correct
b. Exchange first party application has READ permissions on the secret
3. Validates the following in OrganizationRelationship:
a. Has a relationship with Source tenant
b. The move direction is correct.
4. Validates the following on Migration Endpoint:
a. ApplicationId is correct.
b. ApplicationKeyVaultUrl is correct.
c. RemoteTenantId is correct.
.PARAMETER PartnerTenantId
PartnerTenantId - the tenant id of the partner tenant.
.PARAMETER PartnerTenantDomain
PartnerTenantDomain - the tenant domain of the partner tenant.
.PARAMETER ApplicationId
ApplicationId - the application setup for mailbox migration.
.PARAMETER ApplicationKeyVaultUrl
ApplicationKeyVaultUrl - the keyvault url for application secret.
.EXAMPLE - TargetTenant
$report = VerifySetup.ps1 -PartnerTenantId <SourceTenantId> -ApplicationId <AADApplicationId> -ApplicationKeyVaultUrl <appKeyVaultUrl> -PartnerTenantDomain <PartnerTenantDomain> -Verbose
.EXAMPLE - TargetTenant
$report = VerifySetup.ps1 -PartnerTenantId <SourceTenantId> -ApplicationId <AADApplicationId> -ApplicationKeyVaultUrl <appKeyVaultUrl> -PartnerTenantDomain <PartnerTenantDomain> -SubscriptionId <AzureSubscriptionId> -Verbose
.EXAMPLE - SourceTenant
$report = VerifySetup.ps1 -PartnerTenantId <TargetTenantId> -ApplicationId <AADApplicationId>
#>
param
(
[Parameter(Mandatory = $true, HelpMessage='Partner tenant id', ParameterSetName = 'VerifyTarget')]
[Parameter(Mandatory = $true, HelpMessage='Partner tenant id', ParameterSetName = 'VerifySource')]
[ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
[string]$PartnerTenantId,
[Parameter(Mandatory = $true, HelpMessage='AAD ApplicationId', ParameterSetName = 'VerifyTarget')]
[Parameter(Mandatory = $true, HelpMessage='Partner tenant id', ParameterSetName = 'VerifySource')]
[ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
[string]$ApplicationId,
[Parameter(Mandatory = $true, HelpMessage='PartnerTenantDomain', ParameterSetName = 'VerifyTarget')]
[ValidateScript({ -not [string]::IsNullOrWhiteSpace($_) })]
[string]$PartnerTenantDomain,
[Parameter(Mandatory = $true, HelpMessage='App secret key vault url', ParameterSetName = 'VerifyTarget')]
[ValidateScript({
if ($_ -cmatch "^https://[a-zA-Z_0-9\-]+\.vault\.azure.net(:443){0,1}/certificates/[a-zA-Z_0-9\-]+/[a-zA-Z_0-9]+$")
{
$true
}
elseif ($_ -cmatch "^https://[a-zA-Z_0-9]+\.vault\.azure.us(:443){0,1}/certificates/[a-zA-Z_0-9]+/[a-zA-Z_0-9]+$") {
$true
}
else
{
throw [System.Management.Automation.ValidationMetadataException] "Please make sure key vault url matches format specified here: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#vault-name-and-object-name"
}
})]
[string]$ApplicationKeyVaultUrl,
[Parameter(Mandatory = $false, HelpMessage='SubscriptionId for key vault', ParameterSetName = 'VerifyTarget')]
[Parameter(Mandatory = $false, HelpMessage='SubscriptionId for key vault', ParameterSetName = 'VerifySource')]
[ValidateScript({-not [string]::IsNullOrWhiteSpace($_)})]
[string]$SubscriptionId
)
$ErrorActionPreference = 'Stop'
$MS_GRAPH_APP_ID = "00000003-0000-0000-c000-000000000000"
$MS_GRAPH_APP_ROLE = "User.Invite.All"
$EXO_APP_ID = "00000002-0000-0ff1-ce00-000000000000"
$EXO_APP_ROLE = "Mailbox.Migration"
function Main() {
$report = @{}
Check-ExchangeOnlinePowershellConnection
$isTargetTenant = $PSCmdlet.ParameterSetName -eq 'VerifyTarget'
$azureADAccount = Connect-AzureAD
Write-Verbose "Connected to AzureAD - $($azureADAccount | Out-String)"
$azAccount = Connect-AzAccount -Tenant $azureADAccount.Tenant.ToString()
Write-Verbose "Connected to Az Account - $($azAccount | Out-String)"
$currentTenantId = $azureADAccount.TenantId.Guid
if($isTargetTenant -eq $true)
{
Check-AzSubscription
}
Write-Verbose "Verifying Application; AppId: [$ApplicationId] Current tenant: [$currentTenantId] Partner tenant: [$PartnerTenantId] IsTargetTenant: [$isTargetTenant]"
$errors, $warnings = Verify-Application $ApplicationId $currentTenantId $PartnerTenantId $isTargetTenant
$report["Application"] = @{ "Errors" = $errors; "Warnings" = $warnings }
Write-Host "`r`n"
Print-Result "Verifying AAD Application" $errors $warnings
if ($isTargetTenant -eq $true) {
Write-Verbose "Verifying KeyVault; AppId: [$ApplicationId] ApplicationKeyVaultUrl: [$ApplicationKeyVaultUrl]"
$errors = Verify-KeyVault $ApplicationId $ApplicationKeyVaultUrl
Print-Result "Verifying KeyVault" $errors
$report["KeyVault"] = @{ "Errors" = $errors }
}
Write-Verbose "Verifying OrganizationRelationship; AppId: [$ApplicationId] Partner tenant: [$PartnerTenantId] IsTargetTenant: [$isTargetTenant]"
$errors = Verify-OrganizationRelationship $PartnerTenantId $ApplicationId $isTargetTenant
Print-Result "Verifying OrganizationRelationship" $errors
$report["OrganizationRelationship"] = @{ "Errors" = $errors }
if ($isTargetTenant -eq $true) {
Write-Verbose "Verifying MigrationEndpoint; AppId: [$ApplicationId] Partner tenant: [$PartnerTenantDomain] ApplicationKeyVaultUrl: [$ApplicationKeyVaultUrl]"
$errors = Verify-MigrationEndpoint $PartnerTenantDomain $ApplicationId $ApplicationKeyVaultUrl
Print-Result "Verifying MigrationEndpoint" $errors
$report["MigrationEndpoint"] = @{ "Errors" = $errors }
}
Write-Verbose ($report | ConvertTo-Json)
$report
}
function Print-Result([string]$opName, $errors, $warnings) {
Write-Host "[$opName].............." -NoNewLine
if (!$errors -and !$warnings) {
Write-Host "[Passed]" -NoNewLine -ForeGroundColor Green
Write-Host "`r`n"
return
}
if ($errors) {
Write-Host "[Failed]" -ForeGroundColor Red
Write-Host ($errors -join "`n") -ForeGroundColor Red
}
if ($warnings) {
if (!$errors) {
Write-Host "[Warnings]" -ForeGroundColor Yellow
}
Write-Host ($warnings -join "`n") -ForeGroundColor Yellow
}
Write-Host "`r`n`r`n"
}
function Verify-Application ([string]$appId, [string]$currentTenantId, [string]$partnerTenantId, [bool]$isTargetTenant) {
$warnings = @()
$errors = @()
$spn = Get-AzureADServicePrincipal -Filter "AppId eq '$appId'"
if (!$spn) {
$errors += "App [$appId] is not registered in [$currentTenantId] tenant"
return $errors
}
if ($isTargetTenant -eq $true) {
if ($spn.AppOwnerTenantId -ne $currentTenantId) {
$error += "App [$appId] was found in the [$currentTenantId] tenant but is not owned by it. Since this is target tenant, the app used for migration must be owned by target tenant."
}
} elseif ($spn.AppOwnerTenantId -ne $partnerTenantId) {
$error += "App [$appId] was found in the [$currentTenantId] tenant but is not owned by $partnerTenantId. Please use an application owned by target tenant for mailbox migrations."
}
# Check MSGraph and EXO has incoming app roles assignment from the tenant friending app
# 1. collect spns of MSGraph and EXO applications
$msGraphSpn = Get-AzureADServicePrincipal -Filter "AppId eq '$MS_GRAPH_APP_ID'"
$exoSpn = Get-AzureADServicePrincipal -Filter "AppId eq '$EXO_APP_ID'"
if (!$msGraphSpn -or !$exoSpn) {
$errors += "Internal Error: SPNs of MSGraph or EXO not found."
return $errors
}
# Get the permission objects from Exo and MsGraph
$exoMailboxMigrationPermissions = $exoSpn.AppRoles | ? { $_.Value -eq $EXO_APP_ROLE }
$msGraphDirectoryPermissions = $msGraphSpn.AppRoles | ? { $_.Value -eq $MS_GRAPH_APP_ROLE }
# Get the permission objects of the permissions assigned to the application
$exoPermissionForApp = Get-AzureADServiceAppRoleAssignment -ObjectId $exoSpn.ObjectId -All $true | ? { $_.PrincipalId -eq $spn.ObjectId }
$msGraphPermissionForApp = Get-AzureADServiceAppRoleAssignment -ObjectId $msGraphSpn.ObjectId -All $true | ? { $_.PrincipalId -eq $spn.ObjectId }
if (!$exoPermissionForApp -or ($exoPermissionForApp.Id -ne $exoMailboxMigrationPermissions.Id)) {
$errors += "App [$appId] does not have [$EXO_APP_ROLE] permission on Exchange setup or the permission is not consented by an Administrator"
}
if (!$msGraphPermissionForApp -or ($msGraphPermissionForApp.Id -ne $msGraphDirectoryPermissions.Id)) {
$warnings += "App [$appId] does not have [$MS_GRAPH_APP_ROLE] permission on MSGraph setup or the permission is not consented by an Administrator"
}
$errors, $warnings
}
function Verify-KeyVault([string]$appId, [string]$appKvUrl) {
$errors = @()
try {
$uri = [System.Uri]::new($appKvUrl)
$kvName = $uri.Host.Split(".")[0]
$kv = Get-AzKeyVault -VaultName $kvName
if (!$kv) {
$errors += "KeyVault: $kvName not found"
return $errors
}
$exoSpn = Get-AzureADServicePrincipal -Filter "AppId eq '$EXO_APP_ID'"
$exoAccessPolicy = $kv.AccessPolicies | ? { $_.ObjectId -eq $exoSpn.ObjectId }
if (!$exoAccessPolicy) {
$errors += "Exchange does not have any permissions on the KeyVault [$kvName]"
return $errors
}
$certStorePermissions = $exoAccessPolicy.PermissionsToCertificates.ToLower()
$secretStorePermissions = $exoAccessPolicy.PermissionsToSecrets.ToLower()
"get", "list" | % { if (!$certStorePermissions.Contains($_)) {$errors += "Exchange does not have [$_] permission on KeyVault [$kvName]'s Certificate container"}}
"get", "list" | % { if (!$secretStorePermissions.Contains($_)) {$errors += "Exchange does not have [$_] permission on KeyVault [$kvName]'s Secrets container"}}
} catch {
$errors += $_.Message
}
$errors
}
function Verify-OrganizationRelationship([string]$partnerTenantId, [string]$appId, [bool]$isTargetTenant) {
$errors = @()
$orgRel = Get-OrganizationRelationship | ? { $_.DomainNames -contains $partnerTenantId }
if (!$orgRel) {
$errors += "Organization relationship does not exist with [$partnerTenantId]"
return $errors
}
if ($isTargetTenant -eq $true) {
if (!$orgRel.MailboxMoveEnabled) {
$errors += "MailboxMove is not enabled in Organization relationship with [$partnerTenantId]"
}
if ($orgRel.MailboxMoveCapability -ne 'Inbound') {
$errors += "MailboxMoveCapability is invalid in Organization relationship with [$partnerTenantId]. It should be [Inbound] found [$($orgRel.MailboxMoveCapability)]"
}
if ($errors) {
return $errors
}
} else {
if (!$orgRel.MailboxMoveEnabled) {
$errors += "MailboxMove is not enabled in Organization relationship with [$partnerTenantId]"
}
if ($orgRel.MailboxMoveCapability -ne 'RemoteOutbound') {
$errors += "MailboxMoveCapability is invalid in Organization relationship with [$partnerTenantId]. It should be [RemoteOutbound] found [$($orgRel.MailboxMoveCapability)]"
}
if ($orgRel.OAuthApplicationId -ne $appId) {
$errors += "Mailbox Migration ApplicationId is not whitelisted in the Organization Relationship with [$partnerTenantId]. Expected [$appId] found [$($orgRel.ApplicationId)]"
}
if (!$orgRel.MailboxMovePublishedScopes) {
$errors += "Source tenant needs to specify MailboxMovePublishedScopes to allow migration"
}
}
return $errors
}
function Verify-MigrationEndpoint([string]$partnerTenantDomain, [string]$appId, [string]$appKvUrl) {
$errors = @()
$migEp = Get-MigrationEndpoint | ? { $_.ApplicationId -eq $appId }
if (!$migEp) {
$errors += "Migration Endpoint containing [$appId] not found."
return $errors
}
if ($migEp.RemoteTenant -ne $partnerTenantDomain) {
$errors += "RemoteTenant does not match in Migration Endpoint. Expected [$partnerTenantDomain] found [$($migEp.RemoteTenant)]"
}
if ($migEp.ApplicationId -ne $appId) {
$errors += "ApplicationId does not match in Migration Endpoint. Expected [$appId] found [$($migEp.ApplicationId)]"
}
if ($migEp.AppSecretKeyVaultUrl -ne $appKvUrl) {
$errors += "AppSecretKeyVaultUrl does not match in Migration Endpoint. Expected [$appKvUrl] found [$($migEp.AppSecretKeyVaultUrl)]"
}
if (!$migEp.IsRemote) {
$errors += "IsRemote does not match in Migration Endpoint. Expected [true] found [$($migEp.IsRemote)]"
}
return $errors
}
function Check-ExchangeOnlinePowershellConnection {
if ($Null -eq (Get-Command New-OrganizationRelationship -ErrorAction SilentlyContinue)) {
Write-Error "Please connect to the Exchange Online Management module or Exchange Online through basic authentication before running this script!";
}
}
function Check-AzSubscription {
if (!$SubscriptionId)
{
$subscriptions = Get-AzSubscription
if ($subscriptions.Count -gt 1) {
Write-Error "Multipule Azure subscriptions were found for this tenant. Please rerun the script and use the -SubscriptionId parameter with the correct subscription"
}
elseif (!$subscriptions) {
Write-Error "No valid Azure subscriptions were found for this tenant."
}
Set-AzContext -Subscription $subscriptions.SubscriptionId
}
elseif ($SubscriptionID)
{
Write-Verbose "SubscriptionId - $SubscriptionId was provided."
Set-AzContext -Subscription $SubscriptionId
}
}
Main