A PowerShell-based orchestrator that runs a sequence of steps defined in steps.json, tracks progress in run.json, and supports resume after restart, dry-run simulation, selective execution, and configurable behavior on success, skip, or failure.
- Quick Start
- Usage
- Command-Line Options
- steps.json Reference
- Writing Custom Step Scripts
- Run State and Resume Behavior
- Actions and Exit Codes
- Elevation and Shells
- Examples
-
Place
setup.ps1andsteps.jsonin the same directory. -
Create one PowerShell script per step referenced in
steps.json(same directory). -
Run from that directory:
.\setup.ps1
-
To start over after a failed or stopped run:
.\setup.ps1 -Restart
Basic run (resume if possible):
.\setup.ps1Start from scratch (clear progress):
.\setup.ps1 -Restart
# or
.\setup.ps1 --restartSimulate without running scripts:
.\setup.ps1 -DryRun
.\setup.ps1 --dry-runRun only specific steps:
.\setup.ps1 --only="install-prerequisites,configure-settings"Skip specific steps:
.\setup.ps1 --skip="extra-tools,finalize"Combine options (GNU-style and PowerShell):
.\setup.ps1 --restart --only="step-a,step-b"
.\setup.ps1 -DryRun --skip="optional-step"Help:
.\setup.ps1 -Help
.\setup.ps1 --help| Option | Short | Description |
|---|---|---|
-Restart |
— | Delete run.json and run all steps from the beginning. Use after a run stopped due to a failed step or when you want a clean run. |
-DryRun |
— | Simulate execution: no scripts run; each step is treated as success (exit 0). Optional/question steps still prompt. Missing scripts produce a warning. Results are written to run.json. |
--only="name1,name2,..." |
— | Run only the listed step names (comma-separated). All other steps are skipped and recorded as skipped in run.json. |
--skip="name1,name2,..." |
— | Skip the listed steps. If both --only and --skip are used, --only is applied first, then --skip is applied to that set. |
-Help |
— | Print usage and exit. No steps run. |
- PowerShell style:
-Restart,-DryRun,-Help - GNU style:
--restart,--dry-run,--help,--only="...",--skip="..."
steps.json must be in the same directory as setup.ps1. It is a single JSON object: each key is a step name, and each value is a step configuration object.
| Property | Required | Type | Description |
|---|---|---|---|
run |
Conditional | string | Script file name to execute (e.g. "user_settings.ps1"). Required for script steps; omit for question-only steps. |
shell |
No | string | "5" = Windows PowerShell 5 (powershell.exe), "7" = PowerShell 7 (pwsh.exe). Default: "5". |
success |
No | string | Action after success: "continue", "stop", or "restart". Default: "continue". |
skip / skipped |
No | string | Action when step is skipped (exit 200): "continue", "stop", or "restart". Default: "continue". |
fail / failed |
No | string | Action on failure (non‑0, non‑200): "continue", "stop", or "restart". Default: "stop". |
skip-if-success-step |
No | string | Name of a single step. If the specified step has already completed successfully, this step is skipped automatically. |
optional |
No | boolean | If true, user is prompted (Y/N) before running the script. Default: false. |
question |
No | string | Custom prompt for optional steps, or the only content for a question-only step (no script). |
- Script step: has
run; may haveoptionalandquestion. - Question-only step: has
questionand norun. Only the Y/N prompt runs; no script is executed. - Step order is the order of keys in the JSON object (top to bottom).
Use skip-if-success-step when a step should be treated as unnecessary if a specific earlier step has succeeded.
- The value is the name of a single step from
steps.json. - If the specified step has result
successinrun.json, the current step is markedskippedwithout prompting or executing its script. - The skipped result still goes through the step's configured
skip/skippedaction. - This is useful when providing a fallback step that is only needed if a primary step was not executed or did not succeed.
continue— Proceed to the next step.stop— Stop the run. Re-running without-Restartwill exit with a message to use--restartto continue.restart— Prompt “Restart now? (Y/N)”. If Y (and not dry-run), reboot the machine; otherwise remind user to restart and re-runsetup.ps1. Run stops here.
If run is a filename that starts with admin_ (e.g. admin_install.ps1), the script is executed elevated (Run As Administrator) in a new window. All other scripts run in the current console with normal privileges.
{
"enable-feature": {
"run": "admin_enable_feature.ps1",
"shell": "5",
"success": "restart",
"skip": "continue"
},
"install-prerequisites": {
"run": "admin_prerequisites.ps1",
"shell": "5",
"success": "continue"
},
"install-prerequisites-manual": {
"run": "admin_prerequisites_manual.ps1",
"shell": "5",
"skip-if-success-step": "install-prerequisites",
"success": "continue"
},
"configure-settings": {
"run": "user_settings.ps1",
"shell": "7"
},
"apply-preferences": {
"run": "user_preferences.ps1",
"shell": "7",
"fail": "continue"
},
"continue-advanced": {
"question": "Do you want to continue with the advanced setup?",
"success": "continue",
"skip": "stop"
},
"extra-tools": {
"run": "user_extra_tools.ps1",
"shell": "7",
"optional": true,
"question": "Do you want to install extra tools?",
"skip": "continue"
},
"finalize": {
"run": "user_finalize.ps1",
"shell": "7"
}
}- enable-feature: Runs
admin_enable_feature.ps1with PowerShell 5, elevated; on success asks for restart; on skip continues. - install-prerequisites: Elevated script; on success continues.
- install-prerequisites-manual: Runs only if
install-prerequisiteshas not already succeeded; otherwise it is auto-skipped. - configure-settings / apply-preferences: User-level scripts with PowerShell 7; apply-preferences continues even on fail.
- continue-advanced: Question-only step (no
run); success → continue, skip → stop. - extra-tools: Optional step with custom question; user can skip (recorded as skipped, then continue).
- finalize: Normal script step with defaults.
- Step scripts must live in the same directory as
setup.ps1. - The filename must match the
runvalue insteps.json(e.g.user_settings.ps1,admin_prerequisites.ps1).
- Scripts are run with:
-ExecutionPolicy Bypass -NoProfile -File "C:\path\to\script.ps1"
- No arguments are passed from the orchestrator to the script.
- The process exit code is what the orchestrator uses to decide success/skip/failure.
| Exit code | Meaning | Typical use |
|---|---|---|
0 |
Success | Step completed successfully. |
200 |
Skipped | Step was not needed (e.g. already installed, user chose to skip internally). |
| Any other | Failed | Error or unmet condition; orchestrator applies the step’s fail/failed action. |
PowerShell example:
# success
exit 0# skipped (e.g. already installed)
exit 200# failure
Write-Error "Installation failed"
exit 1- Use exit codes consistently: 0 = success, 200 = skipped, non‑zero (e.g. 1) = failure.
- Admin scripts: Use the
admin_prefix in the filename and expect a new elevated window; avoid interactive prompts that assume the same console if it’s confusing for users. - Idempotency: Where possible, make steps safe to run again (e.g. “already installed” → exit 200).
- PATH changes: The orchestrator refreshes
PATH(machine + user) after each step, so steps that modify environment variables are picked up by later steps on the next run. - Restart steps: If a step requires a reboot, use
"success": "restart"(or appropriate action) insteps.jsonand exit 0 after doing the change; the orchestrator will prompt for restart.
Success:
# do_something.ps1
Write-Host "Doing something..."
# ... work ...
exit 0Conditional skip:
# install_tool.ps1
if (Get-Command mytool -ErrorAction SilentlyContinue) {
Write-Host "Already installed."
exit 200
}
# ... install ...
exit 0Failure:
# risky_step.ps1
try {
# ... work ...
} catch {
Write-Error $_.Exception.Message
exit 1
}
exit 0- run.json is created/updated in the same directory as
setup.ps1and stores one record per completed step:result,exitCode,action,timestamp. - Resume: On a later run, steps already in
run.jsonwith resultsuccessorskippedare not run again; execution continues from the first step not yet completed. - Trailing failures: The orchestrator removes the last consecutive failed steps from state so they are retried on the next run (no need to clear state for that).
- Stopped run: If the last completed step had action
stop, the next run without-Restartwill exit with a message to use--restartto start fresh. Use.\setup.ps1 -Restartwhen you want to run again from the beginning. - Dry-run: With
-DryRun, no scripts run but results are written torun.json(all success). Useful to validatesteps.jsonand see order without making changes. - run.json is typically gitignored so local progress is not committed.
- success — script exited with 0.
- skipped — script exited with 200, or user skipped (optional/question step).
- failed — script exited with any other code or threw before exit.
| Result | Default action |
|---|---|
| success | continue |
| skipped | continue |
| failed | stop |
Override per step in steps.json with success, skip/skipped, and fail/failed.
- Exit 0 → success → use
successaction. - Exit 200 → skipped → use
skip/skippedaction. - Other exit → failed → use
fail/failedaction. - continue → go to next step.
- stop → end run; next run without
-Restartwill tell user to use--restart. - restart → prompt to restart PC; after reboot, run
setup.ps1again to resume.
admin_prefix in the step’srunfilename (e.g.admin_install.ps1) → script runs elevated (Run As Administrator) in a new window.- No
admin_prefix → script runs in the current console with normal (user) privileges.
shell: "5"(or omitted) →powershell.exe(Windows PowerShell 5).shell: "7"→pwsh.exe(PowerShell 7). Ifpwsh.exeis not inPATH, the orchestrator addsC:\Program Files\PowerShell\7toPATHbefore use.
.\setup.ps1.\setup.ps1 -Restart.\setup.ps1 -DryRun.\setup.ps1 --only="install-prerequisites,configure-settings".\setup.ps1 --skip="extra-tools"In steps.json:
"confirm-ready": {
"question": "Is the machine ready to proceed?",
"success": "continue",
"skip": "stop"
}No .ps1 file is needed; the orchestrator only shows the prompt and records success/skip.
"install-optional": {
"run": "user_install_optional.ps1",
"shell": "7",
"optional": true,
"question": "Do you want to install optional tools?",
"skip": "continue"
}User is asked; N → step skipped (exit 200), continue to next; Y → script runs.
"install-driver": {
"run": "admin_install_driver.ps1",
"shell": "5",
"success": "restart"
}Script does the install and exits 0; orchestrator then prompts “Restart now? (Y/N)” and can reboot.
"install-git-manual": {
"run": "user_install_git_manual.ps1",
"shell": "7",
"skip-if-success-step": "install-git-winget",
"skip": "continue"
}If the step specified in skip-if-success-step already finished with success, this step is recorded as skipped and its script is not run.
Place everything in one directory (e.g. your repo root or a dedicated “orchestrator” folder):
your-folder/
setup.ps1 # Orchestrator script
steps.json # Step definitions (required)
run.json # Created/updated by setup.ps1 (e.g. in .gitignore)
user_settings.ps1 # Step scripts referenced in steps.json
admin_prerequisites.ps1
...
steps.json and all run scripts must be in the same directory as setup.ps1.
| Issue | What to do |
|---|---|
| “steps.json not found” | Ensure steps.json is in the same folder as setup.ps1 and run from that folder (or use full path to setup.ps1). |
| “Script not found” | Ensure the script file in run exists in the same directory as setup.ps1 and the name matches exactly. |
| Run stopped at a step | If the last step’s action was stop, run .\setup.ps1 -Restart to start over, or fix the step/script and use -Restart to re-run from the beginning. |
| Steps not retrying after failure | Trailing failed steps are auto-cleared so they retry. If you want to retry from an earlier step, use -Restart. |
| Admin step doesn’t show output | Admin scripts run in a new elevated window; check that window or add logging to a file. |
| PATH not updated for next step | The orchestrator refreshes PATH after each step. If you still don’t see changes, ensure the step that sets PATH exits with 0 and run the next step in a new run. |
This manual covers all current options and behaviors of the Step Orchestrator. For the latest behavior, refer to the in-script help: .\setup.ps1 -Help.