PowerShell install script

Install-PermitUSB.ps1 is the recommended way to install, upgrade, and uninstall PermitUSB on Windows endpoints. It wraps the MSI with the bits msiexec alone can't do: prerequisite checks, friendly error messages, automatic .NET 10 Desktop Runtime install, distinct exit codes for fleet-deploy branching, and a purpose-built upgrade path that stops the running agent and tray before swapping binaries.

For raw msiexec usage (GPO software installation, SCCM package commands, embedded-in-MDT sequences), see the MSI installer page instead.

Quickstart

Get the exact command - token pre-filled - from the dashboard's Enrollment page. Roughly:

# From PowerShell as Administrator, in the folder you saved the script to:
powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1 -TenantToken '<token>'

We launch via powershell.exe with -ExecutionPolicy Bypassso the command works the same on any machine regardless of its execution policy - that argument applies only to that one invocation and doesn't change your system-wide policy. The script is EV code-signed, so on a RemoteSigned box it would run without Bypass too.

What the script does

  • checks for admin elevation + 64-bit Windows up front
  • probes for the .NET 10 Desktop Runtime via dotnet --list-runtimes at the arch-specific path (C:\Program Files\dotnet\dotnet.exe on x64, C:\Program Files\dotnet\x64\dotnet.exe on ARM64) and, if missing, downloads + installs it silently from the official Microsoft URL
  • downloads PermitUSB.msi using your enrollment token, OR uses a local copy next to the script if you pass -LocalMsi
  • for upgrades (-Upgrade), stops the agent service and taskkills the tray before running msiexec so the install doesn't fight locked files
  • runs msiexec with the token, server, and optional endpoint group
  • prints a ==> step for each phase and writes a full msiexec log to %TEMP%\PermitUSB-install.log on failure

Parameters

  • -TenantToken - the enrollment token from the dashboard. Required for fresh installs and for upgrades that fetch the MSI from the dashboard (the download endpoint is token-keyed). Optional when combined with -Upgrade -LocalMsi- the existing install's enrollment persists in %ProgramData%\PermitUSB\ across MajorUpgrade.
  • -Server - agent-channel URL. Defaults to https://agent.permitusb.com. Pass only when testing against a non-prod deployment. On upgrades, the script only forwards this to msiexec if you explicitly set it, so a customer on a non-prod server doesn't get silently re-pointed at prod.
  • -EndpointGroup- optional. Pre-assigns the endpoint to a named group. Matched case-insensitively against the tenant's groups; unrecognized names fall back to the default group.
  • -InstallerBaseUrl - where to fetch the MSI from. Defaults to https://permitusb.com. Override if you're hosting an internal mirror.
  • -SkipPrereqs- skip the .NET 10 prereq check + auto-install. Useful when the runtime is being deployed via a separate channel and you want this script to bail loudly if it somehow isn't there yet. -Upgrade implies this.
  • -LocalMsi - use a PermitUSB.msi sitting next to the script instead of downloading from the dashboard. See Offline / vetted installs below.
  • -MsiPath <path>- explicit path to the MSI. Use this when the MSI is somewhere other than the script's folder - a network share, a fleet-deploy staging path that doesn't co-locate files, etc. UNC paths work (\\fileserver\share\PermitUSB.msi). Mutually exclusive with -LocalMsi.
  • -Upgrade - in-place upgrade of an existing install. See Upgrades below.

Modes

Fresh install

powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1 -TenantToken '<token>'

The default. Downloads the MSI by token, installs prerequisites, runs msiexec. Agent enrolls within a minute and appears in the dashboard's Endpoints page as "active".

Offline / vetted installs (-LocalMsi)

For air-gapped fleets, SCCM/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>' -LocalMsi

The script resolves $PSScriptRoot to find the MSI alongside it and prints the MSI's ProductVersion for sanity check before installing. The script must be on disk for this to work - piping it through irm | iex leaves $PSScriptRoot empty and the script fails fast with a clear message.

The local MSI file is not deleted after install - you brought it, you keep it.

Upgrades (-Upgrade)

For in-place upgrades of an existing install. Skips the .NET prereq check (the runtime is already there), drops the -TenantToken requirement (when combined with -LocalMsi - see below), and explicitly stops the agent service and taskkills the tray before msiexec runs.

# Online upgrade (downloads the current MSI via token):
powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1 -Upgrade -TenantToken '<token>'

# Fully offline upgrade (uses local MSI, no token needed):
powershell -ExecutionPolicy Bypass -File .\Install-PermitUSB.ps1 -Upgrade -LocalMsi

Why pre-stop? The MSI itself already declares WixCloseApplications for the tray and ServiceControl Wait="yes" for the service, which usually works. WixCloseApplications sends WM_CLOSE only - a tray process that's hung or ignoring the message keeps a handle on its own EXE and the install bails with exit code 1603 / locked-file. The wrapper using taskkill /F-equivalent (Stop-Process -Force) handles that failure mode.

What survives the upgrade. Everything in %ProgramData%\PermitUSB\ - agent.json, credentials.bin, events.db, policy.bin. The endpoint stays enrolled, keeps its identity, keeps its group assignment, keeps its event queue. On upgrades the script omits TENANT_TOKEN/SERVER/ENDPOINT_GROUP from msiexec unless you explicitly set them, so MajorUpgrade preserves the existing config rather than overwriting it with defaults.

Environment variables

For fleet-deploy wrappers (Intune install scripts, SCCM packages, Ansible tasks) that prefer environment configuration to argv:

  • PERMITUSB_TOKEN - fallback for -TenantToken
  • PERMITUSB_SERVER - fallback for -Server
  • PERMITUSB_ENDPOINT_GROUP - fallback for -EndpointGroup
$env:PERMITUSB_TOKEN = '<token>'
irm https://permitusb.com/install.ps1 | iex

Exit codes

Useful when wrapping the script in an Intune install_script.ps1, SCCM detection clause, or Ansible task. The script sets:

  • 0 - installed cleanly (or 3010 from msiexec internally, surfaced as 0 with a reboot-requested note).
  • 2 - no -TenantToken / $env:PERMITUSB_TOKEN provided (and not -Upgrade -LocalMsi).
  • 3 - not running as Administrator.
  • 4 - 32-bit Windows.
  • 5 - .NET runtime install failed (check Windows installer logs).
  • 6 - MSI download failed (token invalid / expired / network).
  • 7 - -LocalMsi couldn't find a usable MSI (no script-on-disk, or PermitUSB.msi missing from the script's folder).
  • else - msiexec's own exit code; check %TEMP%\PermitUSB-install.log.

Inspect before running

The script is ~250 lines of readable PowerShell. Download it from /Install-PermitUSB.ps1, open it in a text editor, read it, then run it. It's EV code-signed (verified publisher: JJMK Studios, LLC) - confirm the publisher with Get-AuthenticodeSignature before you trust it.

Reset / uninstall

For removing PermitUSB cleanly, including the data directory and bootstrap registry, see the Reset page - Reset-PermitUSB.ps1 is a separate script that handles that side of the lifecycle.