Skip to content

Build Scripts Guidelines

Roman Kuzmin edited this page May 25, 2024 · 11 revisions

Here are some guidelines based on Invoke-Build scripts here and there. They are not rules, scripts may work fine even if they do not follow.


Use Enter-Build or check $WhatIf for initialization

Scripts often use some initialization code invoked before tasks. If it is just setting a few script scope variables then it is fine right in the script body.

But for heavier jobs like loading modules or changing things outside consider using Enter-Build, the special block invoked before tasks. It is invoked in the script scope, as if its code is there.

Alternatively, check the build variable $WhatIf. If it is false then invoke your initialization code.

Why? Because build scripts are not always invoked for running tasks. They may be invoked for getting task information or code completion.

Example with Enter-Build

Enter-Build {
    'Prepare for tasks...'
}

task Task1 {
    'Doing Task1...'
}

Example with $WhatIf

if (!$WhatIf) {
    'Prepare for tasks...'
}

task Task1 {
    'Doing Task1...'
}

$BuildRoot, $PSScriptRoot, $OriginalLocation

For getting the build directory, use the predefined variable $BuildRoot. The engine sets the current location to $BuildRoot before invoking task jobs and other build blocks.

By default $BuildRoot is the build script directory. You may change it in the beginning of your script. Then all tasks may rely on the new custom build root maintained current by the engine.

On the other hand, if you need the directory of the current script regardless of $BuildRoot then use the PowerShell variable $PSScriptRoot.

Use the predefined variable $OriginalLocation in order to get the location which was current on invoking the build script. Note that this location is restored as current after the build.


Set-Location instead of Push-Location and Pop-Location

Consider using Set-Location instead of Push-Location and Pop-Location. The engine takes care of restoring the location to $BuildRoot for tasks.

Push-and-pop makes sense only if the original location is needed in the same task later. But the original location of a task is always $BuildRoot, so that Set-Location $BuildRoot will do the same (but safer, see later).

DO

task MyTask {
    Set-Location ...
    ...
}

DO NOT

task MyTask {
    Push-Location ...
    ...
    Pop-Location
}

Besides, the above code is not safe. If it fails before Pop-Location then the location stack remains not clean. The safe code should be:

task MyTask {
    Push-Location ...
    try {
        ...
    }
    finally {
        Pop-Location
    }
}

Consider throw instead of Write-Error

Consider using throw instead of Write-Error in task code. throw results in error information pointing to the error line. This is useful for analysis and jumping to the source, e.g. from VSCode output with Ctrl+Click.

In contrast, Write-Error points to the caller code. In tasks it is some build engine line, not useful for error analysis.

DO

task MyTask {
    if (...) {
        throw 'Something is wrong'
    }
}

DO NOT

task MyTask {
    if (...) {
        Write-Error 'Something is wrong'
    }
}

CAVEAT

In functions called from tasks, Write-Error is fine and even preferable if errors should point to the calling task.


Avoid Write-Host

Avoid Write-Host unless you deliberately target some messages for console only and do not need them in the redirected output, e.g. in build log files.

Use Write-Build for colored console output which is not lost on redirection. For not colored messages from tasks just output text directly or write by Write-Output.

DO

task MyTask {
    'Message 1'
    Write-Output 'Message 2'
    Write-Build Green 'Green message'
}

DO NOT

task MyTask {
    Write-Host 'Message 1'
    Write-Host 'Message 2'
    Write-Host 'Green message' -ForegroundColor Green
}

CAVEAT

Only tasks and Enter/Exit-* blocks may output log-like messages. Functions with their own returned data cannot. This is not about Invoke-Build, this is PowerShell. Thus, Write-Host in functions may be reasonable.


Avoid -Before and -After

Unless there are reasons, avoid using -Before and -After in order to set task relations. The normal way is to list task relations in the parameter -Jobs (parameter name often omitted). -Before and -After exist for special cases when tasks cannot be modified directly.

DO

task MyTask1 {
    ...
}

task MyTask2 Task1, {
    ...
}

DO NOT

task MyTask1 -Before MyTask2 {
    ...
}

task MyTask2 {
    ...
}
Clone this wiki locally