internal/functions/invoke-commercecompile.ps1


<#
    .SYNOPSIS
        Invoke the D365Commerce compilation
         
    .DESCRIPTION
        Invoke the D365Commerce compilation
         
    .PARAMETER Version
        The version of the D365Commerce used to build
         
    .PARAMETER SourcesPath
        The folder contains a metadata files with binaries
         
    .PARAMETER BuildFolderPath
        The destination build folder
         
    .PARAMETER Force
        Cleanup destination build folder befor build
         
    .EXAMPLE
        PS C:\> Invoke-FSCPSCompile -Version 10.0.39
         
        Example output:
         
        BUILD_FOLDER_PATH : c:\temp\fscps.tools\_bld\10.0.39_build
        BUILD_LOG_FILE_PATH : C:\Users\Administrator\AppData\Local\Temp\ScaleUnit.sln.msbuild.log
        CSU_ZIP_PATH : c:\temp\fscps.tools\_bld\artifacts\CloudScaleUnitExtensionPackage.Master-ContosoForD365Commerce-10.0.39_20240530.48.zip
        HW_INSTALLER_PATH : c:\temp\fscps.tools\_bld\artifacts\Contoso.HardwareStation.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe
        SC_INSTALLER_PATH : c:\temp\fscps.tools\_bld\artifacts\Contoso.StoreCommerce.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe
        SU_INSTALLER_PATH : c:\temp\fscps.tools\_bld\artifacts\Contoso.ScaleUnit.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe
        PACKAGE_NAME : Master-ContosoForD365Commerce-10.0.39_20240530.48
        ARTIFACTS_PATH : c:\temp\fscps.tools\_bld\artifacts
        ARTIFACTS_LIST : ["C:\\temp\\fscps.tools\\_bld\\artifacts\\CloudScaleUnitExtensionPackage.Master-ContosoForD365Commerce-10.0.39_20240530.48.zip",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\POS.2.2.63.1.nupkg",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.Commerce.Runtime.Master-ContosoForD365Commerce-10.2.2.63.1.nupkg",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.HardwareStation.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.ScaleUnit.2.2.63.1.nupkg",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.ScaleUnit.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.StoreCommerce.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\ContosoAddressWebService.2.2.63.1.nupkg",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\ContosoWebService.2.2.63.1.nupkg"]
         
        This will build D365FSC package with version "10.0.39" to the Temp folder
         
    .EXAMPLE
        PS C:\> Invoke-FSCPSCompile -SourcesPath "D:\Sources\connector-d365-commerce\"
         
        Example output:
         
        BUILD_FOLDER_PATH : c:\temp\fscps.tools\_bld\10.0.39_build
        BUILD_LOG_FILE_PATH : C:\Users\Administrator\AppData\Local\Temp\ScaleUnit.sln.msbuild.log
        CSU_ZIP_PATH : c:\temp\fscps.tools\_bld\artifacts\CloudScaleUnitExtensionPackage.Master-ContosoForD365Commerce-10.0.39_20240530.48.zip
        HW_INSTALLER_PATH : c:\temp\fscps.tools\_bld\artifacts\Contoso.HardwareStation.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe
        SC_INSTALLER_PATH : c:\temp\fscps.tools\_bld\artifacts\Contoso.StoreCommerce.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe
        SU_INSTALLER_PATH : c:\temp\fscps.tools\_bld\artifacts\Contoso.ScaleUnit.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe
        PACKAGE_NAME : Master-ContosoForD365Commerce-10.0.39_20240530.48
        ARTIFACTS_PATH : c:\temp\fscps.tools\_bld\artifacts
        ARTIFACTS_LIST : ["C:\\temp\\fscps.tools\\_bld\\artifacts\\CloudScaleUnitExtensionPackage.Master-ContosoForD365Commerce-10.0.39_20240530.48.zip",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\POS.2.2.63.1.nupkg",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.Commerce.Runtime.Master-ContosoForD365Commerce-10.2.2.63.1.nupkg",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.HardwareStation.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.ScaleUnit.2.2.63.1.nupkg",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.ScaleUnit.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\Contoso.StoreCommerce.Installer.Master-ContosoForD365Commerce-10.0.39_20240530.48.exe",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\ContosoAddressWebService.2.2.63.1.nupkg",
        "C:\\temp\\fscps.tools\\_bld\\artifacts\\ContosoWebService.2.2.63.1.nupkg"]
         
        This will build D365FSC package with version "10.0.39" to the Temp folder
         
    .NOTES
        Author: Oleksandr Nikolaiev (@onikolaiev)
         
#>


function Invoke-CommerceCompile {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression", "")]
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param (
        [string] $Version,
        [Parameter(Mandatory = $true)]
        [string] $SourcesPath,
        [string] $BuildFolderPath = (Join-Path $script:DefaultTempPath _bld),
        [switch] $Force
    )

    BEGIN {
        Invoke-TimeSignal -Start
        $helperPath = Join-Path -Path $($Script:ModuleRoot) -ChildPath "\internal\scripts\helpers.ps1" -Resolve
        . ($helperPath)        
        try{
            $CMDOUT = @{
                Verbose = If ($PSBoundParameters.Verbose -eq $true) { $true } else { $false };
                Debug = If ($PSBoundParameters.Debug -eq $true) { $true } else { $false }
            }
            $responseObject = [Ordered]@{}
            Write-PSFMessage -Level Important -Message "//================= Reading current FSC-PS settings ============================//"
            $settings = Get-FSCPSSettings @CMDOUT
            Write-PSFMessage -Level Important -Message "Complete"
            #if($Force)
            #{
                Write-PSFMessage -Level Important -Message "//================= Cleanup build folder =======================================//"
                Remove-Item $BuildFolderPath -Recurse -Force -ErrorAction SilentlyContinue
                Write-PSFMessage -Level Important -Message "Complete"
            #}

            if($settings.artifactsPath -eq "")
            {
                $artifactDirectory = (Join-Path $BuildFolderPath $settings.artifactsFolderName)
            }
            else {
                $artifactDirectory = $settings.artifactsPath
            }

            if (Test-Path -Path $artifactDirectory -ErrorAction SilentlyContinue)
            {
                Remove-Item -Path $artifactDirectory -Recurse -Force -ErrorAction SilentlyContinue
                
            }

            if (!(Test-Path -Path $artifactDirectory))
            {
                $null = [System.IO.Directory]::CreateDirectory($artifactDirectory)
            }
            Get-ChildItem $artifactDirectory -Recurse
            if($Version -eq "")
            {
                $Version = $settings.buildVersion
            }

            if($Version -eq "")
            {
                throw "D365FSC Version should be specified."
            }            

            # Gather version info
            #$versionData = Get-FSCPSVersionInfo -Version $Version @CMDOUT

            $SolutionBuildFolderPath = (Join-Path $BuildFolderPath "$($Version)_build")
            $responseObject.BUILD_FOLDER_PATH = $SolutionBuildFolderPath
        }
        catch {
            Write-PSFMessage -Level Host -Message "Error: " -Exception $PSItem.Exception
            Stop-PSFFunction -Message "Stopping because of errors" -EnableException $true
            return
        }
        finally{
            
        }
    }
    
    PROCESS {
        if (Test-PSFFunctionInterrupt) { return }
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        try {               
            Write-PSFMessage -Level Important -Message "//================= Copy source files to the build folder ======================//"            
            $null = Test-PathExists -Path $BuildFolderPath -Type Container -Create @CMDOUT
            $null = Test-PathExists -Path $SolutionBuildFolderPath -Type Container -Create @CMDOUT
            Copy-Item $SourcesPath\* -Destination $SolutionBuildFolderPath -Recurse -Force @CMDOUT
            Write-PSFMessage -Level Important -Message "Complete"

            Write-PSFMessage -Level Important -Message "//================= Build solution =============================================//"

            $msbuildpath = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -products * -requires Microsoft.Component.MSBuild -property installationPath -latest

            $origLocation = Get-Location
            Set-Location $SolutionBuildFolderPath

            if($msbuildpath -ne "")
            {
                $msbuildexepath = Join-Path $msbuildpath "MSBuild\Current\Bin\MSBuild.exe"
                $msbuildresult = Invoke-MsBuild -Path (Join-Path $SolutionBuildFolderPath $settings.solutionName) -MsBuildParameters "/t:restore,rebuild /property:Configuration=Release /property:NuGetInteractive=true /property:BuildingInsideVisualStudio=false" -MsBuildFilePath "$msbuildexepath" -ShowBuildOutputInCurrentWindow -BypassVisualStudioDeveloperCommandPrompt @CMDOUT
            }
            else
            {
                $msbuildresult = Invoke-MsBuild -Path (Join-Path $SolutionBuildFolderPath $settings.solutionName) -MsBuildParameters "/t:restore,rebuild /property:Configuration=Release /property:NuGetInteractive=true /property:BuildingInsideVisualStudio=false" -ShowBuildOutputInCurrentWindow @CMDOUT
            }

            $responseObject.BUILD_LOG_FILE_PATH = $msbuildresult.BuildLogFilePath

            if ($msbuildresult.BuildSucceeded -eq $true)
            {
                Write-PSFMessage -Level Host -Message ("Build completed successfully in {0:N1} seconds." -f $msbuildresult.BuildDuration.TotalSeconds)
            }
            elseif ($msbuildresult.BuildSucceeded -eq $false)
            {
               throw ("Build failed after {0:N1} seconds. Check the build log file '$($msbuildresult.BuildLogFilePath)' for errors." -f $msbuildresult.BuildDuration.TotalSeconds)
            }
            elseif ($null -eq $msbuildresult.BuildSucceeded)
            {
                throw "Unsure if build passed or failed: $($msbuildresult.Message)"
            }
            Set-Location $origLocation
            if($settings.generatePackages)
            {
                Write-PSFMessage -Level Important -Message "//================= Generate package ==========================================//"

                switch ($settings.namingStrategy) {
                    { $settings.namingStrategy -eq "Default" }
                    {
                        $packageNamePattern = $settings.packageNamePattern;
                        if($settings.packageName.Contains('.zip'))
                        {
                            $packageName = $settings.packageName
                        }
                        else {
                            $packageName = $settings.packageName
                        }
                        $packageNamePattern = $packageNamePattern.Replace("BRANCHNAME", $($settings.sourceBranch))
                        if($settings.deploy)
                        {
                            $packageNamePattern = $packageNamePattern.Replace("PACKAGENAME", $settings.azVMName)
                        }
                        else
                        {
                            $packageNamePattern = $packageNamePattern.Replace("PACKAGENAME", $packageName)
                        }
                        $packageNamePattern = $packageNamePattern.Replace("FNSCMVERSION", $Version)
                        $packageNamePattern = $packageNamePattern.Replace("DATE", (Get-Date -Format "yyyyMMdd").ToString())
                        
                        $packageNamePattern = $packageNamePattern.Replace("RUNNUMBER", $settings.runId)

                        $packageName = $packageNamePattern
                        break;
                    }
                    { $settings.namingStrategy -eq "Custom" }
                    {
                        if($settings.packageName.Contains('.zip'))
                        {
                            $packageName = $settings.packageName
                        }
                        else {
                            $packageName = $settings.packageName + ".zip"
                        }
                        
                        break;
                    }
                    Default {
                        $packageName = $settings.packageName
                        break;
                    }
                }             
                

                [System.IO.DirectoryInfo]$csuZipPackagePath = Get-ChildItem -Path $SolutionBuildFolderPath -Recurse | Where-Object {$_.FullName -match "bin.*.Release.*ScaleUnit.*.zip$"} | ForEach-Object {$_.FullName}
                [System.IO.DirectoryInfo]$hWSInstallerPath = Get-ChildItem -Path $SolutionBuildFolderPath -Recurse | Where-Object {$_.FullName -match "bin.*.Release.*HardwareStation.*.exe$"} | ForEach-Object {$_.FullName}
                [System.IO.DirectoryInfo]$sCInstallerPath = Get-ChildItem -Path $SolutionBuildFolderPath -Recurse | Where-Object {$_.FullName -match "bin.*.Release.*StoreCommerce.*.exe$"} | ForEach-Object {$_.FullName}
                [System.IO.DirectoryInfo]$sUInstallerPath = Get-ChildItem -Path $SolutionBuildFolderPath -Recurse | Where-Object {$_.FullName -match "bin.*.Release.*ScaleUnit.*.exe$"} | ForEach-Object {$_.FullName}
                
                Write-PSFMessage -Level Important -Message "//================= Copy packages to the artifacts folder ======================//"
                if($csuZipPackagePath)
                {    
                    Write-PSFMessage -Level Important -Message "CSU Package processing..."
                    Write-PSFMessage -Level Important -Message $csuZipPackagePath
                    if($settings.cleanupCSUPackage)
                    {
                        $null = [Reflection.Assembly]::LoadWithPartialName('System.IO.Compression')
                        $zipfile = $csuZipPackagePath
                        $stream = New-Object IO.FileStream($zipfile, [IO.FileMode]::Open)
                        $mode   = [IO.Compression.ZipArchiveMode]::Update
                        $zip    = New-Object IO.Compression.ZipArchive($stream, $mode)
                        ($zip.Entries | Where-Object { $_.Name -match 'Azure' }) | ForEach-Object { $_.Delete() }
                        ($zip.Entries | Where-Object { $_.Name -match 'Microsoft' }) | ForEach-Object { $_.Delete() }
                        ($zip.Entries | Where-Object { $_.Name -match 'System'  -and $_.Name -notmatch 'System.Runtime.Caching' -and $_.Name -notmatch 'System.ServiceModel.Http' -and $_.Name -notmatch 'System.ServiceModel.Primitives' -and $_.Name -notmatch 'System.Private.ServiceModel' -and $_.Name -notmatch 'System.Configuration.ConfigurationManager' -and $_.Name -notmatch 'System.Security.Cryptography.ProtectedData' -and $_.Name -notmatch 'System.Security.Permissions' -and $_.Name -notmatch 'System.Security.Cryptography.Xml' -and $_.Name -notmatch 'System.Security.Cryptography.Pkcs' }) | ForEach-Object { $_.Delete() }
                        ($zip.Entries | Where-Object { $_.Name -match 'Newtonsoft' }) | ForEach-Object { $_.Delete() }
                        $zip.Dispose()
                        $stream.Close()
                        $stream.Dispose()
                    }
                    $destinationFullName = (Join-Path $($artifactDirectory) "$(ClearExtension($csuZipPackagePath)).$($packageName).zip")
                    Copy-ToDestination -RelativePath $csuZipPackagePath.Parent.FullName -File $csuZipPackagePath.BaseName -DestinationFullName $destinationFullName
                    $responseObject.CSU_ZIP_PATH = $destinationFullName
                }
                if($hWSInstallerPath)
                {    
                    Write-PSFMessage -Level Important -Message "HW Package processing..."
                    Write-PSFMessage -Level Important -Message $hWSInstallerPath
                    $destinationFullName = (Join-Path $($artifactDirectory) "$(ClearExtension($hWSInstallerPath)).$($packageName).exe")
                    Copy-ToDestination -RelativePath $hWSInstallerPath.Parent.FullName -File $hWSInstallerPath.BaseName -DestinationFullName $destinationFullName
                    $responseObject.HW_INSTALLER_PATH = $destinationFullName
                }
                if($sCInstallerPath)
                {    
                    Write-PSFMessage -Level Important -Message "SC Package processing..."
                    Write-PSFMessage -Level Important -Message $sCInstallerPath
                    $destinationFullName = (Join-Path $($artifactDirectory) "$(ClearExtension($sCInstallerPath)).$($packageName).exe")
                    Copy-ToDestination -RelativePath $sCInstallerPath.Parent.FullName -File $sCInstallerPath.BaseName -DestinationFullName $destinationFullName
                    $responseObject.SC_INSTALLER_PATH = $destinationFullName
                }
                if($sUInstallerPath)
                {    
                    Write-PSFMessage -Level Important -Message "SU Package processing..."
                    Write-PSFMessage -Level Important -Message $sUInstallerPath
                    $destinationFullName = (Join-Path $($artifactDirectory) "$(ClearExtension($sUInstallerPath)).$($packageName).exe")
                    Copy-ToDestination -RelativePath $sUInstallerPath.Parent.FullName -File $sUInstallerPath.BaseName -DestinationFullName $destinationFullName
                    $responseObject.SU_INSTALLER_PATH = $destinationFullName
                }

                Write-PSFMessage -Level Important -Message "//================= Export NuGets ===============================================//"
                Get-ChildItem -Path $BuildFolderPath -Recurse | Where-Object {$_.FullName -match "bin.*.Release.*.nupkg$"} | ForEach-Object {
                    if($settings.cleanupNugets)
                    {                
                        $zipfile = $_
                        # Cleanup NuGet file
                        $null = [Reflection.Assembly]::LoadWithPartialName('System.IO.Compression')            
                        $stream = New-Object IO.FileStream($zipfile.FullName, [IO.FileMode]::Open)
                        $mode   = [IO.Compression.ZipArchiveMode]::Update
                        $zip    = New-Object IO.Compression.ZipArchive($stream, $mode)
                        ($zip.Entries | Where-Object { $_.Name -match 'Azure' }) | ForEach-Object { $_.Delete() }
                        ($zip.Entries | Where-Object { $_.Name -match 'Microsoft' }) | ForEach-Object { $_.Delete() }
                        ($zip.Entries | Where-Object { $_.Name -match 'System' }) | ForEach-Object { $_.Delete() }
                        ($zip.Entries | Where-Object { $_.Name -match 'Newtonsoft' }) | ForEach-Object { $_.Delete() }
                        $zip.Dispose()
                        $stream.Close()
                        $stream.Dispose()
                    }
                    Copy-ToDestination -RelativePath $_.Directory -File $_.Name -DestinationFullName "$($artifactDirectory)\$($_.BaseName).nupkg"        
                }

                $responseObject.PACKAGE_NAME = $packageName
                $responseObject.ARTIFACTS_PATH = $artifactDirectory


                $artifacts = Get-ChildItem $artifactDirectory
                $artifactsList = $artifacts.FullName -join ","

                if($artifactsList.Contains(','))
                {
                    $artifacts = $artifactsList.Split(',') | ConvertTo-Json -compress
                }
                else
                {
                    $artifacts = '["'+$($artifactsList).ToString()+'"]'

                }

                $responseObject.ARTIFACTS_LIST = $artifacts
                        
            }
        }
        catch {
            Write-PSFMessage -Level Host -Message "Error: " -Exception $PSItem.Exception
            Stop-PSFFunction -Message "Stopping because of errors" -EnableException $true
            return
        }
        finally{
            try {
                if($SolutionBuildFolderPath)
                {
                    if (Test-Path -Path $SolutionBuildFolderPath -ErrorAction SilentlyContinue)
                    {
                        Remove-Item -Path $SolutionBuildFolderPath -Recurse -Force -ErrorAction SilentlyContinue
                    }
                }
                if($NuGetPackagesPath)
                {
                    if (Test-Path -Path $NuGetPackagesPath -ErrorAction SilentlyContinue)
                    {
                        Remove-Item -Path $NuGetPackagesPath -Recurse -Force -ErrorAction SilentlyContinue
                    }
                }
                if($outputDir)
                {
                    if (Test-Path -Path $outputDir -ErrorAction SilentlyContinue)
                    {
                        Remove-Item -Path $outputDir -Recurse -Force -ErrorAction SilentlyContinue
                    }
                }
                if($tempCombinedPackage)
                {
                    if (Test-Path -Path $tempCombinedPackage -ErrorAction SilentlyContinue)
                    {
                        Remove-Item -Path $tempCombinedPackage -Recurse -Force -ErrorAction SilentlyContinue
                    }
                }
                Set-Location $origLocation
            }
            catch {
                Write-PSFMessage -Level Verbose -Message "Cleanup warning: $($PSItem.Exception)" 
            }
            $responseObject
        }
    }
    END {
        Invoke-TimeSignal -End
    }
}