Skip to content

Coming From Outset

Rod Christiansen edited this page Sep 3, 2026 · 1 revision

Coming from outset

StartSet is a Windows port of macadmins/outset by Joseph Chilcote and Bart Reardon. If you already run outset on your Macs, the model transfers: drop a script into a well-known directory, and it runs at boot, at login, or on demand, with per-user "once" tracking.

What does not transfer is the process model. Read Windows Session Model before you port anything that touches a user's settings — it is where most of the surprises are.

Outset flag and preference-key names differ between outset releases; check outset --help on your own version rather than relying on this page for the macOS side. Everything on the StartSet side is taken from the source in this repository.

Directories

outset StartSet
boot-once C:\ProgramData\ManagedState\boot-once
boot-every C:\ProgramData\ManagedState\boot-every
login-once C:\ProgramData\ManagedState\login-once
login-every C:\ProgramData\ManagedState\login-every
login-privileged-once C:\ProgramData\ManagedState\login-privileged-once
login-privileged-every C:\ProgramData\ManagedState\login-privileged-every
on-demand C:\ProgramData\ManagedState\on-demand
login-window (recent outset releases) C:\ProgramData\ManagedState\login-window
share C:\ProgramData\ManagedState\share
— C:\ProgramData\ManagedState\on-demand-privileged (no outset equivalent)

The directory names are identical. The root is not: outset lives under /usr/local/outset, StartSet under C:\ProgramData\ManagedState. Note that the data root is not named "StartSet"; C:\Program Files\StartSet holds the binaries and nothing else.

The extra on-demand-privileged directory exists because on Windows the split between "as the user" and "as SYSTEM" has to be explicit — see below.

Concepts

outset concept StartSet equivalent Notes
LaunchDaemon fires boot / on-demand The StartSet Windows service, running as LocalSystem Boot payloads run at service start, so restarting the service re-runs boot-every
LaunchAgent runs login payloads inside the user session LogonEventWorker watches Security event 4624 and impersonates the console user There is no per-user agent; everything is driven from session 0
Trigger files under a temp path .startset.ondemand, .startset.ondemand-privileged, .startset.login-privileged, .startset.cleanup at C:\ProgramData\ManagedState Created by anyone with write access; the service deletes them after processing
com.github.outset preferences plist C:\ProgramData\ManagedState\Config.yaml YAML, snake_case keys, no defaults/MDM channel
Per-user run-once plist share\runonce-<username>.json JSON, keyed by lowercased filename
System run-once state share\runonce-system.json
Ignored users ignored_users in Config.yaml, plus add-ignored-user / remove-ignored-user / list-ignored-users The repo states these match outset's --add-ignored-user / --remove-ignored-user
Overrides — force a once payload to re-run overrides in Config.yaml, plus add-override / remove-override / list-overrides Matched on filename
Checksum validation of payloads share\checksums.yaml + checksum_validation: true, plus the checksum command The repo states this matches outset's --checksum
Wait for network before boot payloads wait_for_network / network_timeout / ignored_network_failure
Shell scripts, .py, packages .ps1, .cmd, .bat, .exe, .msi, .msix No interpreter shebang mechanism; the extension picks the processor
outset --boot, --login, --on-demand, --cleanup managedstatekeeper boot, login, on-demand, cleanup See CLI Reference

Where behaviour differs

1. Login payloads may not run at all — and StartSet says so

On macOS, a LaunchAgent is already inside the user's session. On Windows, StartSet has to CreateProcessAsUser into the console session, and that can fail — most often because the login trigger fires before the user's token exists. StartSet waits up to 60 seconds for the token, and if it still cannot get there, the payload is marked Deferred: not run, not failed, not a success. There is deliberately no SYSTEM fallback.

Outset has no equivalent status. If you build dashboards on StartSet output, treat Deferred / Pending as a real state, not as noise.

2. login-privileged-once is once per machine, not per user

