Scripts/ExportSolution.ps1

# ExportSolution.ps1
function Get-DataverseSolution {
    Param(
        [string] [Parameter(Mandatory = $true)] $StartPath,
        [string] [Parameter(Mandatory = $true)] $SolutionName
    )
    try {
        ######################## EXPORT SOLUTION ########################

        # If patch version numbers can change, get existing values
        if ($global:devops_projectFile.IncrementLatestPatchOnExport -eq "True") {
            # Get current solution versions in files before updating them
            try {
                $versionsFile = Get-Content -Path $global:devops_projectLocation\$global:devops_SolutionName\$global:devops_SolutionName.version -ErrorAction SilentlyContinue | ConvertFrom-Json
                # Sort by version
                $existingPatchVersions = $versionsFile | Sort-Object { [version]$_.Version }
            }
            catch {
                #Legacy Solution Packaging Support
                $versionsFile = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile
                $existingPatchVersions = @([ordered]@{SolutionName = $package.SolutionName; Version = $versionsFile ; })
            }
        }

        # Update version file
        Set-DataverseSolutionVersion

        # Delete any files in old formats / file structures that still exist from Microsoft.PowerPlatform.DevOps
        Remove-Item (Join-Path $StartPath "\dataverse_$SolutionName") -Force -Recurse -ErrorAction Ignore
        Remove-Item (Join-Path $StartPath "\dataverse_*patch*") -Force -Recurse -ErrorAction Ignore
        Remove-Item (Join-Path $StartPath "\pac_$SolutionName") -Force -Recurse -ErrorAction Ignore
        Remove-Item (Join-Path $StartPath "\pac_*patch*") -Force -Recurse -ErrorAction Ignore

        $patchFilesFolderPath = (Join-Path $StartPath "Patches\")
        $deployFilesFolderPath = (Join-Path $StartPath "Deploy\")

        $patchSolutionNames = @()

        # If there are no patches
        if (!$PatchQuery.CrmRecords) {
            # Delete any patch files
            Remove-Item $patchFilesFolderPath -Force -Recurse -ErrorAction Ignore
            Remove-Item $deployFilesFolderPath -Force -Recurse -ErrorAction Ignore

            # Export base solution
            Export-SolutionAndUnpack "$SolutionName"
        }
        else {
            # Export Patches
            # If project config says to increment latest patch
            if ($global:devops_projectFile.IncrementLatestPatchOnExport -eq "True") {
                Write-Host "Exporting all patches with changed version numbers"
                $patchCount = $PatchQuery.CrmRecords.Count
                $iterator = 1
                foreach ($PatchSolution in $PatchQuery.CrmRecords) {
                    $patchSolutionNames += $PatchSolution.uniquename

                    $existingPatch = $existingPatchVersions | Where-Object { $PatchSolution.uniquename -eq $_.SolutionName }

                    # If version number in CRM is different to existing file
                    if ([version]$existingPatch[0].Version -eq [version]$PatchSolution.version) {
                        Write-Host "Skipping patch $iterator of $patchCount due to unchanged version number: $($PatchSolution.uniquename)"
                    }
                    else {
                        Write-Host "Exporting patch $iterator of $patchCount"
                        # Remove the existing files for the patch
                        Remove-Item -Path (Join-Path $deployFilesFolderPath "$($PatchSolution.uniquename).zip") -Force -ErrorAction SilentlyContinue
                        Remove-Item -Path (Join-Path $deployFilesFolderPath "$($PatchSolution.uniquename)_managed.zip") -Force -ErrorAction SilentlyContinue
                        Remove-Item -Path (Join-Path $patchFilesFolderPath $($PatchSolution.uniquename)) -Recurse -Force -ErrorAction SilentlyContinue

                        # Export and unpack
                        Export-SolutionAndUnpack "$($PatchSolution.uniquename)" -IsPatch
                    }
                    $iterator += 1
                }
            }
            # Else (patches have static versions)
            else {
                foreach ($PatchSolution in $PatchQuery.CrmRecords) {
                    $patchSolutionNames += $PatchSolution.uniquename
                    Export-SolutionAndUnpack "$($PatchSolution.uniquename)" -IsPatch
                }
            }

            # Delete old patch files
            $exportedPatchSolutionNames = Get-ChildItem -Path $patchFilesFolderPath -Directory | Select-Object -ExpandProperty Name
            $filteredPatchSolutionNames = $exportedPatchSolutionNames | Where-Object { $patchSolutionNames -notcontains $_ }
            foreach ($patchName in $filteredPatchSolutionNames) {
                Write-Host "Deleting patch: $patchName"
            
                # Remove the folder and its contents
                Remove-Item -Path (Join-Path $patchFilesFolderPath $patchName) -Recurse -Force -ErrorAction SilentlyContinue
                
                # Remove the managed and unmanaged zip files
                Remove-Item -Path (Join-Path $deployFilesFolderPath "$patchName.zip") -Force -ErrorAction SilentlyContinue
                Remove-Item -Path (Join-Path $deployFilesFolderPath "$($patchName)_managed.zip") -Force -ErrorAction SilentlyContinue
                
                Write-Host "Deleted patch: $patchName"
            }
        }
        
        Get-FlowsToBeDeployed "$StartPath" $patchSolutionNames

        Get-ExportDataValid
    }
    catch {
        Write-Host $_
        pause
    }
}

function Export-SolutionAndUnpack {
    Param(
        [string] [Parameter(Mandatory = $true)] $ExportSolutionName,
        [switch] $IsPatch
    )
    try {
        $pacExePath = "$env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe"
        $deployFilesFolderPath = (Join-Path $StartPath "Deploy\")
        
        # Define the full path for the exported ZIP file
        $unmanagedExportedZipFilePath = Join-Path $deployFilesFolderPath "$ExportSolutionName.zip"
        Write-Host "Unmanaged solution file name: $unmanagedExportedZipFilePath"
        if (!(Test-Path -Path $unmanagedExportedZipFilePath)) {

            # Export the unmanaged solution to a ZIP file
            Write-Host "Exporting Unmanaged Solution"
            & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution export -n $ExportSolutionName -p $deployFilesFolderPath --managed false --async --max-async-wait-time 120
        
            # Export the managed solution to a ZIP file
            Write-Host "Exporting Managed Solution"
            & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution export -n $ExportSolutionName -p $deployFilesFolderPath --managed true --async --max-async-wait-time 120
        
            # Check if the export was successful
            if (Test-Path -Path $unmanagedExportedZipFilePath) {
                # If this is a patch, unpack to Patches folder. Otherwise unpack to src folder
                if ($IsPatch) {
                    Write-Host "Patch solution $ExportSolutionName exported"
                    $destinationFolderPath = (Join-Path $StartPath "Patches\$ExportSolutionName\")
                }
                else {
                    Write-Host "Solution exported"
                    Remove-Item (Join-Path $StartPath "src\.") -Force -Recurse -ErrorAction Ignore
                    $destinationFolderPath = (Join-Path $StartPath "src\")
                }

                # Unpack the solution (extract the content)
                Write-Host "Unpacking to destination folder path - " $destinationFolderPath
                & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution unpack --zipfile $unmanagedExportedZipFilePath --folder $destinationFolderPath --packagetype Both --processCanvasApps true
            } 
            else {
                Write-PPDOMessage -Message "Solution export of $ExportSolutionName failed - unable to find output file." -Type 'error' -LogError $true
            }
        }
    }
    catch {
        Write-Host $_
        pause
    }
}

function Get-FlowsToBeDeployed {
    Param(
        [string] [Parameter(Mandatory = $true)] $StartPath,
        [array] [Parameter(Mandatory = $false)] $patchSolutionNames
    )
    try {
        Write-Host "Generating Flows_Default.json for activating post-deploy"
        $FlowLocations = @(Join-Path $StartPath "src\Workflows")
        $FlowJSON = @()
        $AddedFlows = @()

        # If there are patches
        if ($patchSolutionNames.Count -gt 0) {
            foreach ($PatchName in $patchSolutionNames) {
                $FlowLocations += (Join-Path $StartPath "Patches\$PatchName\Workflows")
            }
        }

        foreach ($FlowLocation in $FlowLocations) {
            $Workflows = Get-ChildItem -Path $FlowLocation -Filter *.json -ErrorAction SilentlyContinue
            if ($Workflows) {
                $Workflows | ForEach-Object {
                    $FlowName = $_.BaseName.SubString(0, $_.BaseName.Length - 36)
                    $FlowID = $_.BaseName.Replace($FlowName, '')
                    if ($AddedFlows -notcontains $FlowID) {
                        $FlowJSON += @([ordered]@{FlowID = $FlowID; FlowName = $FlowName; ActivateAsUser = ""; })
                        $AddedFlows += $FlowID
                    }
                }
            }
        }

        if ($FlowJSON.Count -gt 0) {
            ConvertTo-Json -Depth 3 $FlowJSON | Format-Json | Out-FileUtf8NoBom $StartPath\Flows_Default.json
        }
    }
    catch {
        Write-Host $_
        pause
    }
}