MSI installer
PermitUSB ships as a single PermitUSB.msi. Per-machine install, signed, no forced reboot, clean uninstall.
Where to get it
Sign in and visit /app/enrollment. Click Download PermitUSB.msi. The download is gated to authenticated users — each request mints a short-lived signed URL, so anonymous traffic can't pull the binary directly.
Silent install parameters
- TENANT_TOKEN — required. The enrollment token from the dashboard.
- SERVER — optional. The agent-channel URL. Defaults to
https://agent.permitusb.com. Lives on its own DNS-only subdomain (namedagent.rather thanapi.because it's for our installed software, not a public REST API) so traffic bypasses Cloudflare's edge buffering and bot-detection challenges; the dashboard atpermitusb.comstays Cloudflare-proxied. - ENDPOINT_GROUP — optional. Name of the endpoint group the new endpoint should join. Matched case-insensitively and trimmed of surrounding whitespace, so
"Information Technology"and"information technology"resolve to the same group. Unrecognized names fall back to the tenant default group with a warning logged on the cloud side.
Manual silent install
cmd.exe (the format every msiexec doc on the internet shows):
msiexec /i PermitUSB.msi /qn TENANT_TOKEN="<token>"PowerShell — use the --% stop-parsing token:
msiexec --% /i PermitUSB.msi /qn TENANT_TOKEN="<token>"ENDPOINT_GROUP="Information Technology" arrives at msiexec as two arguments and you get the usage dialog instead of an install. Workarounds, in order of preference:--%stop-parsing token (everything after is passed verbatim) — works on PS 5.1 and 7+.$PSNativeCommandArgumentPassing = 'Standard'in your$PROFILE— PS 7.3+ only, fixes the wart globally.- Run from
cmd.exe(or shell out viacmd /c msiexec …) — the original quoting works as documented.
Quote values that contain spaces — e.g. ENDPOINT_GROUP="Information Technology".
Moving an endpoint between groups after install
Two equally good ways:
- From the dashboard's Endpoints page. The agent picks up the change on its next policy poll (within
PolicyPollIntervalSeconds, default 5 min) and updates its localagent.jsonto match. - On the endpoint, edit
%ProgramData%\PermitUSB\agent.jsonand setEndpointGroupto the new group's name, then restart thePermitUSB.Agentservice. Useful for site-side admins re-homing a machine without dashboard access. Local edits win on service restart; mid-session moves come from the dashboard.
The dashboard's Enrollment page generates a token and shows the full msiexec command with the token pre-filled — no need to type it by hand. The Quickstart walks through it.
Upgrading the agent
New version, same machines: just run the new PermitUSB.msi with no properties.
msiexec /i PermitUSB.msi /qnWindows Installer matches the UpgradeCode across builds and runs a MajorUpgrade: stop service → uninstall old binaries → install new binaries → restart service. The agent picks up the new build on the next policy poll (~5 min by default) and the dashboard's "Agent version" column catches up via the X-Agent-Version drift header.
What survives the upgrade. Everything in %ProgramData%\PermitUSB\ — agent.json, credentials.bin, events.db, policy.bin. Those are written by the agent at runtime, not by the MSI, so the installer's RemoveFiles never touches them. The endpoint stays enrolled, keeps its identity, keeps its group assignment, keeps its event queue.
What you don't need to pass on upgrade.
TENANT_TOKEN— already enrolled.credentials.binexists, the agent skips first-run enrollment entirely.ENDPOINT_GROUP—agent.jsonis canonical post-enrollment. The bootstrap registry isn't consulted again. Even if you pass it, it's ignored. To move groups post-install: dashboard, or editagent.jsonand restart the service.SERVER— same story;ApiUrlinagent.jsonwins.
Version-bump rule. The MSI's Version in Product.wxs must increase in one of its first three components or MajorUpgrade silently no-ops (Windows Installer ignores the fourth component when comparing). The C# binaries' assembly version is set in agent/Directory.Build.props and feeds the dashboard's "Agent version" column — bump both files together.
Edge cases.
- Migrating an enrolled endpoint to a new API host.
agent.json'sApiUrlis set during first-run enrollment and stays put across MSI upgrades — the installer doesn't rewrite the file. So if you change the cloud-side API hostname (e.g. moving frompermitusb.comtoagent.permitusb.com), an existing endpoint keeps talking to the old host. Two ways to migrate: (a) hand-edit%ProgramData%\PermitUSB\agent.jsonto the newApiUrland restart the service, or (b) runReset-PermitUSB.ps1+ reinstall the MSI (which picks up the new defaultSERVERautomatically). New installs always go to the installer's current default, no migration needed. - Downgrade is blocked. WiX's MajorUpgrade refuses to install an older version over a newer one. Uninstall first if you really need to roll back.
- Force a re-enroll (tenant move, stuck agent): stop the service, delete
credentials.binandagent.jsonfrom%ProgramData%\PermitUSB\, then reinstall with a freshTENANT_TOKEN. - Wipe data on uninstall:
msiexec /x PermitUSB.msi /qn PURGE_DATA=1removes%ProgramData%\PermitUSB\too. Default uninstall leaves the data behind so a reinstall picks up the same endpoint identity.
What gets installed
%ProgramFiles%\PermitUSB\Agent\— the Worker Service%ProgramFiles%\PermitUSB\Tray\— the per-user tray app%ProgramData%\PermitUSB\— config, SQLite event store, encrypted credentials- Service:
PermitUSB.Agent, starts automatically - Run-key:
HKLM\Software\Microsoft\Windows\CurrentVersion\Run\PermitUSB.Tray - Bootstrap registry:
HKLM\Software\PermitUSB\Bootstrap(cleared on uninstall)
Code signing
Production builds are EV code-signed for Defender SmartScreen compatibility. Pre-release builds may be self-signed; SmartScreen will warn on those, and the warning is expected until the EV-signed build replaces them.
Uninstall
msiexec /x PermitUSB.msi /qnRemoves service, tray app, files, registry keys. Leaves the tenant + endpoint records on the cloud — delete those from the dashboard's Endpoints page.