functions/agreements/Export-TmfAgreement.ps1


<#
.SYNOPSIS
Exports agreements and associated files.
.DESCRIPTION
Retrieves agreements (v1.0) with optional fallback listing and downloads localized files via multiple strategies (with optional beta fallback for content). Returns objects unless -OutPath supplied.
.PARAMETER SpecificResources
Optional list of agreement IDs or display names (comma separated accepted) to filter.
.PARAMETER OutPath
Root folder to write export; when omitted objects are returned (files not downloaded). Legacy alias -OutPutPath is deprecated.
.PARAMETER Append
Add content to existing file
.PARAMETER ForceBeta
Use beta endpoint for metadata retrieval (still attempts v1.0 first where stable); file strategies continue to fall back as needed.
.PARAMETER AllowBetaContentFallback
Permit beta endpoint attempts for file content if v1.0 paths fail.
.PARAMETER ContinueOnListFailure
Continue execution even if initial listing fails (may yield empty output).
.PARAMETER Cmdlet
Internal pipeline parameter; do not supply manually.
.EXAMPLE
Export-TmfAgreement -OutPath C:\temp\tmf -AllowBetaContentFallback
.EXAMPLE
Export-TmfAgreement -SpecificResources 'Privacy Policy' -OutPath C:\temp\tmf
#>

