Public/Install-ByttEmail.ps1

<#
.DESCRIPTION
    Installs Bytt.Email in the signed in tenant

.SYNOPSIS
    Installs Bytt.Email in the signed in tenant

.EXAMPLE
    Install-ByttEmail

.EXAMPLE
    Install-ByttEmail -UpdateAUCriteria

.EXAMPLE
    Install-ByttEmail -AdminUnitName "Custom Admin Unit Name"

.EXAMPLE
    Install-ByttEmail -GroupNamingPrefix "Custom Prefix - " -GroupNamingSuffix " - Custom Suffix"

.PARAMETER UpdateAUCriteria
    If the Bytt.Email administrative unit already exists, update its membership rule to match the expected criteria.

.PARAMETER AdminUnitName
    The name of the administrative unit to create or update. Default is "Bytt.Email".

.PARAMETER GroupNamingPrefix
    The prefix to use for naming the pattern groups. Default is "Bytt.Email pattern -

.PARAMETER GroupNamingSuffix
    The suffix to use for naming the pattern groups. Default is an empty string.
#>

function Install-ByttEmail {
    [CmdletBinding()]

    Param(
        [Parameter(Mandatory = $false)]
        [Switch]$UpdateAUCriteria,

        [Parameter(Mandatory = $false)]
        [string]$AdminUnitName = "Bytt.Email",

        [Parameter(Mandatory = $false)]
        [string]$GroupNamingPrefix = "Bytt.Email pattern - ",

        [Parameter(Mandatory = $false)]
        [string]$GroupNamingSuffix = ""
    )

    Begin {
        
    }

    Process {
        $fortytwoUniverseAppId = "2808f963-7bba-4e66-9eee-82d0b178f408"
        if (![string]::IsNullOrEmpty($ENV:FORTYTWO_UNIVERSE_APP_ID)) {
            $fortytwoUniverseAppId = $ENV:FORTYTWO_UNIVERSE_APP_ID
        }

        $byttEmailAppId = "34ee8edb-d2ff-4ee9-bac3-73b53303e00f"
        if (![string]::IsNullOrEmpty($ENV:BYTT_EMAIL_APP_ID)) {
            $byttEmailAppId = $ENV:BYTT_EMAIL_APP_ID
        }

        Write-Host "Signing in to Microsoft Graph..."
        Connect-MgGraph -Scope Application.ReadWrite.All, AppRoleAssignment.ReadWrite.All, Group.ReadWrite.All, AdministrativeUnit.ReadWrite.All, DelegatedPermissionGrant.ReadWrite.All -NoWelcome
        
        Write-Host "Checking Enterprise Application for Fortytwo Universe..." -NoNewline
        $fortytwoUniverseApp = Get-MgServicePrincipal -Filter "appId eq '$fortytwoUniverseAppId'" -ErrorAction SilentlyContinue

        if (!$fortytwoUniverseApp) {
            Write-Host -ForegroundColor Green " [Creating... " -NoNewline
            $fortytwoUniverseApp = New-MgServicePrincipal -AppId $fortytwoUniverseAppId
            Start-Sleep -Seconds 3
            Write-Host -ForegroundColor Green " Done! (objectid $($fortytwoUniverseApp.Id))]"
            Write-Verbose "Enterprise Application for Fortytwo Universe created with objectid $($fortytwoUniverseApp.Id)."
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Checking Enterprise Application for Bytt.Email..." -NoNewline
        $byttEmailApp = Get-MgServicePrincipal -Filter "appId eq '$byttEmailAppId'" -ErrorAction SilentlyContinue
        if (!$byttEmailApp) {
            Write-Host -ForegroundColor Green " [Creating... " -NoNewline
            $byttEmailApp = New-MgServicePrincipal -AppId $byttEmailAppId
            Start-Sleep -Seconds 3
            Write-Host -ForegroundColor Green " Done! (objectid $($byttEmailApp.Id))]"
            Write-Verbose "Enterprise Application for Bytt.Email created with objectid $($byttEmailApp.Id)."
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Adding admin consent for user.read to Fortytwo Universe..." -NoNewline
        $microsoftGraph = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'" -ErrorAction SilentlyContinue
        $graphPermissions = Get-MgOauth2PermissionGrant -Filter "consentType eq 'AllPrincipals' and resourceId eq '$($microsoftGraph.Id)'" -All
        $match = $graphPermissions | Where-Object { $_.ClientId -eq $fortytwoUniverseApp.Id -and $_.Scope -eq "user.read" }

        if (!$match) {
            $grant = New-MgOauth2PermissionGrant -ClientId $fortytwoUniverseApp.Id -ConsentType "AllPrincipals" -PrincipalId $null -ResourceId $microsoftGraph.Id -Scope "user.read"
            Write-Host -ForegroundColor Green " [OK]"
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Adding admin consent for user.read to Bytt.Email..." -NoNewline 
        $microsoftGraph = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'" -ErrorAction SilentlyContinue
        $graphPermissions = Get-MgOauth2PermissionGrant -Filter "consentType eq 'AllPrincipals' and resourceId eq '$($microsoftGraph.Id)'" -All
        $match = $graphPermissions | Where-Object { $_.ClientId -eq $byttEmailApp.Id -and $_.Scope -eq "user.read" }

        if (!$match) {
            $grant = New-MgOauth2PermissionGrant -ClientId $byttEmailApp.Id -ConsentType "AllPrincipals" -PrincipalId $null -ResourceId $microsoftGraph.Id -Scope "user.read"
            Write-Host -ForegroundColor Green " [OK]"
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Granting user.read.all application permission to Bytt.Email..." -NoNewline
        $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -All
        $match = $appRoleAssignments | Where-Object { $_.AppRoleId -eq "df021288-bdef-4463-88db-98f22de89214" -and $_.PrincipalId -eq $byttEmailApp.Id -and $_.ResourceId -eq $microsoftGraph.Id }
        if (!$match) {
            $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -PrincipalId $byttEmailApp.Id -ResourceId $microsoftGraph.Id -AppRoleId "df021288-bdef-4463-88db-98f22de89214"
            Write-Host -ForegroundColor Green " [OK]"
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Granting groupmember.read.all application permission to Bytt.Email..." -NoNewline
        $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -All
        $match = $appRoleAssignments | Where-Object { $_.AppRoleId -eq "98830695-27a2-44f7-8c18-0c3ebc9698f6" -and $_.PrincipalId -eq $byttEmailApp.Id -and $_.ResourceId -eq $microsoftGraph.Id }
        if (!$match) {
            $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -PrincipalId $byttEmailApp.Id -ResourceId $microsoftGraph.Id -AppRoleId "98830695-27a2-44f7-8c18-0c3ebc9698f6"
            Write-Host -ForegroundColor Green " [OK]"
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Adding admin consent for Fortytwo Universe user_impersonation Bytt.Email..." -NoNewline
        $microsoftGraph = Get-MgServicePrincipal -Filter "appId eq '$($fortytwoUniverseAppId)'" -ErrorAction SilentlyContinue
        $graphPermissions = Get-MgOauth2PermissionGrant -Filter "consentType eq 'AllPrincipals' and resourceId eq '$($microsoftGraph.Id)'" -All
        $match = $graphPermissions | Where-Object { $_.ClientId -eq $byttEmailApp.Id -and $_.Scope -eq "user_impersonation" }

        if (!$match) {
            $grant = New-MgOauth2PermissionGrant -ClientId $byttEmailApp.Id -ConsentType "AllPrincipals" -PrincipalId $null -ResourceId $fortytwoUniverseApp.Id -Scope "user_impersonation"
            Write-Host -ForegroundColor Green " [OK]"
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Creating pattern groups for all domains..."
        $groups = @()
        $domains = Get-MgDomain -All | Where-Object { $_.IsVerified -eq $true } | Where-Object { $_.Id -notlike "*mail.onmicrosoft.com" }

        $creates = $false
        foreach ($domain in $domains) {
            $groupName = "{0}{1}{2}" -f $GroupNamingPrefix, $domain.Id, $GroupNamingSuffix
            Write-Host " - Processing group for domain $($domain.Id)..." -NoNewline
            $group = Get-MgGroup -Filter "displayName eq '$groupName'" -ErrorAction SilentlyContinue
            if (!$group) {
                $group = New-MgGroup -DisplayName $groupName -MailEnabled:$false -SecurityEnabled:$true -MailNickname "$(new-guid)".Substring(0, 8) -AdditionalProperties @{
                    "extension_34ee8edbd2ff4ee9bac373b53303e00f_patterns" = @(
                        "{firstname1}.{lastname-1}@$($domain.Id)"
                        "{firstname1}.{firstname2}.{lastname-1}@$($domain.Id)"
                        "{firstname1}.{lastname-2}.{lastname-1}@$($domain.Id)"
                        "{firstnamewd1}.{lastnamewd-1}@$($domain.Id)"
                        "{firstnamewd1}.{firstnamewd2}.{lastnamewd-1}@$($domain.Id)"
                        "{firstnamewd1}.{lastnamewd-2}.{lastnamewd-1}@$($domain.Id)"
                        "{firstname1}.{firstname2,1}.{lastname-1}@$($domain.Id)"
                        "{firstname1}.{lastname-1}2@$($domain.Id)"
                    )
                } -GroupTypes @("DynamicMembership") -MembershipRule "(user.userPrincipalName -endsWith ""@$($domain.Id)"")" -MembershipRuleProcessingState "On"
                
                Write-Host -ForegroundColor Green " [OK]"
                $creates = $true
            }
            else {
                Write-Host -ForegroundColor Green " [OK]"
            }
            $groups += $group
        }

        if($creates) {
            Start-Sleep 3
        }

        Write-Host "Assigning all created pattern groups to Fortytwo Universe role for Bytt.Email..."
        $appRoleId = "f7906386-2397-411c-820f-270e4f905c05"
        $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $fortytwoUniverseApp.Id
        foreach ($group in $groups) {
            Write-Host " - Processing group $($group.DisplayName)..." -NoNewline
            $match = $appRoleAssignments | Where-Object { $_.AppRoleId -eq $appRoleId -and $_.PrincipalId -eq $group.Id }
            if (!$match) {
                $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $fortytwoUniverseApp.Id -PrincipalId $group.Id -ResourceId $fortytwoUniverseApp.Id -AppRoleId $appRoleId
                Write-Host -ForegroundColor Green " [OK]"
            }
            else {
                Write-Host -ForegroundColor Green " [OK]"
            }
        }

        Write-Host "Assigning all created pattern groups to the Bytt.Email application..."
        $appRoleId = "00000000-0000-0000-0000-000000000000"
        $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $byttEmailApp.Id
        foreach ($group in $groups) {
            Write-Host " - Processing group $($group.DisplayName)..." -NoNewline
            $match = $appRoleAssignments | Where-Object { $_.AppRoleId -eq $appRoleId -and $_.PrincipalId -eq $group.Id }
            if (!$match) {
                $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -PrincipalId $group.Id -ResourceId $byttEmailApp.Id -AppRoleId $appRoleId
                Write-Host -ForegroundColor Green " [OK]"
            }
            else {
                Write-Host -ForegroundColor Green " [OK]"
            }
        }

        Write-Host "Creating administrative unit for Bytt.Email..." -NoNewline
        $adminUnit = Get-MgDirectoryAdministrativeUnit -Filter "displayName eq '$adminUnitName'" -ErrorAction SilentlyContinue
        $membershipRule = "user.memberof -any (group.objectId -in ['$($groups.Id -join "','")'])"
        if (!$adminUnit) {
            $params = @{
                displayName                   = $adminUnitName
                description                   = "Used for delegating the Bytt.Email application permissions to read users and groups"
                membershipType                = "Dynamic"
                membershipRule                = $membershipRule
                membershipRuleProcessingState = "On"
            }

            Write-Host -ForegroundColor Green " [Creating... " -NoNewline
            $adminUnit = New-MgDirectoryAdministrativeUnit -DisplayName $params.displayName -Description $params.description -MembershipType $params.membershipType -MembershipRule $params.membershipRule -MembershipRuleProcessingState $params.membershipRuleProcessingState
            Start-Sleep -Seconds 3
            Write-Host -ForegroundColor Green " Done! ($($adminUnit.Id))]"
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"

            if ($adminUnit.MembershipRule -ne $membershipRule -and !$UpdateAUCriteria.IsPresent) {
                Write-Warning "The $($adminUnitName) administrative unit exists, but the membership rule is different than expected. However, we will not update it automatically to avoid removing any existing members. Please review and update the membership rule manually if needed. The expected rule is: `n`n$membershipRule`n`nYou can re-run Install-ByttEmail with the -UpdateAUCriteria switch to update the rule automatically."
            }
            else {
                if ($adminUnit.MembershipRule -ne $membershipRule -and $UpdateAUCriteria.IsPresent) {
                    Write-Host "Updating membership rule for $($adminUnitName) administrative unit..." -NoNewline
                    Update-MgDirectoryAdministrativeUnit `
                        -AdministrativeUnitId $adminUnit.Id `
                        -MembershipRule $membershipRule `
                        -MembershipRuleProcessingState "On" `
                        -MembershipType "Dynamic"
                    Write-Host -ForegroundColor Yellow " [Updated]"
                }
            }
        }

        Write-Host "Granting the Bytt.Email servicePrincipal access to the Bytt.Email administrative unit..." -NoNewline
        $roleTemplateId = "fe930be7-5e62-47db-91af-98c3a49a38b1" # User Administrator
        $role = Get-MgDirectoryRole | Where-Object roleTemplateId -eq $roleTemplateId

        if(!$role.id) {
            Write-Host -ForegroundColor Red " [Role not found]"
            Write-Warning "The User Administrator role was not found in the tenant. Please ensure that the role exists and try again. `n`nYou can do this with the following cmdlet:`n`nNew-MgDirectoryRole -RoleTemplateId '$roleTemplateId'`n`nAfter that, re-run the installation."
            return
        }
        $roleAssignments = Get-MgDirectoryAdministrativeUnitScopedRoleMember -AdministrativeUnitId $adminUnit.Id
        $match = $roleAssignments | Where-Object { $_.RoleId -eq $role.Id -and $_.RoleMemberInfo.Id -eq $byttEmailApp.Id }
        if (!$match) {
            $params = @{
                roleId         = $role.Id
                roleMemberInfo = @{
                    id = $byttEmailApp.Id
                }
            }

            Invoke-MgGraphRequest -Method Post -uri "https://graph.microsoft.com/v1.0/directory/administrativeUnits/$($adminUnit.Id)/scopedRoleMembers" -Body ($params | ConvertTo-Json) -ContentType "application/json" | Out-Null

            Write-Host -ForegroundColor Green " [OK]"
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }
    }
}