1
\$\begingroup\$

Background

I decided to write build script as a gentle introduction to PowerShell. It builds and packages this Go application, which is an internal tool I decided to open source, and not the focus of this review.

This script is used primarily for packaging artifacts for release.

My approach

The script below gets, vets and builds a small Go application and then invokes a separate command-line tool to add Windows rsrc metadata such as version strings and icons.

Since the version number is mandatory for the packaging process, I validated it using param() rules rather than if statements. It validates against a regex for semver.

I then execute every packing step, check for its return status, and present a graceful message in addition to the error output by the build step.

The packager relies on having rcedit-x64.exe available in the PATH. I originally allowed specifying a path to the executable as a command-line flag, but it seemed to make the code messier for nothing, so I just removed it. The tool is primarily for myself.

Example

Invocation

powershell.exe -ExecutionPolicy Unrestricted .\build.ps1 -version 1.1.0

Output

Packing bittray version 1.1.0
Cleaning old build products.
go get...
go vet...
go build...
Validating artifact...
Applying rsrc metadata...
Compressing archive (bittray-1.1.0.zip)...
SHA1 hash of bittray-1.1.0.zip:
cd283afd10f613919bb4dc694bce3c1f9bd23483
CertUtil: -hashfile command completed successfully.
Done!

Concerns

  1. Does the code follow PS idioms?
  2. Is there a more elegant way to check for the success of commands and bail than repeating if (!$?) all the time?
  3. To wit, is the single trap appropriate?
  4. Is the code adequately defensive? Overly defensive?
  5. I cannot sign my script as I don't have a code signing certificate. Is my invocation correct and safe?
  6. Is there a shorter invocation available without altering the system-side Execution Policy?
  7. Because of the validator on -version, you need to provide a valid version to be able to run just -clean. Any way around that (like providing some order of precedence of param()s, or short-cutting)? I really like those top-level validators, and would prefer to keep them in favor of if statements, if possible.

Code

param(
    [string]
    [Parameter(Mandatory = $True)]
    [ValidateNotNullorEmpty()]
    [ValidatePattern('^([0-9]|[1-9][0-9]*)\.([0-9]|[1-9][0-9]*)\.([0-9]|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$')]
    $version,

    [switch]
    $clean
)

function Clean-Artifacts
{
    Write-Host "Cleaning old build products."
    Remove-Item * -Include bittray.exe, bittray-*.zip
    if (!$?)
    {
        Write-Host -BackgroundColor red -ForegroundColor white "Failed to remove existing artifacts; see above."
        exit 1
    }
}

if ($clean -eq $True)
{
    Clean-Artifacts
    exit 0
}

if ((Get-Command "rcedit-x64.exe" -ErrorAction SilentlyContinue) -eq $null)
{
    Write-Host -ForegroundColor red "Unable to find rcedit-x64.exe in your PATH."
    exit 1
}

Write-Host "Packing bittray version $version" -ForegroundColor green

Clean-Artifacts

Write-Host "go get..."
go get

Write-Host "go vet..."
go vet ./...
if (!$?)
{
    Write-Host -BackgroundColor red -ForegroundColor white "'go vet' failed; see above."
    exit 1
}

Write-Host "go build..."
go build -ldflags -H=windowsgui bittray.go
if (!$?)
{
    Write-Host -BackgroundColor red -ForegroundColor white "'go build' failed; see above."
    exit 1
}

Write-Host "Validating artifact..."
if ( [System.IO.File]::Exists("bittray.exe"))
{
    Write-Host "Applying rsrc metadata..."
    trap
    {
        "Error adding resource metadata: $_"
    }

    rcedit-x64.exe --set-icon .\bitbucket.ico "bittray.exe"
    rcedit-x64.exe "bittray.exe" --set-version-string "ProductName" "Bittray"
    rcedit-x64.exe "bittray.exe" --set-version-string "ProductVersion" "$version"
}
else
{
    Write-Host -BackgroundColor red -ForegroundColor white "'go build' claims to have succeeded, but there is no artifact?"
    exit 1
}

$package = "bittray-$version.zip"
Write-Host "Compressing archive ($package)..."
Compress-Archive -Path .\bittray.exe -CompressionLevel Optimal -DestinationPath $package
if (!$?)
{
    Write-Host -BackgroundColor red -ForegroundColor white "Failed to create zip package."
    exit 1
}

certUtil -hashfile "$package" sha1
if (!$?)
{
    Write-Host -BackgroundColor red -ForegroundColor white "Failed to generate SHA1 integrity checksum."
    exit 1
}
else
{
    Write-Host -BackgroundColor green -ForegroundColor white "Done!"
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.