function Export-TmfAgreement {
    [CmdletBinding()] param(
        [string[]] $SpecificResources,
        [Alias('OutPutPath')] [string] $OutPath,
        [switch] $Append,
        [switch] $ForceBeta,
        [switch] $AllowBetaContentFallback,
        [switch] $ContinueOnListFailure,
        [System.Management.Automation.PSCmdlet] $Cmdlet = $PSCmdlet
    )
    begin {
        Test-GraphConnection -Cmdlet $Cmdlet
        $resourceName = 'agreements'
        $agreementsExport = @()
        $graph = if ($ForceBeta) {
            $script:graphBaseUrlBeta 
        } else {
            $script:graphBaseUrl1 
        }
        $graphBeta = $script:graphBaseUrlBeta
        $graphIG = "$graph/identityGovernance/termsOfUse"
        $graphIGBeta = "$graphBeta/identityGovernance/termsOfUse"
        try {
            $ctx = Get-MgContext -ErrorAction Stop; if ($ctx -and $ctx.Scopes -and -not ($ctx.Scopes | Where-Object { $_ -like 'Agreement.*' })) {
            } 
        } catch { 
            Write-PSFMessage -Level Verbose -Message 'Current token scopes lack Agreement.*; downloads may fail.' 
        }
    }
    process {
        if ($OutPath) {
            $resourceFolderPath = Join-Path -Path $OutPath -ChildPath $resourceName; if (-not (Test-Path $resourceFolderPath)) {
                New-Item -Path $resourceFolderPath -ItemType Directory -Force | Out-Null 
            }; $filesFolder = Join-Path -Path $resourceFolderPath -ChildPath 'files'; if (-not (Test-Path $filesFolder)) {
                New-Item -Path $filesFolder -ItemType Directory -Force | Out-Null; Write-PSFMessage -Level Verbose -String 'TMF.Export.CreatedDirectory' -StringValues $filesFolder 
            } 
        }
        $allAgreements = @(); $uri = "$graph/agreements?`$select=id,displayName,isViewingBeforeAcceptanceRequired,isPerDeviceAcceptanceRequired,userReacceptRequiredFrequency,termsExpiration"; try {
            while ($uri) {
                $resp = Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop; if ($resp.value) {
                    $allAgreements += $resp.value 
                }; $uri = $resp.'@odata.nextLink' 
            } 
        } catch {
            $igListSucceeded = $false; try {
                Write-PSFMessage -Level Verbose -Message 'Attempting fallback listing via identityGovernance path'; $igUri = "$graphIG/agreements?`$select=id,displayName,isViewingBeforeAcceptanceRequired,isPerDeviceAcceptanceRequired,userReacceptRequiredFrequency,termsExpiration"; while ($igUri) {
                    $respIG = Invoke-MgGraphRequest -Method GET -Uri $igUri -ErrorAction Stop; if ($respIG.value) {
                        $allAgreements += $respIG.value 
                    }; $igUri = $respIG.'@odata.nextLink' 
                }; if ($allAgreements.Count -gt 0) {
                    $igListSucceeded = $true 
                } 
            } catch { 
                Write-PSFMessage -Level Verbose -Message "Fallback retrieval through identityGovernance path failed: $($Error[0].Exception)"
            } if (-not $ContinueOnListFailure -and -not $igListSucceeded) {
                throw 
            } elseif (-not $igListSucceeded) {
                Write-PSFMessage -Level Warning -Message 'Continuing despite agreement list failure.' 
            } 
        }
        if ($SpecificResources) {
            $ids = @(); foreach ($e in $SpecificResources) {
                $ids += $e -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ } 
            }; $ids = $ids | Select-Object -Unique; $allAgreements = $allAgreements | Where-Object { $ids -contains $_.id -or $ids -contains $_.displayName } 
        }
        foreach ($agreement in $allAgreements) {
            $obj = [ordered]@{}
            foreach ($p in $agreement.GetEnumerator()) {
                if ($p.Key -ne "files" -and $p.Key -ne "id" -and $null -ne $p.Value) {
                    $obj[$p.Key] = $p.Value
                }
            }
            $obj["files"] = @()
            $obj["present"] = $true
            try {
                try {
                    $localizations = (Invoke-MgGraphRequest -Method GET -Uri "$graphIG/agreements/$($agreement.id)/file/localizations?`$select=id,fileName,language,isDefault").value; if (-not $localizations) {
                        $localizations = (Invoke-MgGraphRequest -Method GET -Uri "$graph/agreements/$($agreement.id)/file/localizations?`$select=id,fileName,language,isDefault").value 
                    } if (-not $localizations) {
                        $igFiles = (Invoke-MgGraphRequest -Method GET -Uri "$graphIG/agreements/$($agreement.id)/files?`$select=id,displayName").value; foreach ($igFile in ($igFiles | Where-Object { $_ })) {
                            $fileLocs = (Invoke-MgGraphRequest -Method GET -Uri "$graphIG/agreements/$($agreement.id)/files/$($igFile.id)/localizations?`$select=id,fileName,language,isDefault").value; if (-not $fileLocs) {
                                $fileLocs = (Invoke-MgGraphRequest -Method GET -Uri "$graph/agreements/$($agreement.id)/files/$($igFile.id)/localizations?`$select=id,fileName,language,isDefault").value 
                            }; if ($fileLocs) {
                                $localizations += $fileLocs 
                            } 
                        } 
                    } 
                } catch { 
                    Write-PSFMessage -Level Warning -String 'TMF.Export.LocalizationsRetrievalFailed' -StringValues $agreement.displayName, $_.Exception.Message 
                }
                if ($localizations) {
                    foreach ($loc in $localizations) {
                        $targetPath = Join-Path -Path $filesFolder -ChildPath $loc.fileName
                        $relativePath = 'files/' + [IO.Path]::GetFileName($targetPath) 
                        $obj.files += [ordered]@{ fileName = $loc.fileName; language = $loc.language; isDefault = $loc.isDefault; filePath = $relativePath }
                        if ($OutPath) {
                            try {
                                $filePath = $targetPath 
                                $fileDataResponse = Invoke-MgGraphRequest -Method GET -Uri "$graph/agreements/$($agreement.id)/file/localizations/$($loc.id)/filedata/data" -ContentType "application/json"
                                $fileBytes = [Convert]::FromBase64String($fileDataResponse.value)
                                [System.IO.File]::WriteAllBytes($filePath, $fileBytes)
                            } catch {
                                Write-PSFMessage -Level Warning -String 'TMF.Export.FileDownloadFailed' -StringValues $loc.fileName, $_.Exception.Message 
                            } 
                        } 
                    } 
                }
                if (-not $localizations) {
                    $files = (Invoke-MgGraphRequest -Method GET -Uri "$graph/agreements/$($agreement.id)/files?`$select=id,fileName,language,isDefault").value; if ($files) {
                        foreach ($file in $files) {
                            $targetPath = Join-Path -Path $filesFolder -ChildPath $file.fileName
                            $relativePath = 'files/' + [IO.Path]::GetFileName($targetPath) 
                            
                            $obj.files += [ordered]@{ fileName = $file.fileName; language = $file.language; isDefault = $file.isDefault; filePath = $relativePath }
                            if ($OutPath) {
                                try {
                                    $filePath = $targetPath

                                    $fileDataResponse = Invoke-MgGraphRequest -Method GET -Uri "$graph/agreements/$($agreement.id)/files/$($file.id)/filedata/data" -ContentType "application/json"
                                    $fileBytes = [Convert]::FromBase64String($fileDataResponse.value)
                                    [System.IO.File]::WriteAllBytes($filePath, $fileBytes)
                                } catch {
                                    Write-PSFMessage -Level Warning -String 'TMF.Export.FileDownloadFailed' -StringValues $file.fileName, $_.Exception.Message 
                                } 
                            } 
                        } 
                    } 
                }
            } catch {
                Write-PSFMessage -Level Warning -String 'TMF.Export.FileRetrievalFailed' -StringValues $agreement.displayName, $_.Exception.Message 
            }
            $agreementsExport += $obj
        }
        if (-not $OutPath) {
            return $agreementsExport 
        }
    }
    end {
        if (-not $OutPath) {
            return 
        }
        $resourceFolderPath = Join-Path -Path $OutPath -ChildPath $resourceName
        if (-not (Test-Path $resourceFolderPath)) {
            New-Item -Path $resourceFolderPath -ItemType Directory -Force | Out-Null 
        }
        if ($agreementsExport) {
            if ($Append) {
                Write-TmfExportFile -OutPath $OutPath -ResourceName $resourceName -Data $agreementsExport -Append
            }
            else {
                Write-TmfExportFile -OutPath $OutPath -ResourceName $resourceName -Data $agreementsExport
            }
        }
    }
}