private/export/Invoke-ZtTenantDataExport.ps1

function Invoke-ZtTenantDataExport {
    <#
    .SYNOPSIS
        Executes a single Entra export tasks as specified.
 
    .DESCRIPTION
        Executes a single Entra export tasks as specified.
        This command assumes it is called from within the parallelized export workflow started by Export-ZtTenantData.
 
        It is the location where we translate the configuration entries in the "export-tenant.config.psd1" config file into action.
        Also where all the tracking and metadata collecting happens, to ensure we can troubleshoot issues.
 
    .PARAMETER Export
        The export settings to realize.
 
    .PARAMETER ExportPath
        The path where the tenant data gets exported to.
 
    .PARAMETER DependencyTimeout
        How long we are willing to wait for another export process that we depend on to complete.
        If this time expires, our export will also fail.
 
    .EXAMPLE
        PS C:\> Invoke-ZtTenantDataExport -Export $_ -DependencyTimeout $dependencyTimeout -ExportPath $exportPath
 
        Executes the provided tenant export configuration to the path specified in $exportPath
    #>

    [CmdletBinding()]
    param (
        [hashtable]
        $Export,

        [string]
        $ExportPath,

        [Psftimespan]
        $DependencyTimeout
    )
    begin {
        $previousMessages = Get-PSFMessage -Runspace ([runspace]::DefaultRunspace.InstanceId)
        $result = [PSCustomObject]@{
            PSTypeName = 'ZeroTrustAssessment.ExportStatistics'
            Name       = $Export.Name
            Export     = $Export

            # Performance Metrics, in case we want to identify problematic exports
            Start      = $null
            End        = $null
            Duration   = $null

            # What Happened?
            Success    = $true
            Error      = $null
            Messages   = $null

            # Exports should have no output, but we'll catch it anyways, just in case
            Output     = $null
        }
        $workflow = $global:__PSF_Workflow
        # Dummy in case this function is run outside of the Runspace Workflow it is intended for
        if (-not $workflow) {
            $workflow = @{ Data = @{ } }
            $workflow.Data[$Export.Name] = [PSCustomObject]@{
                Status  = 'Pending'
                Name    = $Export.Name
                Updated = Get-Date
                Message = ''
            }
        }
        $identity = New-Guid
    }
    process {
        Write-PSFMessage -Message "Processing export '{0}'" -StringValues $Export.Name -Target $Export -Tag start

        #region Wait for Dependencies
        if ($Export.DependsOn) {
            $exportState = Get-ZtConfig -ExportPath $ExportPath -Property $Export.DependsOn
            $start = Get-Date
            $limit = $start.Add($DependencyTimeout)

            $workflow.Data[$Export.Name].Status = 'Waiting'
            $workflow.Data[$Export.Name].Updated = Get-Date

            while (-not $exportState -and $workflow.Data[$Export.DependsOn].Status -ne 'Done') {
                Write-PSFMessage -Message "Export '{0}' depends on '{1}', which is not yet completed" -StringValues $Export.Name, $Export.DependsOn -Once "ZeroTrust-$($Export.Name)-$($identity)" -Target $Export

                # Case: Dependency does not exist
                if (-not $workflow.Data[$Export.DependsOn]) {
                    Write-PSFMessage -Level Warning -Message "Dependency '{0}' is not registered for export and has not previously been exported. No succes is possible, skipping Processing." -StringValues $Export.DependsOn, "$($workflow.Data[$Export.DependsOn].Status)" -Target $Export -Tag end
                    $result.Success = $false
                    $result.Error = 'Dependency Failed'
                    $workflow.Data[$Export.Name].Status = 'Failed'
                    $workflow.Data[$Export.Name].Updated = Get-Date
                    $workflow.Data[$Export.Name].Message = "Dependency '$($Export.DependsOn)' not exported and not scheduled to be so."
                    return
                }

                # Case: Dependency Failed
                if ($workflow.Data[$Export.DependsOn].Status -notin 'Pending', 'Waiting', 'InProgress', 'Done') {
                    Write-PSFMessage -Level Warning -Message "Dependency '{0}' is in state '{1}', which there is no presumed way of recovery from. Skipping Processing." -StringValues $Export.DependsOn, "$($workflow.Data[$Export.DependsOn].Status)" -Target $Export -Tag end
                    $result.Success = $false
                    $result.Error = 'Dependency Failed'
                    $workflow.Data[$Export.Name].Status = 'Failed'
                    $workflow.Data[$Export.Name].Updated = Get-Date
                    $workflow.Data[$Export.Name].Message = "Dependency '$($Export.DependsOn)' not expected to succeed. Status: $($workflow.Data[$Export.DependsOn].Status)"
                    return
                }

                # Case: Timeout Reached
                if ($limit -lt (Get-Date)) {
                    Write-PSFMessage -Level Warning -Message "Timeout waiting for dependency '{0}' to complete (current state: '{1}'). Skipping Processing." -StringValues $Export.DependsOn, "$($workflow.Data[$Export.DependsOn].Status)" -Target $Export -Tag end
                    $result.Success = $false
                    $result.Error = 'Dependency Failed'
                    $workflow.Data[$Export.Name].Status = 'Failed'
                    $workflow.Data[$Export.Name].Updated = Get-Date
                    $workflow.Data[$Export.Name].Message = "Timeout waiting for dependency '$($Export.DependsOn)' to complete."
                    return
                }

                Start-Sleep -Seconds 5
            }
        }
        #endregion Wait for Dependencies

        try {
            $result.Start = Get-Date
            $workflow.Data[$Export.Name].Status = 'InProgress'
            switch ($Export.Type) {
                PrivilegedGroup {
                    $exportParam = $Export | ConvertTo-PSFHashtable -ReferenceCommand Export-ZtGraphEntityPrivilegedGroup
                    $result.Output = Export-ZtGraphEntityPrivilegedGroup @exportParam -ExportPath $ExportPath -ErrorAction Stop
                }
                default {
                    $exportParam = $Export | ConvertTo-PSFHashtable -ReferenceCommand Export-ZtGraphEntity
                    $result.Output = Export-ZtGraphEntity @exportParam -ExportPath $ExportPath -ErrorAction Stop
                }
            }
        }
        catch {
            Write-PSFMessage -Level Warning -Message "Error executing export '{0}'" -StringValues $Export.Name -Target $Export -ErrorRecord $_
            $workflow.Data[$Export.Name].Status = 'Failed'
            $workflow.Data[$Export.Name].Updated = Get-Date
            $workflow.Data[$Export.Name].Message = "$_"
            $result.Success = $false
            $result.Error = $_
        }
        finally {
            $result.End = Get-Date
            $result.Duration = $result.End - $result.Start
            if ($workflow.Data[$Export.Name].Status -ne 'Failed') {
                $workflow.Data[$Export.Name].Status = 'Done'
                $workflow.Data[$Export.Name].Updated = Get-Date
            }
        }
        Write-PSFMessage -Message "Processing test '{0}' - Concluded" -StringValues $Export.Name -Target $Export -Tag end
    }
    end {
        $result.Messages = Get-PSFMessage -Runspace ([runspace]::DefaultRunspace.InstanceId) | Where-Object { $_ -notin $previousMessages }
        $result
    }
}