PowerShell install script
Install-PermitUSB.ps1 is the recommended way to install, upgrade and uninstall on Windows endpoints. It wraps the MSI with what msiexec alone cannot do: prerequisite checks, friendly errors, automatic .NET 10 Desktop Runtime install, distinct exit codes for fleet-deploy branching, and an upgrade path that stops the running agent and tray before swapping binaries.
Quickstart
Get the exact command, with the token pre-filled, from the dashboard Enrollment page. Roughly, from PowerShell as Administrator in the folder you saved the script to:
powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1 -TenantToken '<token>'The -ExecutionPolicy Bypass argument applies only to that one invocation and does not change your system-wide policy. The script is EV code-signed, so on a RemoteSigned box it runs without Bypass too.
What the script does
- Checks for admin elevation and 64-bit Windows up front
- Probes for the .NET 10 Desktop Runtime through dotnet --list-runtimes at the arch-specific path, and if missing downloads and installs it silently from the official Microsoft URL
- Downloads PermitUSB.msi using your enrollment token, or uses a local copy next to the script when you pass -LocalMsi
- For upgrades, stops the agent service and force-kills the tray before running msiexec so the install does not fight locked files
- Runs msiexec with the token, server and optional endpoint group
- Prints a step marker for each phase and writes a full msiexec log to %TEMP%\PermitUSB-install.log on failure
Parameters
| Parameter | What it does |
|---|---|
| -TenantToken | The enrollment token. Required for fresh installs and for upgrades that fetch the MSI from the dashboard, since the download endpoint is token-keyed. Optional with -Upgrade -LocalMsi. |
| -Server | Agent-channel URL, default https://agent.permitusb.com. On upgrades the script forwards it only if you set it explicitly, so a non-prod server is never silently re-pointed at prod. |
| -EndpointGroup | Pre-assigns the endpoint to a named group. Matched case-insensitively; unrecognized names fall back to the default group. |
| -InstallerBaseUrl | Where to fetch the MSI from, default https://permitusb.com. Override for an internal mirror. |
| -SkipPrereqs | Skip the .NET 10 check and auto-install. Useful when the runtime is deployed through a separate channel and you want a loud failure otherwise. -Upgrade implies this. |
| -LocalMsi | Use a PermitUSB.msi sitting next to the script instead of downloading. |
| -MsiPath <path> | Explicit path to the MSI, including UNC paths. Mutually exclusive with -LocalMsi. |
| -Upgrade | In-place upgrade of an existing install. |
Fresh install
The default path. Downloads the MSI by token, installs prerequisites, runs msiexec. The agent enrolls within a minute and appears on the Endpoints page as active.
Offline and vetted installs
For air-gapped fleets, SCCM or Intune packaging, and admins who want to vet the binary before deploying. Put PermitUSB.msi and Install-PermitUSB.ps1 in the same folder, then:
powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1 -TenantToken '<token>' -LocalMsiThe script resolves $PSScriptRoot to find the MSI beside it and prints the MSI ProductVersion as a sanity check before installing. The script must be on disk for this: piping it through irm and iex leaves $PSScriptRoot empty, and the script fails fast with a clear message. The local MSI is not deleted after install, because you brought it.
Upgrades
Skips the .NET prereq check, drops the token requirement when combined with -LocalMsi, and explicitly stops the agent service and kills the tray before msiexec runs.
# Online upgrade, downloads the current MSI by token
powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1 -Upgrade -TenantToken '<token>'
# Fully offline upgrade, no token needed
powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1 -Upgrade -LocalMsiWhy pre-stop? The MSI declares WixCloseApplications for the tray and ServiceControl Wait for the service, which usually works. But WixCloseApplications sends WM_CLOSE only, so a tray process that is hung or ignoring the message keeps a handle on its own EXE and the install bails with a locked-file 1603. The wrapper force-stops the process instead, which handles that failure mode.
Environment variables
For fleet-deploy wrappers that prefer environment configuration to arguments: PERMITUSB_TOKEN, PERMITUSB_SERVER and PERMITUSB_ENDPOINT_GROUP are fallbacks for the matching parameters.
$env:PERMITUSB_TOKEN = '<token>'
powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1Exit codes
| Code | Meaning |
|---|---|
| 0 | Installed cleanly. A 3010 from msiexec surfaces as 0 with a reboot-requested note. |
| 2 | No token provided, and not an -Upgrade -LocalMsi run. |
| 3 | Not running as Administrator. |
| 4 | 32-bit Windows. |
| 5 | .NET runtime install failed. Check the Windows installer logs. |
| 6 | MSI download failed: token invalid, expired, or a network problem. |
| 7 | -LocalMsi could not find a usable MSI. |
| other | msiexec’s own exit code. Check %TEMP%\PermitUSB-install.log. |
Inspect before running
The script is around 250 lines of readable PowerShell. Download it, open it in an editor, read it, then run it. It is EV code-signed by JJMK Studios, LLC; confirm the publisher with Get-AuthenticodeSignature before you trust it.
Something missing or wrong here? Tell us. Documentation gaps get filled fast.



