Repository navigation
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.
| 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.
| 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 |
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.
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.
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.
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.
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.
Same as outset in spirit. parallel_execution exists in the config schema but is not
implemented; setting it logs a warning and changes nothing.
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.
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.
.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.
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.
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.
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.
- Rewrite the payload in PowerShell (or batch, or ship an
.exe). There is no shebang mechanism. - 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. - If it was
login-privileged-once, decide whether you actually meant once-per-user, and move it tologin-onceif so. - Drop it flat in the directory — no subfolders — with an ownership that passes the elevated check if it is going in an elevated directory.
- Number the filename if order matters.
- 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 readreports\items.jsonand confirm nothing isPending.
StartSet — MIT licensed — windowsadmins/startset — a Windows port of macadmins/outset.
StartSet
How it works
Migrating
Operating
Contributing