This is the sharpest difference and the easiest to get wrong. StartSet selects a per-user run-once ledger only for payload types it classifies as user context — login-once, login-every, on-demand. login-privileged-once is not one of them, so it records into share\runonce-system.json and fires once on the machine, not once per user.

If you are porting an outset login-privileged-once payload that expects to run for each new user, either move the per-user part into login-once (which is genuinely per user) or make the payload idempotent and put it in login-privileged-every.

3. Privileged login payloads cannot touch the user's hive

An outset login-privileged-* script runs as root but still within the context of a login, and on macOS a root script can reach a user's home directory directly. On Windows, login-privileged-* runs as LocalSystem in session 0: HKCU is SYSTEM's hive, %APPDATA% is SYSTEM's profile, and user32 calls do nothing. Split such work into a privileged half that stages data and a login-every half that consumes it as the user.

4. Unlock is not a login

Outset's login trigger and StartSet's are both sign-in events, but on Windows workstation unlock is logon type 7 and is not watched. Payloads do not re-run when a user unlocks the screen.

5. Cached credentials matter

StartSet watches logon types 2 (interactive), 10 (RDP) and 11 (cached interactive). Type 11 is the normal case for every sign-in after a user's first on a given machine. This has no macOS analogue and is only mentioned because a port that watched only types 2 and 10 would appear to work in testing and then quietly stop firing in production.

6. Ordering is alphabetical and strictly sequential

Same as outset in spirit. parallel_execution exists in the config schema but is not implemented; setting it logs a warning and changes nothing.

7. Subdirectories are ignored

Discovery is top level only. A payload nested one folder deep never runs and nothing reports it missing. This is the single most common delivery failure — see Package Delivery.

8. Elevated payloads are ownership-checked

Files in boot-*, login-window, login-privileged-* and on-demand-privileged must be owned by SYSTEM, the built-in Administrator, Administrators, or a service SID. Anything else is skipped with Permission validation failed. This is the Windows analogue of outset's insistence on root-owned, non-world-writable scripts, but it tests ownership, not the permission bits.

9. Packages are payloads, but not user-context ones

.msi and .msix files in a payload directory are installed by StartSet (msiexec /i … /qn /norestart and Add-AppxPackage respectively). Unlike scripts, they are never routed into the user's session — an .msi in login-every installs as SYSTEM.

10. Editing a payload re-arms it

The run-once ledger stores the payload's SHA256 and compares it every time. Changing a *-once script's contents makes it run again without any ledger surgery. Outset users used to deleting a plist entry can usually just edit the file.

11. boot-once payloads are deleted

After a successful run, a boot-once payload is deleted from disk as well as recorded in the ledger. No other payload type is deleted.

12. Logging is a session tree, not a single log file

Outset writes to a log file and the unified log. StartSet writes a per-run directory under logs\YYYY-MM-DD\HHMM\ containing startset.log, session.json and events.jsonl, plus aggregated reports\ for monitoring tools, plus Warning-and-above to the Windows Application event log under source StartSet. Retention is 30 days. See Logging and Reporting.

Porting checklist

  1. Rewrite the payload in PowerShell (or batch, or ship an .exe). There is no shebang mechanism.
  2. Decide the context deliberately: does this need the user's hive and desktop (login-*, on-demand), or machine scope and elevation (login-privileged-*, boot-*, on-demand-privileged)? You cannot have both in one payload.
  3. If it was login-privileged-once, decide whether you actually meant once-per-user, and move it to login-once if so.
  4. Drop it flat in the directory — no subfolders — with an ownership that passes the elevated check if it is going in an elevated directory.
  5. Number the filename if order matters.
  6. Test with managedstatekeeper process <type> first, then through the service (Restart-Service StartSet, or a trigger file, or a real sign-out and sign-in), then read reports\items.json and confirm nothing is Pending.

Clone this wiki locally