AppHandling/Sign-NavContainerApp.ps1

<#
 .Synopsis
  Uses a NAV/BC Container to sign an App
 .Description
  appFile must be shared with the container
  Copies the pfxFile to the container if necessary
  Creates a session to the container and Signs the App using the provided certificate and password
 .Parameter containerName
  Name of the container in which you want to publish an app
 .Parameter appFile
  Path of the app you want to sign
 .Parameter pfxFile
  Path/Url of the certificate pfx file to use for signing
 .Parameter pfxPassword
  Password of the certificate pfx file
 .Parameter timeStampServer
  Specifies the URL of the time stamp server. Default is $bcContainerHelperConfig.timeStampServer, which defaults to http://timestamp.digicert.com
 .Example
  Sign-BcContainerApp -appFile c:\programdata\bccontainerhelper\myapp.app -pfxFile http://my.secure.url/mycert.pfx -pfxPassword $securePassword
 .Example
  Sign-BcContainerApp -appFile c:\programdata\bccontainerhelper\myapp.app -pfxFile c:\programdata\bccontainerhelper\mycert.pfx -pfxPassword $securePassword
#>

function Sign-BcContainerApp {
    Param (
        [string] $containerName = $bcContainerHelperConfig.defaultContainerName,
        [Parameter(Mandatory=$true)]
        [string] $appFile,
        [Parameter(Mandatory=$true)]
        [string] $pfxFile,
        [Parameter(Mandatory=$true)]
        [SecureString] $pfxPassword,
        [Parameter(Mandatory=$false)]
        [string] $timeStampServer = $bcContainerHelperConfig.timeStampServer,
        [Parameter(Mandatory=$false)]
        [string] $digestAlgorithm = $bcContainerHelperConfig.digestAlgorithm,
        [switch] $importCertificate
    )

$telemetryScope = InitTelemetryScope -name $MyInvocation.InvocationName -parameterValues $PSBoundParameters -includeParameters @()
try {

    $containerAppFile = Get-BcContainerPath -containerName $containerName -path $appFile
    if ("$containerAppFile" -eq "") {
        throw "The app ($appFile)needs to be in a folder, which is shared with the container $containerName"
    }

    $sharedPfxFile = Join-Path $bcContainerHelperConfig.hostHelperFolder "Extensions\$containerName\my\$([GUID]::NewGuid().ToString()).pfx"
    $removeSharedPfxFile = $true
    if ($pfxFile -like "https://*" -or $pfxFile -like "http://*") {
        Write-Host "Downloading certificate file to container"
        Download-File -sourceUrl $pfxFile -destinationFile $sharedPfxFile
    }
    else {
        if (Get-BcContainerPath -containerName $containerName -path $pfxFile) {
            $sharedPfxFile = $pfxFile
            $removeSharedPfxFile = $false
        }
        else {
            Write-Host "Copying certificate file to container"
            Copy-Item -Path $pfxFile -Destination $sharedPfxFile -Force
        }
    }

    try {
        TestPfxCertificate -pfxFile $sharedPfxFile -pfxPassword $pfxPassword -certkind "Codesign"

        Invoke-ScriptInBcContainer -containerName $containerName -useSession:$false -ScriptBlock { Param($appFile, $pfxFile, $pfxPassword, $timeStampServer, $digestAlgorithm, $importCertificate)

            function GetExtendedErrorMessage {
                Param(
                    $errorRecord
                )
            
                $exception = $errorRecord.Exception
                $message = $exception.Message
            
                try {
                    $errorDetails = $errorRecord.ErrorDetails | ConvertFrom-Json
                    $message += " $($errorDetails.error)`r`n$($errorDetails.error_description)"
                }
                catch {}
                try {
                    if ($exception -is [System.Management.Automation.MethodInvocationException]) {
                        $exception = $exception.InnerException
                    }
                    $webException = [System.Net.WebException]$exception
                    $webResponse = $webException.Response
                    try {
                        if ($webResponse.StatusDescription) {
                            $message += "`r`n$($webResponse.StatusDescription)"
                        }
                    } catch {}
                    $reqstream = $webResponse.GetResponseStream()
                    $sr = new-object System.IO.StreamReader $reqstream
                    $result = $sr.ReadToEnd()
                    try {
                        $json = $result | ConvertFrom-Json
                        $message += "`r`n$($json.Message)"
                    }
                    catch {
                        $message += "`r`n$result"
                    }
                    try {
                        $correlationX = $webResponse.GetResponseHeader('ms-correlation-x')
                        if ($correlationX) {
                            $message += " (ms-correlation-x = $correlationX)"
                        }
                    }
                    catch {}
                }
                catch{}
                $message
            }

            if ($importCertificate) {
                Import-PfxCertificate -FilePath $pfxFile -Password $pfxPassword -CertStoreLocation "cert:\localMachine\root" | Out-Null
                Import-PfxCertificate -FilePath $pfxFile -Password $pfxPassword -CertStoreLocation "cert:\localMachine\my" | Out-Null
            }
    
            if (!(Test-Path "C:\Windows\System32\msvcr120.dll")) {
                Write-Host "Downloading vcredist_x86"
                (New-Object System.Net.WebClient).DownloadFile('https://bcartifacts.blob.core.windows.net/prerequisites/vcredist_x86.exe','c:\run\install\vcredist_x86.exe')
                Write-Host "Installing vcredist_x86"
                start-process -Wait -FilePath c:\run\install\vcredist_x86.exe -ArgumentList /q, /norestart
                Write-Host "Downloading vcredist_x64"
                (New-Object System.Net.WebClient).DownloadFile('https://bcartifacts.blob.core.windows.net/prerequisites/vcredist_x64.exe','c:\run\install\vcredist_x64.exe')
                Write-Host "Installing vcredist_x64"
                start-process -Wait -FilePath c:\run\install\vcredist_x64.exe -ArgumentList /q, /norestart
            }

            if (!(Test-Path "C:\Windows\System32\vcruntime140_1.dll")) {
                Write-Host "Downloading vcredist_x64 (version 140)"
                (New-Object System.Net.WebClient).DownloadFile('https://aka.ms/vs/17/release/vc_redist.x64.exe','c:\run\install\vcredist_x64-140.exe')
                Write-Host "Installing vcredist_x64 (version 140)"
                start-process -Wait -FilePath c:\run\install\vcredist_x64-140.exe -ArgumentList /q, /norestart
            }
    
            if (Test-Path "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\SignTool.exe") {
                $signToolExe = (get-item "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\SignTool.exe").FullName
            } else {
                Write-Host "Downloading Signing Tools"
                $winSdkSetupExe = "c:\run\install\winsdksetup.exe"
                $winSdkSetupUrl = "https://bcartifacts.blob.core.windows.net/prerequisites/winsdksetup.exe"
                (New-Object System.Net.WebClient).DownloadFile($winSdkSetupUrl,$winSdkSetupExe)
                Write-Host "Installing Signing Tools"
                Start-Process $winSdkSetupExe -ArgumentList "/features OptionId.SigningTools /q" -Wait
                if (!(Test-Path "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\SignTool.exe")) {
                    throw "Cannot locate signtool.exe after installation"
                }
                $signToolExe = (get-item "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\SignTool.exe").FullName
            }

            Write-Host "Signing $appFile"
            $unsecurepassword = ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pfxPassword)))
            $attempt = 1
            $maxAttempts = 5
            do {
                try {
                    if ($digestAlgorithm) {
                        & "$signtoolexe" @("sign", "/f", "$pfxFile", "/p","$unsecurepassword", "/fd", $digestAlgorithm, "/td", $digestAlgorithm, "/tr", "$timeStampServer", "$appFile") | Write-Host
                    }
                    else {
                        & "$signtoolexe" @("sign", "/f", "$pfxFile", "/p","$unsecurepassword", "/t", "$timeStampServer", "$appFile") | Write-Host
                    }
                    break
                } catch {
                    if ($attempt -ge $maxAttempts) {
                        throw
                    }
                    else {
                        $seconds = [Math]::Pow(4,$attempt)
                        Write-Host "Signing failed, retrying in $seconds seconds"
                        $attempt++
                        Start-Sleep -Seconds $seconds
                    }
                }
            } while ($attempt -le $maxAttempts)
        } -ArgumentList $containerAppFile, (Get-BcContainerPath -containerName $containerName -path $sharedPfxFile), $pfxPassword, $timeStampServer, $digestAlgorithm, $importCertificate
    }
    finally {
        if ($removeSharedPfxFile -and (Test-Path $sharedPfxFile)) {
            Remove-Item -Path $sharedPfxFile -Force
        }
    }
}
catch {
    TrackException -telemetryScope $telemetryScope -errorRecord $_
    throw
}
finally {
    TrackTrace -telemetryScope $telemetryScope
}
}
Set-Alias -Name Sign-NavContainerApp -Value Sign-BcContainerApp
Export-ModuleMember -Function Sign-BcContainerApp -Alias Sign-NavContainerApp