Documentation › Formatting for the Builder
Formatting for the Builder
How to lay out a plain-text runbook so the Builder can turn it into an interactive runbook. The layout is the one you already use to comment a script: a header, then steps made of a title, a dashed line, comments and the code to paste.
A plain-text runbook is an ordinary script in its shell or language, with structured comments. The comments (the header, sections, steps and their descriptions) are what the Builder reads to lay out the interactive runbook. The code is yours: write it exactly as you would run it, and test it before you build. The Builder does not check that commands work. Only the Settings step has rules for its code, because the Builder turns it into fields (Section 2.8).
Supported:
| Header line | Values |
|---|---|
Shell: | PowerShell, pwsh, Bash, sh, zsh |
Language: | MySQL |
2.1 How the rules are marked
- Required. The Builder checks it. A file that breaks it is not built, and the Builder lists each problem with its line number and the reason.
- Convention. How to get the interactive runbook to look and behave right. The Builder does not check it; a file that ignores it still builds but may display oddly. Check the preview.
Every Required rule is listed in Section 2.10, and every message the Builder gives is explained in Troubleshooting.
2.2 The plain-text runbook file
Required
- The file name ends in
.txt. To upgrade, the Builder also accepts an interactive runbook (.html) built with any earlier version (Section 3.4). - The file is saved as UTF-8. UTF-16 with a byte-order mark is also accepted, which is what PowerShell 5.1's
Out-Filewrites by default. - The text never contains
</scriptor<!--, in any letter case. Either would break the interactive runbook. Split it if a command needs it, for example'</scr' + 'ipt>'.
Convention
- No other escaping is needed. Type
<,>and&as they are. - Windows and Unix line endings both work.
2.3 Header
The first thing in the text.
<#
Title of the runbook
Prepared by: Name, Company
Version: 261007.1
Compatible with: Windows Server 2019 and later, PowerShell 5.1
Shell: PowerShell
#>
Required
- Line 1 holds only
<#. - The first line inside is the title. It may not be empty or start with
Prepared by:,Version:,Compatible with:,Shell:orLanguage:. - The header ends with a line starting
#>. - If there is a
Shell:line, its value isPowerShell,pwsh,Bash,shorzsh. If there is aLanguage:line, its value isMySQL. Any letter case. - The header has a
Shell:line or aLanguage:line, not both.
Convention
| Line | Effect |
|---|---|
| First line inside | Title, shown at the top and in the browser tab |
Prepared by: | Shown under the title |
Version: | Shown under the title and in the step list |
Compatible with: | Shown under the title, on its own line: the systems or versions the runbook was written for |
Shell: or Language: | What the code is written in. It sets the label on each block of code, the coloring, and the line endings used when copying. PowerShell if neither is given |
Every header line other than the title is optional, and they may come in any order. Any other line inside the header is ignored: it is not shown in the interactive runbook and not checked, so a mistyped key such as Prepard by: is silently left out. Check the Builder's summary after building.
The <# and #> markers are used for every shell. They are never pasted anywhere.
Any change to the runbook text, however small, starts the interactive runbook fresh: Done marks and the values operators have typed into Settings are not carried over from the old interactive runbook, whether they were kept in the browser or saved to a progress file.
2.4 Sections
All Convention. Three lines, exactly this shape.
# =================================
# SECTION 1: SETUP (one-time only) #
# =================================
# Comments here are the section's introduction.
- The middle line is
SECTION <number>: <title>. The trailing#is optional. - Text in brackets at the end of the title is shown in lighter type. Use it for notes such as
(changes nothing). - Code placed after the introduction becomes a card titled "Paste this to start Section N" (or "to run Section N" if the section has no steps).
2.5 Steps
A blank line, the title, a dashed line, comments, then code.
# E. Title of the step
# --------------------
# Comments explaining the step.
code to paste
Required
- The runbook has at least one step, or one section with code to paste.
- Each section number is used once.
- Each step letter is used once within its section.
Convention
<Capital letter>. Titlenumbers the step from its section:1-E.- A title with no letter, such as
# Settingsor# Before you start, gives an unnumbered card. - The title may run over two comment lines.
- Dashed and
====lines can be any length of three or more.
2.6 Comments, then code
All Convention.
- Every comment line directly after the dashed line is the step's description.
- The first line that is not a comment starts the code.
- Everything from there to the next step or section is what the Copy button copies.
- Keep the code free of blank lines and comments.
- A step with no code shows "Nothing to paste for this step." Use it for steps the operator does by hand, such as recording a result.
2.7 Formatting inside comments
All Convention.
| Write | Result |
|---|---|
# on its own | New paragraph |
# - item | Bullet. Indent the next line to continue it |
# 1. item | Numbered list |
# text (2 spaces before the text, after the # ) | The line is shown on its own, indented one level. Every 2 more spaces add a level: 4 spaces is two levels. Bullets, numbered items and their continuation lines are not affected |
# Example: ... or # Example Values: | Starts a boxed block. The rest of the paragraph is shown as typed, keeping its column alignment |
$name | Shown as code |
Things that spoil the display without stopping the build:
- A comment must start with
#(hash, space) or be a bare#.#noteis treated as the start of the code. - A step title with no blank line above it is swallowed into the previous step's code.
- A comment placed after the code has started is copied with the code.
2.8 Settings
The interactive runbook reads the code of the unnumbered step titled Settings and puts a field on that card for each setting. Operators type their values into the fields; the plain-text runbook supplies the starting values.
Rules for the Settings step
Required. A runbook has at most one Settings step: one unnumbered step titled Settings.
Required. Every code line in the Settings step is one setting, written as shown below. It starts in the first column, and nothing follows the value, not even a comment.
| Shell or language | Line | Field |
|---|---|---|
| PowerShell, pwsh | $name = "text" or $name = 'text' | Text box |
| PowerShell, pwsh | $name = $true or $name = $false | Check box |
| PowerShell, pwsh | $name = 30 | Number box |
| Bash, sh, zsh | name="text", name='text' or name=word | Text box |
| Bash, sh, zsh | name=true or name=false | Check box |
| Bash, sh, zsh | name=30 | Number box |
| MySQL | SET @name = 'text'; or SET @name = "text"; | Text box |
| MySQL | SET @name = TRUE; or SET @name = FALSE; | Check box |
| MySQL | SET @name = 30; | Number box |
Required. These are refused:
- a line that is not a setting, such as a command
- a value the shell or database works out, such as
$file = Join-Path $working_dir "list.txt","$HOME/logs",$(date)orNOW(). Set these in a later step instead - a double-quoted value containing
$or a backtick that the shell would expand. Use single quotes, or escape it (a backtick before$in PowerShell, a backslash in Bash) - a missing closing quote
- in PowerShell, pwsh and MySQL, no value at all. In Bash, sh and zsh,
name=is valid and starts the field empty, the same asname="" - a comment or second command after the value
- the same setting twice (PowerShell and MySQL names are compared ignoring letter case)
Settings are for names, addresses, paths and numbers, never passwords or other secrets (Section 2.9).
Using a setting in later steps
Convention. Use the variable ($working_dir, "$target_dir", @db_name). The shell or database fills in the value the operator set when they ran the Settings step. The interactive runbook shows and copies these lines exactly as written.
Loading a setting again in a later step
Convention. A step that must work in a new terminal window or database session, after the Settings variables are gone, can load the settings it needs. Set the setting to itself, on a line of its own starting in the first column:
| Shell or language | Line |
|---|---|
| PowerShell, pwsh | $working_dir = $working_dir |
| Bash, sh, zsh | target_dir="$target_dir" or target_dir=$target_dir |
| MySQL | SET @db_name = @db_name; |
The interactive runbook replaces the right-hand side with the current value of that Settings field, so the operator sees and copies, for example, $working_dir = "D:\Work\Client A". Lines after it can then use the variable as usual:
$working_dir = $working_dir
$settings_file = Join-Path $working_dir "settings.json"
This is the only change the interactive runbook makes outside the Settings step. The name must be one that is set in the Settings step; a line that sets any other variable to itself is left as written. Do not write a setting's value out again in a later step: it would not follow the field.
Example values
Convention. Show sample values in the Settings comments like this:
# Example Values:
# $app_dir = "C:\Temp\Export"
# $working_dir = "C:\Temp\Export\Client"
The box shows the block exactly as typed, with every space kept, so the indentation and alignment are yours to choose. Only the # that starts each comment line is removed. The box never changes when a field is edited.
2.9 Passwords and other secrets
Keep passwords, keys, tokens and other secrets out of runbooks. The Builder does not check for them: it cannot reliably tell a secret from ordinary text, and trying would get in the way of real work. Keeping them out is up to the author.
- Not in the plain-text runbook. An interactive runbook is a file that gets copied, emailed, attached to tickets and kept for years. Treat everything in it as readable by anyone who ever has a copy.
- Not in a setting. Values typed into Settings fields are kept in plain text, by the browser on that computer and in any progress file the operator saves. Never use a setting for a secret.
- Not in a command to paste. Pasted commands end up in shell history, transcripts and clipboard managers.
- Prompt for it when it is needed instead, for example
Get-CredentialorRead-Host -AsSecureStringin PowerShell,read -rsin Bash, or the sign-in prompt of the tool being used, such asConnect-ExchangeOnline. Where the system offers it, use a credential store, a managed identity or certificate-based sign-in rather than a password.
2.10 Required rules at a glance
The Builder checks exactly these rules, and no others. Troubleshooting explains each message and its fix.
| # | Rule | Section | Builder says |
|---|---|---|---|
| R1 | A .txt file, or an interactive runbook (.html) to upgrade | 2.2, 3.4 | This file type is not supported (or: This HTML file is not an interactive runbook) |
| R2 | Saved as UTF-8, or UTF-16 with a byte-order mark | 2.2 | This file is not saved as UTF-8 |
| R3 | Text, not binary data | 2.2 | This file is not saved as UTF-8 (or: This is not a text file) |
| R4 | No </script | 2.2 | …would cut the runbook short when it is built |
| R5 | No <!-- | 2.2 | …can stop the interactive runbook from being built correctly |
| R6 | Starts with <# alone on line 1 | 2.3 | A runbook must start with its header |
| R7 | Header closed with #> | 2.3 | The header has no closing #> line |
| R8 | First header line is the title | 2.3 | …must be the title of the runbook |
| R9 | A supported shell or language, and not both | 2.3 | The shell must be PowerShell, pwsh, Bash, sh or zsh / The language must be MySQL / …not both |
| R10 | At least one step | 2.5 | No steps were found |
| R11 | Settings step holds only setting lines | 2.8 | …may only hold setting lines |
| R12 | Each setting set once | 2.8 | …is already set on line N |
| R13 | No worked-out values | 2.8 | The value is a command or an expression |
| R14 | No expandable $ or backtick in double quotes | 2.8 | …that PowerShell would expand |
| R15 | Nothing after the value | 2.8 | There is text after the closing quote |
| R16 | Closing quote present | 2.8 | The closing quote is missing |
| R17 | A value is given (PowerShell, pwsh and MySQL) | 2.8 | No value is given |
| R18 | Each section number used once, and each step letter once within its section | 2.5 | There is already a Section N… / There is already a step N-X… |
| R19 | At most one Settings step | 2.8 | There is already a Settings step… |
A complete example
A plain-text runbook that follows this documentation and builds without errors. Save it as archive-app-logs.txt and drop it onto the Builder to see the result.
<#
Archive old application logs on a file server
Prepared by: Jo Admin, Example Co
Version: 261008.1
Compatible with: Windows Server 2019 and later, PowerShell 5.1 and 7
Shell: PowerShell
#>
# Before you start
# ----------------
# This runbook moves application log files older than a set number
# of days from the log folder to the archive folder, on one server.
# It deletes nothing.
#
# Use it when the log drive alert fires, or monthly. Do not use it
# for the database server: use "Archive SQL logs" instead.
#
# You need:
# - a PowerShell session on the server, run as an administrator
# - write access to the archive folder
# - nobody else running this runbook on the same server
#
# Stop and tell the runbook owner if any check in Section 1 does not
# match what the step says you should see.
#
# To undo, use the runbook "Restore archived application logs".
# Settings
# --------
# Example Values:
# $log_dir = "D:\Logs\App"
# $archive_dir = "E:\Archive\AppLogs"
# $archive_drive = "E"
# $keep_days = 30
# $min_free_gb = 50
$log_dir = "D:\Logs\App"
$archive_dir = "E:\Archive\AppLogs"
$archive_drive = "E"
$keep_days = 30
$min_free_gb = 50
# =====================================
# SECTION 1: CHECK (changes nothing) #
# =====================================
# Every step in this section only reads. It is safe to repeat.
# A. Confirm the server and both folders
# --------------------------------------
# You should see the server's name, then True twice.
#
# Stop here if either line says False.
hostname
Test-Path $log_dir
Test-Path $archive_dir
# B. Count the files to be moved
# ------------------------------
# You should see Count and Sum (MB). Write both down for step 3-B.
#
# If Count is 0, there is nothing to archive. Stop here.
$old = Get-ChildItem $log_dir -File | Where-Object LastWriteTime -lt (Get-Date).AddDays(-$keep_days)
$old | Measure-Object Length -Sum | Select-Object Count, @{ n = 'Sum (MB)'; e = { [math]::Round($_.Sum / 1MB) } }
# C. Check there is room in the archive
# -------------------------------------
# You should see one row for drive $archive_drive.
# Look at the Free (GB) column.
#
# Stop here if Free (GB) is under $min_free_gb.
# Do not start Section 2. Send the number to the
# runbook owner.
Get-PSDrive -Name $archive_drive
# ======================================
# SECTION 2: ARCHIVE (moves files) #
# ======================================
# This section changes the server. Run it only after every step in
# Section 1 looked right.
# A. Load the settings again (new window only)
# --------------------------------------------
# Only needed if you closed the PowerShell window after the
# Settings step. Otherwise, check Done and go on.
$log_dir = $log_dir
$archive_dir = $archive_dir
$keep_days = $keep_days
# B. Move the old files to the archive
# ------------------------------------
# Moves each file older than $keep_days days. A file that is
# already in the archive is skipped and listed, so this step is
# safe to run again.
#
# You should see one line per skipped file, or nothing at all.
Get-ChildItem $log_dir -File | Where-Object LastWriteTime -lt (Get-Date).AddDays(-$keep_days) | ForEach-Object {
if (Test-Path (Join-Path $archive_dir $_.Name)) { "Skipped, already archived: $($_.Name)" }
else { Move-Item $_.FullName -Destination $archive_dir }
}
# =====================================
# SECTION 3: VERIFY AND RECORD #
# =====================================
# A. Check nothing old is left
# ----------------------------
# You should see Count 0, or only the files listed as skipped in
# step 2-B.
Get-ChildItem $log_dir -File | Where-Object LastWriteTime -lt (Get-Date).AddDays(-$keep_days) | Measure-Object | Select-Object Count
# B. Record the result
# --------------------
# 1. In the ticket, record the Count and Sum (MB) from step 1-B and
# any files listed as skipped in step 2-B.
# 2. Note the date, the server, and who ran it.