Scripts/ExportSolution.ps1

# ExportSolution.ps1
function Get-DataverseSolution {
    Param(
        [string] [Parameter(Mandatory = $true)] $StartPath,
        [string] [Parameter(Mandatory = $true)] $SelectedSolution
    )
    try {
        $SolutionName = $SelectedSolution
        ######################## 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
            Unpack-Solution "$SolutionName" -IsPatchExport $false
        }
        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 managed and unmanaged zip files
                        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

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

                    # Export and unpack
                    Unpack-Solution "$($PatchSolution.uniquename)" -IsPatchExport $true
                }
            }

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

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

    }
}

function Unpack-Solution {
    Param(
        [string] [Parameter(Mandatory = $true)] $ExportSolutionName,
        [bool] [Parameter(Mandatory = $true)] $IsPatchExport = $false
    )
    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
            $unmanagedExportCommand = "solution export -n $ExportSolutionName -p $deployFilesFolderPath --managed false --async --max-async-wait-time 120"
            Write-Host "Running export command: $pacExePath $unmanagedExportCommand"
            & $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
            $managedExportCommand = "solution export -n $ExportSolutionName -p $deployFilesFolderPath --managed true --async --max-async-wait-time 120"
            Write-Host "Running export command: $pacExePath $managedExportCommand"
            & $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) {
                Write-Host "Solution exported successfully: $unmanagedExportedZipFilePath"            

                # If $IsPatchExport unpack to Patches folder. Otherwise unpack to src folder
                if ($IsPatchExport) {
                    Write-Host "Patch solution $ExportSolutionName exported"
                    $destinationFolderPath = (Join-Path $StartPath "Patches\$ExportSolutionName\")
                }
                else {
                    Write-Host "Full solution exported"
                    Remove-Item (Join-Path $StartPath "src\.") -Force -Recurse -ErrorAction Ignore
                    $destinationFolderPath = (Join-Path $StartPath "src\")
                }
            
                Write-Host "Unpacking to destination folder path - " $destinationFolderPath

                # Unpack the solution (extract the content)
                $unpackCommand = "solution unpack --zipfile $unmanagedExportedZipFilePath --folder $destinationFolderPath --packagetype Both --processCanvasApps true"
                Write-Host "Running unpack command: $pacExePath $unpackCommand"
                & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution unpack --zipfile $unmanagedExportedZipFilePath --folder $destinationFolderPath --packagetype Both --processCanvasApps true
            } 
            else {
                Write-Host "Solution export of $ExportSolutionName failed!"
            }
        }
    }
    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"
        $SolutionPath = (Join-Path $StartPath "src\Workflows")
        $FlowJSON = @()
        $AddedFlows = @()

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

        # If there are patches to check too
        if ($patchSolutionNames.Count -gt 0) {
            foreach ($PatchName in $patchSolutionNames) {
                $SolutionPath = (Join-Path $StartPath "Patches\$PatchName\Workflows")
                $PatchesFlowJSON = @()

                $Workflows = Get-ChildItem -Path $SolutionPath -Filter *.json -ErrorAction SilentlyContinue
                if ($Workflows) {
                    $Workflows | ForEach-Object {
                        $FlowName = $_.BaseName.SubString(0, $_.BaseName.Length - 36)
                        $FlowID = $_.BaseName.Replace($FlowName, '')

                        # If the flow isn't already in the list, add it
                        if ($AddedFlows -notcontains $FlowID) {
                            $FlowJSON += @([ordered]@{FlowID = $FlowID; FlowName = $FlowName; ActivateAsUser = ""; })
                            $AddedFlows += $FlowID
                        }
                    }
                }
            }
        }

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