Flyoobe.Extensions/Write-an-Extension.md
The Setup Extensions system lets you drop PowerShell scripts into
.\scripts\ and have them show up automatically in the Setup Extensions page. Scripts declare behavior via simple header metadata.
This doc explains:
Add these at the top of your .ps1:
Description
# Description: <string>
Category
# Category: Pre | Mid | Post | All | Tool
Host
# Host: embedded | console | log (default: embedded)
embedded: runs inside the app; stdout/stderr is captured and shown in UIconsole: launches external PowerShell with -NoExitlog: runs embedded and streams to the app’s Live Log windowOptions
# Options: value1; value2; value3; ... (optional)Per-option host override suffixes (optional):
"Action (console)" → force external console"Action (silent)" → force embedded without log"Action (log)" → force embedded with live log
(The suffix is removed before passing the option text to the script.)Textbox
# Input: true | false (optional)($ArgsText or $args[1]).# InputPlaceholder: <string> (optional, requires # Input: true)To keep maximum compatibility with new and legacy scripts:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "<script.ps1>" "<Option>" "<ArgsText>"
param([string]$Option, [string]$ArgsText)
Position 0 → $Option, position 1 → $ArgsText$args[0], $args[1] in scripts without a param block.-Option / -ArgsText parameters are required. If you do define them, PowerShell binds the two values by position.- embedded (default):
Runs headless, captures stdout/stderr, shows progress + status in the control.
# Host: embedded or no host specified.
- console:
Launches external powershell.exe with -NoExit so the user can interact.
# Host: console or select an option ending with (console).
- log:
Runs headless and opens a live log window that streams output in real-time.
# Host: log or select an option ending with (log).
- silent (per-option override only): Use the suffix (silent) to run embedded without opening the live log, even if host default is log.
To keep large script collections organized, each extension can declare a category in its header Each extension can declare a category to indicate when it should be used during the setup flow.
Category: Pre | Post | Mid | Tool | All
| Category | When It Runs / Fits | What It’s Typically Used For | Example Use-Cases |
|---|---|---|---|
| Pre (Setup Operating System) | Early in the setup process, before personalization | Preparing the system state | Debloating, enabling services, network configuration |
| Mid (Configure Operating System) | During core system configuration | Adjusting Windows behavior and defaults | Feature toggles, performance tuning, registry adjustments |
| Post (Enter Extensions) | After Windows is installed and configured | User-facing customization and finishing touches | Explorer/UI tweaks, theme/visual styles, privacy adjustments |
| Tool (Utility Mode) | Anytime — independent helper utilities | External tools not tied to a setup phase | Ninite installer, Winget multi-installer, Flyby11 Upgrade Helper |
| All (Universal) | Always visible in all categories | Scripts that can be run at any time | Generic utilities, backup/restore actions? |
Scripts are loaded from `.\scripts\ (subfolders allowed).
*.ps1 files are parsed for the first ~15 header lines to read the metadata.# Options: exists.# Input: true.# Description: Clears temp files
# Category: Post
# Host: embedded
# No param() needed — runs with zero positional arguments
Write-Output "Cleaning temp..."
# your logic here
# Description: File Explorer tweaks
# Category: Post
# Host: embedded
# Options: Show file extensions; Hide file extensions; Open This PC (console)
param([string]$Option) # position 0
switch ($Option) {
'Show file extensions' { Write-Output "Enabling extensions..."; # ... }
'Hide file extensions' { Write-Output "Disabling extensions..."; # ... }
'Open This PC' { Start-Process "explorer.exe" "shell:MyComputerFolder" }
default { Write-Error "Unknown option: $Option" }
}
# Description: Manage feature IDs via ViVeTool
# Category: Post
# Host: log
# Options: Enable IDs; Disable IDs; Query IDs; Open GitHub Releases
# Input: true
# InputPlaceholder: Enter comma-separated IDs (e.g., 123,456,789)
param([string]$Option, [string]$ArgsText) # positions 0 and 1
function Parse-Ids($s) {
if ([string]::IsNullOrWhiteSpace($s)) { return @() }
$s.Split(',') | % { $_.Trim() } | ? { $_ -match '^\d+$' } | % { [int]$_ }
}
switch ($Option) {
'Open GitHub Releases' { Start-Process 'https://github.com/thebookisclosed/ViVe/releases'; break }
'Enable IDs' {
$ids = Parse-Ids $ArgsText
if ($ids.Count -eq 0) { throw "No valid IDs" }
& "ViVeTool.exe" '/enable' ("/id:{0}" -f ($ids -join ','))
}
# ... Disable / Query similar
}
# Description: Winget bulk install
# Category: Post
# Host: log
# Options: Install packages
# Input: true
# InputPlaceholder: Enter package IDs separated by space/comma/newline
$option = $args.Count -ge 1 ? $args[0] : $null
$text = $args.Count -ge 2 ? $args[1] : $null
if ($option -ne 'Install packages') { throw "Unknown option: $option" }
$ids = $text -split "[\r\n,; ]+" | ? { $_ } | % { $_.Trim() }
foreach ($id in $ids) {
Write-Output "Installing: $id"
winget install $id -e --accept-source-agreements --accept-package-agreements
}
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "<script.ps1>" "<Option>" "<ArgsText>"Running…, Done., etc.) and can display a progress bar while the script runs.param([string]$Option,[string]$ArgsText) for clarity; still compatible with positional calls.Write-Error on misuse to surface helpful feedback in the UI.