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 = "",

        [Parameter(Mandatory = $false)]
        [Switch]$WhatIf
    )

    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) {
            if ($WhatIf.IsPresent) {
                $fortytwoUniverseApp = @{
                    Id          = "00000000-0000-0000-0000-000000000000"
                    DisplayName = "Fortytwo Universe"
                }
                Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
            }
            else {
                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) {
            if ($WhatIf.IsPresent) {
                $byttEmailApp = @{
                    Id          = "00000000-0000-0000-0000-000000000000"
                    DisplayName = "Bytt.Email"
                }
                Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
            }
            else {
                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) {
            if ($WhatIf.IsPresent) {
                Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
            }
            else {
                $grant = New-MgOauth2PermissionGrant -ClientId $fortytwoUniverseApp.Id -ConsentType "AllPrincipals" -PrincipalId $null -ResourceId $microsoftGraph.Id -Scope "user.read"
                Write-Host -ForegroundColor Green " [Created]"
            }
        }
        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) {
            if ($WhatIf.IsPresent) {
                Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
            }
            else {
                # $grant = New-MgOauth2PermissionGrant -ClientId $byttEmailApp.Id -ConsentType "AllPrincipals" -PrincipalId $null -ResourceId $microsoftGraph.Id -Scope "user.read"
                $grant = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants" -Method Post -Body (@{
                        clientId    = $byttEmailApp.Id
                        consentType = "AllPrincipals"
                        principalId = $null
                        resourceId  = $microsoftGraph.Id
                        scope       = "user.read"
                    } | convertto-json) -ContentType "application/json"
                if ($grant.id) {
                    Write-Host -ForegroundColor Green " [Created]"
                }
                else {
                    Write-Host -ForegroundColor Red " [Failed]"
                }
            }
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Granting user.read.all application permission to Bytt.Email..." -NoNewline
        $appRoleAssignments = $byttEmailApp.Id -ne "00000000-0000-0000-0000-000000000000" ? (Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -All) : $null
        $match = $appRoleAssignments | Where-Object { $_.AppRoleId -eq "df021288-bdef-4463-88db-98f22de89214" -and $_.PrincipalId -eq $byttEmailApp.Id -and $_.ResourceId -eq $microsoftGraph.Id }
        if (!$match) {
            if ($WhatIf.IsPresent) {
                Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
            }
            else {
                $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -PrincipalId $byttEmailApp.Id -ResourceId $microsoftGraph.Id -AppRoleId "df021288-bdef-4463-88db-98f22de89214"
                if ($appRoleAssignment.id) {
                    Write-Host -ForegroundColor Green " [Created]"
                }
                else {
                    Write-Host -ForegroundColor Red " [Failed]"
                }
            }
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }

        Write-Host "Granting groupmember.read.all application permission to Bytt.Email..." -NoNewline
        $appRoleAssignments = $byttEmailApp.Id -ne "00000000-0000-0000-0000-000000000000" ? (Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -All) : $null
        $match = $appRoleAssignments | Where-Object { $_.AppRoleId -eq "98830695-27a2-44f7-8c18-0c3ebc9698f6" -and $_.PrincipalId -eq $byttEmailApp.Id -and $_.ResourceId -eq $microsoftGraph.Id }
        if (!$match) {
            if ($WhatIf.IsPresent) {
                Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
            }
            else {
                $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -PrincipalId $byttEmailApp.Id -ResourceId $microsoftGraph.Id -AppRoleId "98830695-27a2-44f7-8c18-0c3ebc9698f6"
                Write-Host -ForegroundColor Green " [Created]"
            }
        }
        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) {
            if ($WhatIf.IsPresent) {
                Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
            }
            else {
                # $grant = New-MgOauth2PermissionGrant -ClientId $byttEmailApp.Id -ConsentType "AllPrincipals" -PrincipalId $null -ResourceId $fortytwoUniverseApp.Id -Scope "user_impersonation"
                $grant = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants" -Method Post -Body (@{
                        clientId    = $byttEmailApp.Id
                        consentType = "AllPrincipals"
                        principalId = $null
                        resourceId  = $fortytwoUniverseApp.Id
                        scope       = "user_impersonation"
                    } | convertto-json) -ContentType "application/json"
                if ($grant.id) {
                    Write-Host -ForegroundColor Green " [Created]"
                }
                else {
                    Write-Host -ForegroundColor Red " [Failed]"
                }
            }
        }
        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) {
                if ($WhatIf.IsPresent) {
                    Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
                    $group = $null
                }
                else {
                    Write-Host -ForegroundColor Green " [Creating... " -NoNewline
                    $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 'Bytt.Email - user'..."
        $appRoleId = "f7906386-2397-411c-820f-270e4f905c05"
        $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $fortytwoUniverseApp.Id
        foreach ($group in $groups) {
            Write-Host " - Group $($group.DisplayName)..." -NoNewline
            $match = $appRoleAssignments | Where-Object { $_.AppRoleId -eq $appRoleId -and $_.PrincipalId -eq $group.Id }
            if (!$match) {
                if ($WhatIf.IsPresent) {
                    Write-Host -ForegroundColor Yellow " [Assigning... (WhatIf)]"
                }
                else {
                    $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $fortytwoUniverseApp.Id -PrincipalId $group.Id -ResourceId $fortytwoUniverseApp.Id -AppRoleId $appRoleId
                    Write-Host -ForegroundColor Green " [Assigned]"
                }
            }
            else {
                Write-Host -ForegroundColor Green " [OK]"
            }
        }

        Write-Host "Assigning the signed in user the Fortytwo Universe role for 'Bytt.Email - Administrator'..." -NoNewline
        $appRoleId = "ac4b0c05-5bb0-4e7a-811f-36df487eaafe"
        $appRoleAssignments = Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $fortytwoUniverseApp.Id
        
        $me = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/me"
        $userObjectId = $me.id
        $match = $appRoleAssignments | Where-Object { $_.AppRoleId -eq $appRoleId -and $_.PrincipalId -eq $userObjectId }
        if (!$match) {
            if ($WhatIf.IsPresent) {
                Write-Host -ForegroundColor Yellow " [Assigning... (WhatIf)]"
            }
            else {
                $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $fortytwoUniverseApp.Id -PrincipalId $userObjectId -ResourceId $fortytwoUniverseApp.Id -AppRoleId $appRoleId
                Write-Host -ForegroundColor Green " [Assigned]"
            }
        }
        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 = $byttEmailApp.Id -ne "00000000-0000-0000-0000-000000000000" ? (Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $byttEmailApp.Id) : $null
        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) {
                if ($WhatIf.IsPresent) {
                    Write-Host -ForegroundColor Yellow " [Assigning... (WhatIf)]"
                }
                else {
                    $appRoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $byttEmailApp.Id -PrincipalId $group.Id -ResourceId $byttEmailApp.Id -AppRoleId $appRoleId
                    Write-Host -ForegroundColor Green " [Assigned]"
                }
            }
            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"
            }

            if ($WhatIf.IsPresent) {
                Write-Host -ForegroundColor Yellow " [Creating... (WhatIf)]"
            }
            else {
                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

                    if ($WhatIf.IsPresent) {
                        Write-Host -ForegroundColor Yellow " [Updating... (WhatIf)]"
                    }
                    else {
                        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
                }
            }

            if ( $WhatIf.IsPresent) {
                Write-Host -ForegroundColor Yellow " [Granting... (WhatIf)]"
            }
            else {
                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 " [Granted]"
            }
        }
        else {
            Write-Host -ForegroundColor Green " [OK]"
        }
    }
}