Windows port of macadmins/outset - Script automation at boot, login, and on-demand for Windows enterprise environments.
StartSet provides a robust framework for running scripts at various points during the Windows lifecycle:
- Boot scripts: Run at system startup (before user login)
- Login scripts: Run when users log in (user or privileged context)
- On-demand scripts: Run when triggered manually or via trigger files
- Full parity with macadmins/outset functionality
- Run-once tracking with checksum validation
- Network connectivity wait before boot scripts
- PowerShell, batch, executable, and package (MSI/MSIX) support
- YAML-based configuration
- Windows Service for automatic trigger detection
- Event log integration
- Serilog logging with file rotation (30 days)
- Dual architecture support (x64 and ARM64)
- Code signing support
C:\ProgramData\ManagedState\
├── boot-once\ # Scripts run once at boot (deleted after)
├── boot-every\ # Scripts run every boot
├── login-window\ # Scripts run at login window (before auth)
├── login-once\ # Scripts run once per user at login
├── login-every\ # Scripts run every login
├── login-privileged-once\ # Elevated scripts run once per user
├── login-privileged-every\ # Elevated scripts run every login
├── on-demand\ # User-context on-demand scripts
├── on-demand-privileged\ # Elevated on-demand scripts
├── share\ # Shared data directory
├── triggers\ # Trigger files; the only folder users may create files in
├── Config.yaml # Legacy configuration file
└── logs\ # Log files
└── startset.log
C:\Program Files\StartSet\
├── managedstatekeeper.exe # CLI / execution engine
└── StartSetService.exe # Windows Service
- Copy
managedstatekeeper.exeandStartSetService.exetoC:\Program Files\StartSet\ - Register the Windows Service:
sc.exe create StartSet binPath="C:\Program Files\StartSet\StartSetService.exe" start=auto sc.exe description StartSet "StartSet - Script automation at boot, login, and on-demand" sc.exe start StartSet
Deploy the MSI or .intunewin package through your MDM solution.
# Run boot scripts
managedstatekeeper boot
# Run login scripts for current user
managedstatekeeper login
# Run on-demand scripts
managedstatekeeper on-demand
# Run privileged on-demand scripts
managedstatekeeper on-demand --privileged
# List all scripts
managedstatekeeper list
# List scripts with execution status
managedstatekeeper list --show-executed
# Add a script to boot-every
managedstatekeeper add myscript.ps1 --type boot-every
# Remove a script
managedstatekeeper remove myscript.ps1 --type boot-every
# Manage ignored users (matching outset)
managedstatekeeper add-ignored-user bob jane
managedstatekeeper remove-ignored-user bob
managedstatekeeper list-ignored-users
# Manage script overrides (force re-run of run-once scripts)
managedstatekeeper add-override myscript.ps1
managedstatekeeper remove-override myscript.ps1 --clear-runonce
managedstatekeeper list-overrides
# Compute checksums (matching outset)
managedstatekeeper checksum myscript.ps1
managedstatekeeper checksum all --record
# Show version
managedstatekeeper --versionEach setting is read from the first of these that sets it:
- A one-off flag for this run (
--verbose,--debug). - Policy:
HKLM\SOFTWARE\Policies\StartSet, for Group Policy or MDM. - Machine settings:
HKLM\SOFTWARE\StartSet\Settings, written bymanagedstatekeeper add-ignored-userand the other settings commands, or by an administrator. - The legacy
C:\ProgramData\ManagedState\Config.yaml. - The built-in default.
Environment variables are not a source. Both registry keys are read in the 64-bit view.
Every setting can be set at every level. In the registry the value name is the setting's name below, with booleans and numbers as REG_DWORD and lists as REG_MULTI_SZ; the Config.yaml name (wait_for_network) is accepted as an alias.
| Setting | Config.yaml | Default |
|---|---|---|
WaitForNetwork |
wait_for_network |
1 |
NetworkTimeout |
network_timeout |
180 seconds |
IgnoreNetworkFailure |
ignored_network_failure |
0 |
Verbose |
verbose |
0 |
Debug |
debug |
0 |
LogLevel |
log_level |
unset |
ChecksumValidation |
checksum_validation |
0 |
AllowedExtensions |
allowed_extensions |
.ps1 .cmd .bat .exe .msi .msix |
ScriptTimeout |
script_timeout |
3600 seconds |
LoginScriptTimeout |
login_script_timeout |
120 seconds |
LoginBatchBudget |
login_batch_budget |
300 seconds |
ParallelExecution |
parallel_execution |
0 |
LoginDelay |
login_delay |
0 seconds |
ShellReadyTimeout |
shell_ready_timeout |
180 seconds |
ShellSettleDelay |
shell_settle_delay |
10 seconds |
LogonCatchUpGrace |
logon_catch_up_grace |
20 seconds |
LogScriptOutput |
log_script_output |
1 |
IgnoredUsers |
ignored_users |
empty |
Overrides |
overrides |
empty |
To set a timeout by policy:
New-Item -Path 'HKLM:\SOFTWARE\Policies\StartSet' -Force | Out-Null
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\StartSet' -Name NetworkTimeout -Value 60 -Type DWordThe service runs as SYSTEM and executes what is under C:\ProgramData\ManagedState, so the installer makes that folder writable only by Administrators and SYSTEM (Users can read it), with inheritance from ProgramData turned off. The service applies the same ACL each time it starts.
Once the folder is locked, only an administrator can create a file in it, so a file there counts as written by an administrator whichever account owns it. A payload or Config.yaml is used when:
- every folder from the file up to
C:\ProgramData\ManagedStateis locked, so only SYSTEM, Administrators or TrustedInstaller can create, delete or change permissions there; - no one else has write, delete, change-permissions or take-ownership rights on the file itself;
- and it is not a link.
Otherwise it is skipped, and the run log says why.
An owner always holds the right to change a file's permissions, so the service gives any file owned by an individual account to the Administrators group.
The first time the service locks a folder that was open before, any file in it whose owner is not an administrator could have come from a standard user. The service moves each such file to C:\ProgramData\ManagedState\quarantine\<timestamp>\ and logs it; nothing is deleted. After that first lock, files are only given to Administrators.
Create one of these files to run payloads now. The service watches C:\ProgramData\ManagedState\triggers, the one folder a standard user may create files in, and the data root itself:
.startset.ondemand- Triggers on-demand scripts.startset.login- Runs the login scripts now, in the signed-in user's session.startset.ondemand-privileged- Triggers privileged on-demand scripts.startset.login-privileged- Runs the login-privileged scripts now, as SYSTEM.startset.cleanup- Triggers cleanup of trigger files
The first two are honoured in either folder. The others run payloads as SYSTEM, so they are honoured only in the data root, which only administrators can write. One left in triggers is deleted and logged.
New-Item -ItemType File 'C:\ProgramData\ManagedState\triggers\.startset.ondemand'- .NET 10 SDK
- Windows SDK (for code signing)
- Code signing certificate (for production builds)
# Full build with signing
.\build.ps1
# Development build (unsigned)
.\build.ps1 -AllowUnsigned
# Build specific architecture
.\build.ps1 -Architecture x64
# Clean build
.\build.ps1 -Clean
# Build with specific certificate
.\build.ps1 -Thumbprint "YOUR_CERT_THUMBPRINT"packages/StartSet/
├── src/
│ ├── StartSet.Core/ # Models, enums, constants
│ ├── StartSet.Infrastructure/ # Logging, config, network, validation
│ ├── StartSet.Engine/ # Script execution engine
│ ├── StartSet.CLI/ # Command-line interface
│ └── StartSet.Service/ # Windows Service
├── build.ps1 # Build script
├── Directory.Build.props # Shared build properties
└── StartSet.sln # Solution file
MIT License - See LICENSE file for details.
- Inspired by macadmins/outset
- Part of the windowsadmins ecosystem