Public/Acronis/Start-AcronisAutomatedSetup.ps1

$ApiBase = "https://dk01-cloud.acronis.com"


function Invoke-WebJson {
    param(
        [Parameter(Mandatory)] [string]$Method,
        [Parameter(Mandatory)] [string]$Uri,
        [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [hashtable]$Headers,
        [string]$Body,
        [string]$ContentType = "application/json; charset=UTF-8"
    )
    $hdr = @{'Accept' = 'application/json' }
    if ($Headers) { $hdr += $Headers }
    $params = @{ Method = $Method; Uri = $Uri; UseBasicParsing = $true; Headers = $hdr }
    if ($Session) { $params.WebSession = $Session }
    if ($PSBoundParameters.ContainsKey('Body')) { $params.Body = $Body; $params.ContentType = $ContentType }

    $resp = Invoke-WebRequest @params
    if ($resp -and $resp.Content) {
        try { return ($resp.Content | ConvertFrom-Json) } catch { return $null }
    }
    return $null
}

function Ensure-DynamicUserGroup {
    param(
        [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [Parameter(Mandatory)] [string]$ApiBase,
        [Parameter(Mandatory)] [object[]]$AcronisGroups,
        [Parameter(Mandatory)] [string]$AzureBackupGroupId
    )
    $usersGroup = $AcronisGroups | Where-Object { $_.name -eq 'All users' }
    if (-not $usersGroup) { throw "'All users' group not found in Acronis." }

    $dynamic = $AcronisGroups | Where-Object { $_.name -eq 'AcronisBackup' -and $_.type -eq 'Dynamic' }
    if ($dynamic) { return $dynamic }

    Write-ModuleLog -Message "Creating 'AcronisBackup' dynamic group in Acronis" -Level Info -Component "AcronisAutomatedSetup"
    $createBody = @{ name = 'AcronisBackup'; type = 'Dynamic'; parentId = "$($usersGroup.parentId)"; groupKind = 'USER'; query_azure_group_id = "$AzureBackupGroupId" } | ConvertTo-Json
    $null = Invoke-WebJson -Method 'POST' -Uri "$ApiBase/bc/api/resource_manager/v1/o365/groups?parentId=$($usersGroup.parentId)" -Session $Session -Body $createBody

    # re-fetch groups to get the new ID
    $groups = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/resource_manager/v1/o365/groups" -Session $Session).data
    $dynamic = $groups | Where-Object { $_.name -eq 'AcronisBackup' -and $_.type -eq 'Dynamic' }
    if (-not $dynamic) { throw "Failed to create 'AcronisBackup' dynamic group." }
    return $dynamic
}

function Get-ExistingPlansState {
    param(
        [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [Parameter(Mandatory)] [string]$ApiBase,
        [Parameter(Mandatory)] [string]$UserGroupId,
        [Parameter(Mandatory)] [string]$TeamsGroupId,
        [Parameter(Mandatory)] [string]$SitesGroupId
    )
    $plansUser = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/policy_manager/v2/o365/applied_policies?embed=policy&group=$UserGroupId%3AUSER" -Session $Session).Policies
    $plansTeam = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/policy_manager/v2/o365/applied_policies?embed=policy&group=$TeamsGroupId%3ATEAM" -Session $Session).Policies
    $plansSite = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/policy_manager/v2/o365/applied_policies?embed=policy&group=$SitesGroupId%3ASITE" -Session $Session).Policies

    $plans = @()
    if ($plansUser) { $plans += $plansUser }
    if ($plansTeam) { $plans += $plansTeam }
    if ($plansSite) { $plans += $plansSite }

    $state = [ordered]@{
        HasMailbox    = $false
        HasOneDrive   = $false
        HasTeams      = $false
        HasSharePoint = $false
        Plans         = $plans
    }
    foreach ($p in $plans) {
        if ($p.KIND -eq 'MAILBOX') {
            $state.HasMailbox = $true
            if ($p.name -ne 'Microsoft 365 mailboxes to Cloud storage') {
                $state.MailboxRename = $true
                $state.MailboxRenamePolicyId = $p.id
            }
        }
        elseif ($p.KIND -eq 'DRIVE') {
            $state.HasOneDrive = $true
            if ($p.name -ne 'OneDrive to Cloud storage') {
                $state.OneDriveRename = $true
                $state.OneDriveRenamePolicyId = $p.id
            }
        }
        elseif ($p.KIND -eq 'TEAM') {
            $state.HasTeams = $true
            if ($p.name -ne 'Microsoft Teams to Cloud storage') {
                $state.TeamsRename = $true
                $state.TeamsRenamePolicyId = $p.id
            }
        }
        elseif ($p.KIND -eq 'SITE') {
            $state.HasSharePoint = $true
            if ($p.name -ne 'SharePoint sites to Cloud storage') {
                $state.SharePointRename = $true
                $state.SharePointRenamePolicyId = $p.id
            }
        }
    }
    return $state
}

function New-And-Apply-Policy {
    param(
        [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [Parameter(Mandatory)] [string]$ApiBase,
        [Parameter(Mandatory)] [ValidateSet('MAILBOX', 'DRIVE', 'TEAM', 'SITE')] [string]$Kind,
        [Parameter(Mandatory)] [string]$SelectionGroupId,   # group to select resources from
        [Parameter(Mandatory)] [ValidateSet('USER', 'TEAM', 'SITE')] [string]$SelectionGroupKind,
        [Parameter(Mandatory)] [string]$ApplicableGroupId,  # group used for applicable_policies and apply
        [Parameter(Mandatory)] [ValidateSet('USER', 'TEAM', 'SITE')] [string]$ApplicableSuffix,
        [Parameter(Mandatory)] [string]$PolicyDisplayName
    )
    $uuid = [guid]::NewGuid().ToString()

    $draftBody = @{ id = $uuid; kind = $Kind; name = 'AcronisBackup'; selectedResources = @(@{ id = "$SelectionGroupId"; kind = $SelectionGroupKind; isGroup = $true }) } | ConvertTo-Json -Depth 6
    $draft = Invoke-WebJson -Method 'POST' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/drafts" -Session $Session -Body $draftBody
    $draftId = $draft.policy.Id

    # Set names
    $null = Invoke-WebJson -Method 'PUT' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/drafts/$draftId/default_name" -Session $Session -Body (@{ name = $PolicyDisplayName } | ConvertTo-Json)
    $null = Invoke-WebJson -Method 'PUT' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/drafts/$draftId/name" -Session $Session -Body (@{ name = $PolicyDisplayName } | ConvertTo-Json)

    # Scheduling every 6 hours
    $null = Invoke-WebJson -Method 'PUT' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/drafts/$draftId/scheduling" -Session $Session -Body (@{ backupFrequency = 6 } | ConvertTo-Json)

    # Retention: cleanupByTime, 5 years
    $retention = @{ retentionRules = @{ type = 'cleanupByTime'; backupCount = 1; archiveSize = 1099511627776; backupSets = 'all'; startCleanup = 'after'; time = @(@{ backupSet = 'all'; period = @{ value = 5; type = 'years' } }) } }
    $null = Invoke-WebJson -Method 'PUT' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/drafts/$draftId/retention" -Session $Session -Body ($retention | ConvertTo-Json -Depth 10)

    # Remove encryption
    $null = Invoke-WebJson -Method 'DELETE' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/drafts/$draftId/password" -Session $Session -Body '{}'

    # Publish
    $null = Invoke-WebJson -Method 'POST' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/policies" -Session $Session -Body (@{ draftId = "$draftId" } | ConvertTo-Json)

    # Get applicable and apply
    $appl = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/applicable_policies?group=$ApplicableGroupId%3A$ApplicableSuffix" -Session $Session).data
    $null = Invoke-WebJson -Method 'POST' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/policies/$($appl.id)/apply" -Session $Session -Body (@{ resources = @(@{ id = "$ApplicableGroupId"; type = 'group' }) } | ConvertTo-Json)
}


function Rename-Policy {
    param(
        [Parameter(Mandatory)] [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [Parameter(Mandatory)] [string]$ApiBase,
        [Parameter(Mandatory)] [string]$PolicyKind
    )

    $Policy = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/policies" -Session $Session).data | Where-Object { $_.kind -eq $PolicyKind }
    $groupsData = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/resource_manager/v1/o365/groups" -Session $session).data
    $teamsGroup = $groupsData | Where-Object { $_.name -eq 'All teams' }
    $sitesGroup = $groupsData | Where-Object { $_.name -eq 'All sites' }
    $backupAzureGroup = $aadGroups | Where-Object { $_.name -eq 'AcronisBackup' }
    $dynamicGroup = Ensure-DynamicUserGroup -Session $session -ApiBase $ApiBase -AcronisGroups $groupsData -AzureBackupGroupId $backupAzureGroup.id

    switch ($PolicyKind) {
        'MAILBOX' {
            $selectedResources = @(@{ id = "$($dynamicGroup.id)"; kind = 'USER'; isGroup = $true })
            $NewName = "Microsoft 365 mailboxes to Cloud storage"
        }
        'DRIVE' {
            $selectedResources = @(@{ id = "$($dynamicGroup.id)"; kind = 'USER'; isGroup = $true })
            $NewName = "OneDrive to Cloud storage"
        }
        'TEAM' {
            $selectedResources = @(@{ id = "$($teamsGroup.id)"; kind = 'USER'; isGroup = $true })
            $NewName = "Microsoft Teams to Cloud storage"
        }
        'SITE' {
            $selectedResources = @(@{ id = "$($sitesGroup.id)"; kind = 'USER'; isGroup = $true })
            $NewName = "SharePoint sites to Cloud storage"
        }
    }
    
    $DraftResponse = Invoke-WebJson -Method 'POST' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/drafts" -Session $Session -Body (@{
            planId            = $Policy.id;
            kind              = $PolicyKind;
            selectedResources = $selectedResources
        } | ConvertTo-Json)
    if ($DraftResponse.policy.id) {
        $null = Invoke-WebJson -Method 'PUT' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/drafts/$($DraftResponse.policy.id)/name" -Session $Session -Body (@{ name = $NewName } | ConvertTo-Json)
        $null = Invoke-WebJson -Method 'POST' -Uri "$ApiBase/bc/api/policy_manager/v1/o365/policies" -Session $Session -Body (@{ draftId = $DraftResponse.policy.id } | ConvertTo-Json)
    }
}

function Start-AcronisAutomatedSetup {
    [CmdletBinding()]
    param (
        [Parameter()]
        $CloudFactoryCustomer,
        [Parameter()]
        [string]$TenantId
    )

    begin {
        # Initialize results collection
        $results = [System.Collections.Generic.List[pscustomobject]]::new()
        
        # Determine if $CloudFactoryCustomer or $TenantId is provided
        if (-not $CloudFactoryCustomer -and -not $TenantId) {
            Write-Error "You must provide either CloudFactoryCustomer or TenantId."
            return
        }

        if ($CloudFactoryCustomer) {
            $TenantId = $CloudFactoryCustomer.externalServices.MICROSOFT
            if (-not $TenantId) {
                Write-ModuleLog -Message "CloudFactoryCustomer does not have a Microsoft TenantId." -Level Error -Component "AcronisAutomatedSetup"
                throw "CloudFactoryCustomer does not have a Microsoft TenantId."
            }
        }

        Write-ModuleLog -Message "Finding CloudFactory Customer for TenantId: $TenantId - This could take a while...." -Level Info -Component "AcronisAutomatedSetup"

        $AllCloudFactoryCustomers = Get-CFCustomers -All
        $CloudFactoryCustomer = $AllCloudFactoryCustomers | Where-Object { $_.externalServices.MICROSOFT -eq $TenantId }

        if (-not $CloudFactoryCustomer) {
            Write-ModuleLog -Message "No CloudFactory Customer found for TenantId: $TenantId" -Level Error -Component "AcronisAutomatedSetup"
            throw "No CloudFactory Customer found for TenantId: $TenantId"
        }
        Write-ModuleLog -Message "Verified CloudFactory Customer: $($CloudFactoryCustomer.name)" -Level Info -Component "AcronisAutomatedSetup"
    }


    process {
        if ($CloudFactoryCustomer.externalServices.ACRONIS) {
            Write-ModuleLog -Message "Acronis is already attached to CloudFactory Customer: $($CloudFactoryCustomer.name) -Verifying existing Acronis Customer" -Level Info -Component "AcronisAutomatedSetup"
            try {
                $AcronisCustomer = Get-AcronisCustomerInfo -CustomerId $CloudFactoryCustomer.externalServices.ACRONIS
                Write-ModuleLog -Message "Acronis Customer found: $($AcronisCustomer.name)" -Level Info -Component "AcronisAutomatedSetup"
            }
            catch {
                Write-ModuleLog -Message "Failed to retrieve Acronis Customer information for ID: $($CloudFactoryCustomer.externalServices.ACRONIS)" -Level Error -Component "AcronisAutomatedSetup"
                throw "Failed to retrieve Acronis Customer information for ID: $($CloudFactoryCustomer.externalServices.ACRONIS)"
            }
        }

        else {
            Write-ModuleLog -Message "Acronis is not enabled for CloudFactory Customer: $($CloudFactoryCustomer.name) - Enabling Acronis" -Level Info -Component "AcronisAutomatedSetup"
            try {
                $AcronisTenantName = "$($CloudFactoryCustomer.customerReference) - $($CloudFactoryCustomer.name)"
                $AcronisCustomer = New-AcronisCustomer -CustomerName $AcronisTenantName -CustomerReference $CloudFactoryCustomer.customerReference
                Write-ModuleLog -Message "Acronis Customer created: $($AcronisCustomer.name)" -Level Info -Component "AcronisAutomatedSetup"

                $AddCfExternalService = Add-CFExternalService -CustomerObject $CloudFactoryCustomer -ServiceName "ACRONIS" -Uuid $AcronisCustomer.id
                if (-not $AddCfExternalService) {
                    Write-ModuleLog -Message "Failed to add Acronis external service to CloudFactory Customer: $($CloudFactoryCustomer.name)" -Level Error -Component "AcronisAutomatedSetup"
                    throw "Failed to add Acronis external service to CloudFactory Customer: $($CloudFactoryCustomer.name)"
                }
                Write-ModuleLog -Message "Acronis Customer ID: $($AcronisCustomer.id) added to CloudFactory Customer: $($CloudFactoryCustomer.name)" -Level Info -Component "AcronisAutomatedSetup"
            }
            catch {
                Write-ModuleLog -Message "Failed to create Acronis Customer for CloudFactory Customer: $($CloudFactoryCustomer.name)" -Level Error -Component "AcronisAutomatedSetup"
                throw "Failed to create Acronis Customer for CloudFactory Customer: $($CloudFactoryCustomer.name)"
            }
        }

        try {
            # Enable all offerings for the customer
            Write-ModuleLog -Message "Enabling all Acronis offerings for customer: $($CloudFactoryCustomer.name)" -Level Info -Component "AcronisAutomatedSetup"
            $enabledOfferings = Enable-AcronisAllOfferings -CustomerId $AcronisCustomer.id
            if ($enabledOfferings) {
                Write-ModuleLog -Message "Successfully enabled all Acronis offerings for customer: $($AcronisCustomer.name)" -Level Info -Component "AcronisAutomatedSetup"
            }
            else {
                Write-ModuleLog -Message "No offerings were enabled for customer: $($AcronisCustomer.name)" -Level Warning -Component "AcronisAutomatedSetup"
            }
        }
        catch {
            Write-ModuleLog -Message "Failed to enable all Acronis offerings for customer: $($AcronisCustomer.name)" -Level Error -Component "AcronisAutomatedSetup" -ErrorRecord $_
            throw "Failed to enable all Acronis offerings for customer: $($AcronisCustomer.name)"
        }

        try {
            # Login to Acronis
            $session = Get-AcronisSession
        }
        catch {
            Write-ModuleLog -Message "Failed to establish Acronis session for customer: $($AcronisCustomer.name)" -Level Error -Component "AcronisAutomatedSetup" -ErrorRecord $_
            throw "Failed to establish Acronis session for customer: $($AcronisCustomer.name)"
        }

        try {
            # Refetch the Cloud factory customer to ensure we have the latest data
            $CFCustomer = Get-CFCustomers -CustomerId $CloudFactoryCustomer.id
            $acronisTenant = $CloudFactoryCustomer.externalServices.ACRONIS
            $msTenant = $CloudFactoryCustomer.externalServices.MICROSOFT
            if (-not $acronisTenant -or -not $msTenant) {
                throw "CloudFactory Customer is missing required external services. ACRONIS: $acronisTenant, MICROSOFT: $msTenant"
            }
            $row = [ordered]@{
                CustomerName            = $CFCustomer.Name
                AcronisTenantId         = $acronisTenant
                MicrosoftTenantId       = $msTenant
                SwitchedTenant          = $false
                HasMailboxPlanBefore    = $false
                CreatedMailboxPlan      = $false
                HasOneDrivePlanBefore   = $false
                CreatedOneDrivePlan     = $false
                HasTeamsPlanBefore      = $false
                CreatedTeamsPlan        = $false
                HasSharePointPlanBefore = $false
                CreatedSharePointPlan   = $false
                Success                 = $false
                Errors                  = ''
            }
            $errors = @()
            Write-ModuleLog -Message "Switching to Acronis tenant: $($acronisTenant) - CF Customer: $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"

            try {
                $switchUrl = "$ApiBase/bc/api/gateway/session?tenant_id=$acronisTenant"
                $null = Invoke-WebJson -Method 'PUT' -Uri $switchUrl -Session $session -Body '' -ContentType 'application/json'
                Start-Sleep -Seconds 2
                $confirm = Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/gateway/session" -Session $session
                $currentTenant = $confirm.current_tenant_uuid
                if ($currentTenant -ne $acronisTenant) { throw "Failed to switch to tenant $acronisTenant (current: $currentTenant)" }
                $row.SwitchedTenant = $true
            }
            catch {
                $msg = "Tenant switch error for $($CFCustomer.Name): $($_.Exception.Message)"
                Write-ModuleLog -Message $msg -Level Error -Component "AcronisAutomatedSetup"
                $errors += $msg
                $results.Add([pscustomobject]$row) | Out-Null
                continue
            }

            try {
                # Fetch groups
                $groupsData = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/resource_manager/v1/o365/groups" -Session $session).data
        
                # Check if accountId exists
                if (-not $groupsData -or -not $groupsData.accountId) {
                    # This means the acronis tenant has not correctly linked to the ms tenant - attempt onboarding
                    $onboardUrl = "https://dk01-cloud.acronis.com/api/resource_manager/v1/o365/accounts/auth?admin_consent=True&tenant=$($msTenant)&state=eyJkY0lkIjoibzM2NXdvcmxkd2lkZSJ9"
                    $onboard = Invoke-WebJson -Method 'GET' -Uri $onboardUrl -Session $session
                    # Sleep for 5 seconds to let the acronis tenant catch up
                    Write-ModuleLog -Message "Waiting for Acronis tenant to catch up..." -Level Info -Component "AcronisAutomatedSetup"
                    Start-Sleep -Seconds 5
                    $groupsData = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/resource_manager/v1/o365/groups" -Session $session).data
                }

                $accountId = ($groupsData | Select-Object -First 1 -ExpandProperty accountId)

                $aadGroups = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/resource_manager/v1/o365/accounts/$accountId/azure_ad_groups?limit=1000" -Session $session).items
                $usersGroup = $groupsData | Where-Object { $_.name -eq 'All users' }
                $teamsGroup = $groupsData | Where-Object { $_.name -eq 'All teams' }
                $sitesGroup = $groupsData | Where-Object { $_.name -eq 'All sites' }
                $backupAzureGroup = $aadGroups | Where-Object { $_.name -eq 'AcronisBackup' }

                if (-not $backupAzureGroup) { throw "Azure AD group 'AcronisBackup' not found" }
                if (-not $usersGroup) { throw "Acronis 'All users' group not found" }
                if (-not $teamsGroup) { throw "Acronis 'All teams' group not found" }
                if (-not $sitesGroup) { throw "Acronis 'All sites' group not found" }

                $dynamicGroup = Ensure-DynamicUserGroup -Session $session -ApiBase $ApiBase -AcronisGroups $groupsData -AzureBackupGroupId $backupAzureGroup.id
            
                $state = Get-ExistingPlansState -Session $session -ApiBase $ApiBase -UserGroupId $dynamicGroup.id -TeamsGroupId $teamsGroup.id -SitesGroupId $sitesGroup.id
                $row.HasMailboxPlanBefore = [bool]$state.HasMailbox
                $row.HasOneDrivePlanBefore = [bool]$state.HasOneDrive
                $row.HasTeamsPlanBefore = [bool]$state.HasTeams
                $row.HasSharePointPlanBefore = [bool]$state.HasSharePoint

                if ($state.Plans -and $state.Plans.Count -gt 0) {
                    $plansText = ($state.Plans | ForEach-Object { "[$($_.name) $($_.KIND)]" } | Sort-Object)
                    Write-ModuleLog -Message ("Existing backup plans found: " + ($plansText -join ', ')) -Level Debug -Component "AcronisAutomatedSetup"
                }

                # Create missing policies
                if (-not $state.HasMailbox) {
                    try {
                        New-And-Apply-Policy -Session $session -ApiBase $ApiBase -Kind 'MAILBOX' -SelectionGroupId $dynamicGroup.id -SelectionGroupKind 'USER' -ApplicableGroupId $dynamicGroup.id -ApplicableSuffix 'USER' -PolicyDisplayName 'Microsoft 365 mailboxes to Cloud storage'
                        $row.CreatedMailboxPlan = $true
                        Write-ModuleLog -Message "Mailbox policy created and applied for $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"
                    }
                    catch { $msg = "Mailbox policy error: $($_.Exception.Message)"; Write-Log $msg 'ERROR'; $errors += $msg }
                }
                elseif ($state.MailboxRename) {
                    try {
                        Rename-Policy -Session $session -ApiBase $ApiBase -PolicyKind 'MAILBOX'
                        $row.RenamedMailboxPlan = $true
                        Write-ModuleLog -Message "Mailbox policy renamed for $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"
                    }
                    catch { $msg = "Mailbox policy error: $($_.Exception.Message)"; Write-Log $msg 'ERROR'; $errors += $msg }
                }

                if (-not $state.HasOneDrive) {
                    try {
                        New-And-Apply-Policy -Session $session -ApiBase $ApiBase -Kind 'DRIVE' -SelectionGroupId $dynamicGroup.id -SelectionGroupKind 'USER' -ApplicableGroupId $dynamicGroup.id -ApplicableSuffix 'USER' -PolicyDisplayName 'OneDrive to Cloud storage'
                        $row.CreatedOneDrivePlan = $true
                        Write-ModuleLog -Message "OneDrive policy created and applied for $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"
                    }
                    catch { $msg = "OneDrive policy error: $($_.Exception.Message)"; Write-Log $msg 'ERROR'; $errors += $msg }
                }
                elseif ($state.OneDriveRename) {
                    try {
                        Rename-Policy -Session $session -ApiBase $ApiBase -PolicyKind 'DRIVE'
                        $row.RenamedOneDrivePlan = $true
                        Write-ModuleLog -Message "OneDrive policy renamed for $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"
                    }
                    catch { $msg = "OneDrive policy error: $($_.Exception.Message)"; Write-Log $msg 'ERROR'; $errors += $msg }
                }

                if (-not $state.HasTeams) {
                    try {
                        # Check if the team group is empty
                        $teamsGroupContent = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/resource_manager/v1/o365/groups/$($teamsGroup.id)/resources?limit=30" -Session $session)
                        if ($teamsGroupContent.items -and $teamsGroupContent.items.Count -gt 0) {
                            New-And-Apply-Policy -Session $session -ApiBase $ApiBase -Kind 'TEAM' -SelectionGroupId $teamsGroup.id -SelectionGroupKind 'TEAM' -ApplicableGroupId $teamsGroup.id -ApplicableSuffix 'TEAM' -PolicyDisplayName 'Microsoft Teams to Cloud storage'
                            $row.CreatedTeamsPlan = $true
                            Write-ModuleLog -Message "Teams policy created and applied for $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"
                        }
                        else {
                            Write-ModuleLog -Message "Nothing to backup in teams, all is good.." -Level Info -Component "AcronisAutomatedSetup"
                        }
                    }
                    catch { $msg = "Teams policy error: $($_.Exception.Message)"; Write-ModuleLog -Message $msg -Level Error -Component "AcronisAutomatedSetup"; $errors += $msg }
                }
                elseif ($state.TeamsRename) {
                    try {
                        Rename-Policy -Session $session -ApiBase $ApiBase -PolicyKind 'TEAM'
                        $row.RenamedTeamsPlan = $true
                        Write-ModuleLog -Message "Teams policy renamed for $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"
                    }
                    catch { $msg = "Teams policy error: $($_.Exception.Message)"; Write-Log $msg 'ERROR'; $errors += $msg }
                }

                if (-not $state.HasSharePoint) {
                    try {
                        # Check if the site group is empty
                        $sitesGroupContent = (Invoke-WebJson -Method 'GET' -Uri "$ApiBase/bc/api/resource_manager/v1/o365/groups/$($sitesGroup.id)/resources?limit=30" -Session $session)
                        if ($sitesGroupContent.items -and $sitesGroupContent.items.Count -gt 0) {
                            New-And-Apply-Policy -Session $session -ApiBase $ApiBase -Kind 'SITE' -SelectionGroupId $sitesGroup.id -SelectionGroupKind 'SITE' -ApplicableGroupId $sitesGroup.id -ApplicableSuffix 'SITE' -PolicyDisplayName 'SharePoint sites to Cloud storage'
                            $row.CreatedSharePointPlan = $true
                            Write-ModuleLog -Message "SharePoint policy created and applied for $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"
                        }
                        else {
                            Write-ModuleLog -Message "Nothing to backup in sites, all is good.." -Level Info -Component "AcronisAutomatedSetup"
                        }
                    }
                    catch { $msg = "SharePoint policy error: $($_.Exception.Message)"; Write-ModuleLog -Message $msg -Level Error -Component "AcronisAutomatedSetup"; $errors += $msg }
                }
                elseif ($state.SharePointRename) {
                    try {
                        Rename-Policy -Session $session -ApiBase $ApiBase -PolicyKind 'SITE'
                        $row.RenamedSharePointPlan = $true
                        Write-ModuleLog -Message "SharePoint policy renamed for $($CFCustomer.Name)" -Level Info -Component "AcronisAutomatedSetup"
                    }
                    catch { $msg = "SharePoint policy rename error: $($_.Exception.Message)"; Write-ModuleLog -Message $msg -Level Error -Component "AcronisAutomatedSetup"; $errors += $msg }
                }

                # Final verification (optional lightweight re-check)
                try {
                    $final = Get-ExistingPlansState -Session $session -ApiBase $ApiBase -UserGroupId $dynamicGroup.id -TeamsGroupId $teamsGroup.id -SitesGroupId $sitesGroup.id
                    $row.Success = [bool]($final.HasMailbox -and $final.HasOneDrive -and $final.HasTeams -and $final.HasSharePoint)
                }
                catch { $msg = "Final verification error: $($_.Exception.Message)"; Write-ModuleLog -Message $msg -Level Error -Component "AcronisAutomatedSetup"; $errors += $msg }
            }
            catch {
                $msg = "Unhandled error for customer $($CFCustomer.Name): $($_.Exception.Message)"
                Write-ModuleLog -Message $msg -Level Error -Component "AcronisAutomatedSetup"
                $errors += $msg
            }

            if ($errors.Count -gt 0) { $row.Errors = ($errors -join '; ') }
            $results.Add([pscustomobject]$row) | Out-Null

        }
        catch {
            Write-ModuleLog -Message "Critical error processing customer: $($_.Exception.Message)" -Level Error -Component "AcronisAutomatedSetup" -ErrorRecord $_
            # Add a basic error result if we haven't already added one
            if (-not $row) {
                $row = [ordered]@{
                    CustomerName            = "Unknown"
                    AcronisTenantId         = "Unknown"
                    MicrosoftTenantId       = $TenantId
                    SwitchedTenant          = $false
                    HasMailboxPlanBefore    = $false
                    CreatedMailboxPlan      = $false
                    HasOneDrivePlanBefore   = $false
                    CreatedOneDrivePlan     = $false
                    HasTeamsPlanBefore      = $false
                    CreatedTeamsPlan        = $false
                    HasSharePointPlanBefore = $false
                    CreatedSharePointPlan   = $false
                    Success                 = $false
                    Errors                  = "Critical error: $($_.Exception.Message)"
                }
            }
            else {
                $row.Success = $false
                if ([string]::IsNullOrEmpty($row.Errors)) {
                    $row.Errors = "Critical error: $($_.Exception.Message)"
                }
                else {
                    $row.Errors += "; Critical error: $($_.Exception.Message)"
                }
            }
            $results.Add([pscustomobject]$row) | Out-Null
        }

    }

    end {
        # Display single customer results
        if ($results.Count -gt 0) {
            $result = $results[0]  # Since this is for a single customer, get the first (and only) result
            
            Write-ModuleLog -Message "Acronis Automated Setup completed for: $($result.CustomerName)" -Level Info -Component "AcronisAutomatedSetup"
            
            if ($result.Success) {
                Write-ModuleLog -Message "Setup completed successfully!" -Level Info -Component "AcronisAutomatedSetup"
            } else {
                Write-ModuleLog -Message "Setup completed with errors." -Level Warning -Component "AcronisAutomatedSetup"
            }
            
            # Display what was accomplished
            Write-Host ""
            Write-Host "Setup Results:" -ForegroundColor Cyan
            Write-Host "Customer: $($result.CustomerName)" -ForegroundColor White
            Write-Host "Acronis Tenant ID: $($result.AcronisTenantId)" -ForegroundColor White
            Write-Host "Microsoft Tenant ID: $($result.MicrosoftTenantId)" -ForegroundColor White
            Write-Host ""
            
            Write-Host "Backup Plans:" -ForegroundColor Cyan
            if ($result.CreatedMailboxPlan) {
                Write-Host "✓ Mailbox backup plan created" -ForegroundColor Green
            } elseif ($result.HasMailboxPlanBefore) {
                Write-Host "✓ Mailbox backup plan already existed" -ForegroundColor Yellow
            } else {
                Write-Host "✗ Mailbox backup plan not created" -ForegroundColor Red
            }
            
            if ($result.CreatedOneDrivePlan) {
                Write-Host "✓ OneDrive backup plan created" -ForegroundColor Green
            } elseif ($result.HasOneDrivePlanBefore) {
                Write-Host "✓ OneDrive backup plan already existed" -ForegroundColor Yellow
            } else {
                Write-Host "✗ OneDrive backup plan not created" -ForegroundColor Red
            }
            
            if ($result.CreatedTeamsPlan) {
                Write-Host "✓ Teams backup plan created" -ForegroundColor Green
            } elseif ($result.HasTeamsPlanBefore) {
                Write-Host "✓ Teams backup plan already existed" -ForegroundColor Yellow
            } else {
                Write-Host "✗ Teams backup plan not created" -ForegroundColor Red
            }
            
            if ($result.CreatedSharePointPlan) {
                Write-Host "✓ SharePoint backup plan created" -ForegroundColor Green
            } elseif ($result.HasSharePointPlanBefore) {
                Write-Host "✓ SharePoint backup plan already existed" -ForegroundColor Yellow
            } else {
                Write-Host "✗ SharePoint backup plan not created" -ForegroundColor Red
            }
            
            # Show errors if any
            if (-not [string]::IsNullOrEmpty($result.Errors)) {
                Write-Host ""
                Write-Host "Errors encountered:" -ForegroundColor Red
                Write-Host $result.Errors -ForegroundColor Red
            }
            
            Write-Host ""
        }
        else {
            Write-ModuleLog -Message "No customer was processed." -Level Warning -Component "AcronisAutomatedSetup"
        }
    }
}