Skip to content

Latest commit

 

History

History
173 lines (139 loc) · 9.55 KB

README.md

File metadata and controls

173 lines (139 loc) · 9.55 KB

ScriptRunner MSI installer

Packages the published app (API + built React SPA, served from one process - see the root README's "Serve React build as static files" note) plus a "ScriptRunner" Windows Service registration into a single ScriptRunnerSetup.msi.

This installer was authored in a Linux sandbox with no Windows machine or WiX toolchain available, then iterated against real wix build output on an actual Windows machine - several real bugs (missing WixToolset.Heat/WixToolset.UI.wixext package references, a BindPath needed for harvested files, InstallerPlatform for 64-bit components, and a deferred-custom-action property-resolution bug that silently skipped service registration entirely) were found and fixed this way. Budget time to verify the specific machine/OS build you're targeting still installs and starts the service cleanly - paste back whatever WiX, msiexec, or install-service.log reports if something doesn't behave as documented here, the same way earlier issues got fixed.

What gets installed

What Where
Application files (published API + wwwroot with the built SPA) C:\Program Files\ScriptRunner\
Windows Service Name ScriptRunner, Automatic startup, auto-restart on crash
Default data directory + bootstrap pointer (bootstrap.json, install log) %ProgramData%\ScriptRunner\ - see "First run" below; the actual database location is chosen from the web setup wizard and can be redirected elsewhere
Secrets (SCRIPTRUNNER_MASTER_KEY, SCRIPTRUNNER_JWT_SIGNING_KEY, ASPNETCORE_URLS) HKLM:\SYSTEM\CurrentControlSet\Services\ScriptRunner\Environment (per-service, not machine-wide - see SECURITY.md)
Firewall rule Inbound TCP allow on the configured port (default 8080)

The installer itself does not prompt for a data directory or create an admin account

  • both of those now happen from the web UI after the service starts (the first-run setup wizard - see the root README's "First run" section). This is deliberate: a native MSI dialog can't browse the server's filesystem in any useful way once the app might be managed remotely, so the data-directory picker is a web page backed by a small server-side API instead (SetupController/DirectoryBrowser.cs).

The service listens on http://0.0.0.0:8080 by default. Change the port by editing $Port in scripts/Install-Service.ps1 before building, or by hand-editing the registry Environment value's ASPNETCORE_URLS entry after install and restarting the service (the install script preserves whatever's already there on upgrade/repair, so a manual change survives an MSI upgrade).

Prerequisites (on the machine building the MSI)

  1. .NET 8 SDK and Node.js 20+ (same as running the app itself - see the root README's Prerequisites section).

  2. WiX v5 CLI + UI extension:

    dotnet tool install --global wix --version 5.0.2
    wix extension add WixToolset.UI.wixext/5.0.2

    (If you already have an older wix global tool installed, use dotnet tool update --global wix --version 5.0.2 instead.)

Building the MSI

From the repo root:

.\build-installer.ps1

This runs, in order: npm install && npm run build in web/, copies web/dist into src/ScriptRunner.Api/wwwroot, dotnet publish the API to publish/, then dotnet build this project. The resulting MSI lands at installer\bin\Release\ScriptRunnerSetup.msi.

Useful flags:

  • .\build-installer.ps1 -SelfContained - publishes a self-contained win-x64 build (larger MSI, but the target machine doesn't need the .NET 8 runtime preinstalled).
  • .\build-installer.ps1 -SkipFrontendBuild - reuses whatever's already in web/dist instead of rebuilding it; handy while iterating on the WiX authoring itself.

If you'd rather run the steps by hand (e.g. to isolate which one fails):

cd web; npm install; npm run build; cd ..
Copy-Item web\dist\* src\ScriptRunner.Api\wwwroot\ -Recurse -Force
dotnet publish src\ScriptRunner.Api -c Release -o publish\
dotnet build installer\ScriptRunner.Installer.wixproj -c Release

Installing

Run the resulting ScriptRunnerSetup.msi elevated (double-click and accept the UAC prompt, or msiexec /i ScriptRunnerSetup.msi /l*v install.log from an elevated prompt for verbose logging - the /l*v log is the first thing to check if the install fails or the custom action reports an error). The installer asks for an install directory (defaults to C:\Program Files\ScriptRunner) and shows the license in License.rtf (replace this placeholder with your own org's terms before distributing the installer to anyone outside your team).

A framework-dependent build (the default) checks for the ASP.NET Core Runtime (x64) before copying any files, and stops with a clear message + download link if it's not found, rather than installing successfully and then failing to start the service later with a much less obvious error. It checks for the existence of the shared-framework folder (C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App), not a specific 8.x version - a first attempt at this checked the registry instead and turned out to false- block a machine that genuinely had 8.0.30 installed, so it's deliberately kept to the simplest thing that can't be wrong: that folder either exists or it doesn't. That also means it's intentionally loose the other way - "some version of the ASP.NET Core shared runtime is present," not "specifically 8.x" - since a false pass here (only .NET 6 installed, say) isn't a silent failure: the service still fails to start with .NET's own clear "You must install .NET to run this application" error naming the exact missing version. If you still get a false block on a machine you're sure has it installed, check whether that folder exists under a different path on that machine (e.g. a custom .NET install location) and let me know. A -SelfContained build (see below) skips this check entirely, since it doesn't need the runtime preinstalled.

On first launch, browse to http://<machine>:8080 - you'll land on the first-run setup wizard (see the root README's "First run" section) rather than a login page, since there's no admin account yet. Walk through it to choose a data directory and create the admin account; you're signed in as that account automatically once it finishes.

Upgrading

Rebuild and run a new ScriptRunnerSetup.msi (the Version in Product.wxs should be bumped for a real release, but AllowSameVersionUpgrades="yes" means re-running the same version also works, e.g. while iterating). The MajorUpgrade element handles removing the old version's files and re-running the service install; existing secrets in the registry and the SQLite database wherever it currently lives are left alone (Install-Service.ps1 only generates a key when one doesn't already exist, and setup "completing" a second time is a no-op - see IDataDirectoryProvider).

Upgrading from a build before the network-reference-scripts / setup-wizard feature needs one manual step: this app uses EF Core's EnsureCreated() rather than migrations (see the root README's "What's intentionally out of scope"), so an existing SQLite database file won't automatically gain the new columns (SourceMode, NetworkPath, NetworkCredentialId on scripts) or the new bootstrap.json setup-state file - the app will likely throw a "no such column" error against a stale schema. Back up and delete the old scriptrunner.db (and any bootstrap.json next to it) before starting the upgraded service, then re-create your scripts/credentials/users, or restore them via your own backup/scripting against the new schema.

Uninstalling

Uninstall from "Apps & Features" or msiexec /x ScriptRunnerSetup.msi. This stops and removes the ScriptRunner service and the installed files, but deliberately leaves %ProgramData%\ScriptRunner (the database) and the service's registry Environment key (the secrets) in place - see the comment at the top of scripts/Uninstall-Service.ps1. Delete those by hand if you want a genuinely clean wipe:

Remove-Item -Recurse -Force "$env:ProgramData\ScriptRunner"
Remove-Item -Force "HKLM:\SYSTEM\CurrentControlSet\Services\ScriptRunner" -Recurse -ErrorAction SilentlyContinue

Manual fallback (no MSI)

Everything the MSI's custom actions do is also just scripts\Install-Service.ps1, which is safe to run by hand from an elevated PowerShell prompt against a dotnet publish output you produce yourself - useful if you'd rather not build/use the MSI at all, or if you're debugging why a custom action didn't behave as documented:

dotnet publish src\ScriptRunner.Api -c Release -o C:\ScriptRunner
.\installer\scripts\Install-Service.ps1 -InstallDir C:\ScriptRunner -DataDir "$env:ProgramData\ScriptRunner"

And to remove it: .\installer\scripts\Uninstall-Service.ps1. This is the same path the root README's older "Running as a Windows Service" section walked through by hand with raw sc.exe commands - the scripts here just make it idempotent and add the registry env-var handling and firewall rule.

Files in this directory

  • Product.wxs - the WiX source: directories, the harvested-files feature, the two service-management custom actions, and the install UI.
  • ScriptRunner.Installer.wixproj - SDK-style WiX v5 project; harvests $(ApiPublishDir) (defaults to ..\publish\) into the MSI.
  • scripts/Install-Service.ps1 / scripts/Uninstall-Service.ps1 - the actual service (de)registration logic, invoked by the MSI and runnable standalone.
  • License.rtf - placeholder license text shown in the install UI.