Skip to navigation

Deploy the Passport CLI with mobile device management

Deploy the Passport CLI to managed machines with a mobile device management (MDM) tool, such as Jamf or Intune. Developers who manage their own machine should use the self-serve Install the Passport CLI steps instead.

The Passport CLI is installed machine-wide so consumers don’t install it by hand, and a post-install script then provisions the per-user client certificate in each signed-in user’s session by running passport setup.

Each user must sign in with passport login before the post-install script can provision their certificate. The passport setup command reads the signed-in user’s login profile, so it can’t issue a certificate until the user has authenticated.

Install on macOS

To deploy the Passport CLI through your MDM tool, do the following:

  1. Inject a configuration profile containing the Passport Proxy endpoints.
  2. (Optional) Run the uninstall script to clean up any existing installation.
  3. Download the Passport CLI .pkg (Apple silicon or Intel) and deploy it.
  4. Run the post-install script to provision each signed-in user.

The post-install script runs passport setup in the signed-in user’s session:

Post-install script
#!/bin/bash
set -eu
user=$(/usr/bin/stat -f '%Su' /dev/console)
case "$user" in
""|root|loginwindow|_mbsetupuser)
echo "No GUI user; Passport setup must run after user login."
exit 0
;;
esac
uid=$(/usr/bin/id -u "$user")
if ! /bin/launchctl print "gui/$uid" >/dev/null 2>&1; then
echo "GUI session gui/$uid is unavailable; retry after login."
exit 0
fi
exec /bin/launchctl asuser "$uid" \
/usr/bin/sudo -H -u "$user" \
/usr/local/bin/passport setup

Configuration profile

The configuration profile sets the Passport Proxy endpoints and, optionally, a forced routing policy. Supply these settings to your MDM tool as a managed preference for the com.postman.passport domain. The tool forces them in a system-scoped Apple configuration profile and deploys it.

Passport configuration profile example
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ManagementURL</key>
<string>https://passport-management.example.com</string>
<key>ProxyURL</key>
<string>https://passport-proxy.example.com:8443</string>
<key>RoutingPolicy</key>
<dict>
<key>Mode</key>
<string>allowlist</string>
<key>Strict</key>
<true/>
<key>AllowedHosts</key>
<array>
<string>api.example.com</string>
<string>*.internal.example.com</string>
</array>
</dict>
</dict>
</plist>

The Passport settings:

  • ManagementURL — (Optional) The certificate management origin that issues each client certificate. Defaults to ProxyURL when omitted. Equivalent to passport setup --management-url.
  • ProxyURL — The data-plane proxy origin that requests route through, including the port. Required when the profile manages endpoints. Equivalent to passport setup --proxy-url.
  • RoutingPolicy — (Optional) Forces the daemon’s routing policy on managed machines, overriding the user’s local daemon.yml. When present, it must contain all three of the following keys:
    • Mode — Set to forward-all to route every host through the proxy, or allowlist to route only the hosts in AllowedHosts.
    • Strict — In allowlist mode, true denies non-listed hosts with a 502 and false sends them direct to the origin. It has no effect in forward-all mode.
    • AllowedHosts — The hosts to route through the proxy in allowlist mode, as DNS names, IP addresses, or simple * globs (up to 256 entries). Use an empty array in forward-all mode.

For a routing-only profile, omit ManagementURL and ProxyURL.

Install on Windows

Deploy the Passport CLI through Intune, then run the post-install script, configured in Intune as follows:

  • Don’t run the script using the signed-in user’s credentials, so it runs as SYSTEM.
  • Run the script in the 64-bit PowerShell host.
  • Don’t enforce the script signature check.

The script registers a scheduled task that runs passport setup in each user’s session, at sign-in and immediately for the current user, because Intune user-context scripts don’t always run. It logs to C:\ProgramData\Postman\Passport\Deploy.log (SYSTEM) and %LOCALAPPDATA%\Postman\Passport\IntuneSetup.log (per user).

Post-install script
$ErrorActionPreference = 'Stop'
if ($env:PROCESSOR_ARCHITEW6432 -eq 'AMD64' -and -not [Environment]::Is64BitProcess) {
& (Join-Path $env:WINDIR 'SysNative\WindowsPowerShell\v1.0\powershell.exe') -NoProfile -ExecutionPolicy Bypass -File $PSCommandPath
exit $LASTEXITCODE
}
$dir = Join-Path $env:ProgramData 'Postman\Passport'
$log = Join-Path $dir 'Deploy.log'
$userPs1 = Join-Path $dir 'Passport-User-Setup.ps1'
$taskName = 'Postman Passport User Setup'
New-Item -ItemType Directory -Path $dir -Force | Out-Null
function Write-Log($m) { $l = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $m; Add-Content $log $l; Write-Output $l }
try {
Write-Log "Starting as $([Security.Principal.WindowsIdentity]::GetCurrent().Name)"
# 1. Stop any daemon left running as SYSTEM by earlier attempts
Get-CimInstance Win32_Process -Filter "Name like 'passport%'" | Where-Object {
$_.CommandLine -match 'daemon' -and (Invoke-CimMethod -InputObject $_ -MethodName GetOwner).User -eq 'SYSTEM'
} | ForEach-Object { Write-Log "Stopping SYSTEM daemon PID $($_.ProcessId)"; Stop-Process -Id $_.ProcessId -Force }
# 2. Drop the per-user setup script where users can read/execute it
$userScript = @'
# Passport-User-Setup.ps1
# Runs "passport.exe setup" as the SIGNED-IN USER (not SYSTEM).
# Intune: Run using logged on credentials = Yes, 64-bit = Yes, Signature check = No
# Log: %LOCALAPPDATA%\Postman\Passport\IntuneSetup.log
$ErrorActionPreference = 'Stop'
# --- Re-launch in 64-bit PowerShell if started in 32-bit -------------------
if ($env:PROCESSOR_ARCHITEW6432 -eq 'AMD64' -and -not [Environment]::Is64BitProcess) {
$ps64 = Join-Path $env:WINDIR 'SysNative\WindowsPowerShell\v1.0\powershell.exe'
& $ps64 -NoProfile -ExecutionPolicy Bypass -File $PSCommandPath
exit $LASTEXITCODE
}
# --- Logging -----------------------------------------------------------------
$logDir = Join-Path $env:LOCALAPPDATA 'Postman\Passport'
$logFile = Join-Path $logDir 'IntuneSetup.log'
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
function Write-Log {
param([string]$Message)
$line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message
Add-Content -Path $logFile -Value $line
Write-Output $line
}
try {
$user = [Security.Principal.WindowsIdentity]::GetCurrent().Name
Write-Log "Starting. User: $user, 64-bit: $([Environment]::Is64BitProcess)"
# Refuse to run as SYSTEM - setup is per-user
if ($user -eq 'NT AUTHORITY\SYSTEM') {
Write-Log 'ERROR: Running as SYSTEM. Set "Run this script using the logged on credentials" to Yes in Intune.'
exit 1
}
# --- Find install location (64-bit and 32-bit registry views) ------------
$installLocation = $null
foreach ($view in [Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32) {
$base = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $view)
$key = $base.OpenSubKey('SOFTWARE\Postman\Passport CLI')
if ($key) { $installLocation = $key.GetValue('InstallLocation'); $key.Close() }
$base.Close()
if ($installLocation) { break }
}
if (-not $installLocation) {
Write-Log 'ERROR: Passport installation location not found in registry.'
exit 1
}
$passport = Join-Path $installLocation 'passport.exe'
if (-not (Test-Path -LiteralPath $passport -PathType Leaf)) {
Write-Log "ERROR: Passport not found: $passport"
exit 1
}
# --- Run setup ------------------------------------------------------------
Write-Log "Running: `"$passport`" setup"
$output = & $passport --color off --no-animation setup 2>&1 | Out-String
$exitCode = $LASTEXITCODE
if ($null -eq $exitCode) { $exitCode = 0 }
if ($output) { Write-Log "Output:`r`n$output" }
switch ($exitCode) {
0 { Write-Log 'Setup succeeded: credentials ready and daemon running.' }
2 { Write-Log 'ERROR: Partial success - credentials ready but daemon failed to start.' }
default { Write-Log "ERROR: Setup failed with exit code $exitCode." }
}
# --- Confirm daemon state -------------------------------------------------
$status = & $passport daemon status 2>&1 | Out-String
Write-Log "Daemon status:`r`n$status"
exit $exitCode
}
catch {
Write-Log "ERROR: $($_.Exception.Message)"
exit 1
}
'@
Set-Content -Path $userPs1 -Value $userScript -Encoding UTF8 -Force
Write-Log "Wrote $userPs1"
# 3. Scheduled task: runs as whoever is logged on (Users group), at logon
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$userPs1`""
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited # BUILTIN\Users
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-ExecutionTimeLimit (New-TimeSpan -Minutes 10) -StartWhenAvailable
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
-Principal $principal -Settings $settings -Force | Out-Null
Write-Log "Registered task '$taskName'"
# 4. Run it now for the currently signed-in user (if any)
$loggedOn = (Get-CimInstance Win32_ComputerSystem).UserName
if ($loggedOn) {
Start-ScheduledTask -TaskName $taskName
Write-Log "Started task for signed-in user $loggedOn"
} else {
Write-Log 'No user signed in; task will run at next logon.'
}
exit 0
}
catch {
Write-Log "ERROR: $($_.Exception.Message)"
exit 1
}

Uninstall

Run the uninstall script for your operating system.

macOS

Run the uninstall script to remove the Passport CLI and per-user state. Remove the com.postman.passport configuration profile separately through your MDM tool.

Uninstall script
#!/bin/bash
set -eu
CE_PACKAGE="@postman/postman-passport"
user=${4:-$(/usr/bin/stat -f '%Su' /dev/console)}
case "$user" in
""|root|loginwindow|_mbsetupuser|*/*)
echo "No valid GUI user; skipping per-user cleanup."
user=""
;;
esac
if [ -n "$user" ]; then
uid=$(/usr/bin/id -u "$user")
home=$(
/usr/bin/dscl /Search -read "/Users/$user" NFSHomeDirectory |
/usr/bin/sed -n 's/^NFSHomeDirectory: //p'
)
case "$home" in
/*) ;;
*)
echo "Cannot resolve home for $user." >&2
exit 1
;;
esac
# Run a command in the logged-in user's shell/environment.
as_user() {
/bin/launchctl asuser "$uid" \
/usr/bin/sudo -u "$user" -H \
/bin/zsh -lic "$1" >/dev/null 2>&1 || true
}
#
# Stop Lens before removing Passport.
#
echo "Stopping Passport Lens..."
as_user 'command -v passport >/dev/null && passport lens stop'
as_user 'command -v passport >/dev/null && passport lens clean'
#
# Remove Community Edition, regardless of which supported
# package manager was used to install it.
#
echo "Removing Passport Community Edition..."
as_user "command -v npm >/dev/null && npm uninstall -g '$CE_PACKAGE'"
as_user "command -v pnpm >/dev/null && pnpm remove -g '$CE_PACKAGE'"
as_user "command -v bun >/dev/null && bun remove -g '$CE_PACKAGE'"
#
# Stop Passport launch services.
#
for label in \
com.postman.passport-daemon \
com.postman.passport-env
do
/bin/launchctl bootout "gui/$uid/$label" 2>/dev/null || true
/bin/launchctl bootout "gui/0/$label" 2>/dev/null || true
/bin/launchctl bootout "system/$label" 2>/dev/null || true
done
#
# Clear environment injected by Passport.
#
for variable in \
HTTP_PROXY \
HTTPS_PROXY \
NO_PROXY \
http_proxy \
https_proxy \
no_proxy \
NODE_EXTRA_CA_CERTS \
REQUESTS_CA_BUNDLE \
CURL_CA_BUNDLE \
GIT_SSL_CAINFO \
SSL_CERT_FILE
do
/bin/launchctl asuser "$uid" \
/bin/launchctl unsetenv "$variable" 2>/dev/null || true
done
#
# Remove per-user state.
#
/bin/rm -f \
"$home/Library/LaunchAgents/com.postman.passport-daemon.plist" \
"$home/Library/LaunchAgents/com.postman.passport-env.plist"
# Passport owns these directories outright and they are channel-scoped.
for dir in .passport .passport-beta .passport-stage; do
if [ -d "$home/$dir" ]; then
/bin/rm -rf "$home/$dir"
fi
done
echo "Removed Passport state for $user."
fi
#
# Remove managed Passport installation.
#
/bin/rm -f \
/usr/local/bin/passport \
/usr/local/share/man/man1/passport.1
/bin/rm -rf /usr/local/libexec/postman-passport
/usr/sbin/pkgutil --forget com.postman.passport-cli >/dev/null 2>&1 || true
echo "Passport removed."
echo "Remove the com.postman.passport profile separately through Jamf."
echo "Restart open applications to clear inherited environment values."

Windows

Run the uninstall script as SYSTEM, using the same Intune settings as the post-install script. Run it instead of uninstalling the installer package (MSI) on its own, because the per-user teardown needs passport.exe. The script does the following:

  1. Removes the Postman Passport User Setup scheduled task registered at install time.
  2. Runs the per-user teardown in the signed-in user’s session.
  3. Stops leftover Passport processes and removes the Passport certificate authority from LocalMachine\Root.
  4. Uninstalls the MSI, located by its UpgradeCode.

Only the signed-in user is cleaned up; other profiles keep their .passport state. Remove the HKLM\Software\Policies\Postman\Passport policy separately through Intune or Group Policy.

Uninstall script
$ErrorActionPreference = 'Stop'
if ($env:PROCESSOR_ARCHITEW6432 -eq 'AMD64' -and -not [Environment]::Is64BitProcess) {
& (Join-Path $env:WINDIR 'SysNative\WindowsPowerShell\v1.0\powershell.exe') -NoProfile -ExecutionPolicy Bypass -File $PSCommandPath
exit $LASTEXITCODE
}
$dir = Join-Path $env:ProgramData 'Postman\Passport'
$log = Join-Path $dir 'Uninstall.log'
$msiLog = Join-Path $dir 'Uninstall-msi.log'
$setupPs1 = Join-Path $dir 'Passport-User-Setup.ps1'
$cleanupPs1 = Join-Path $dir 'Passport-User-Cleanup.ps1'
$setupTask = 'Postman Passport User Setup'
$cleanupTask = 'Postman Passport User Cleanup'
$upgradeCode = '{57DA6E9B-820A-4DE7-BB47-8EE9D7A36CA5}'
$stateDirs = '.passport', '.passport-beta', '.passport-stage'
$failed = $false
New-Item -ItemType Directory -Path $dir -Force | Out-Null
function Write-Log($m) { $l = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $m; Add-Content $log $l; Write-Output $l }
try {
Write-Log "Starting as $([Security.Principal.WindowsIdentity]::GetCurrent().Name)"
# 1. Remove the install-time logon task first so it cannot re-run setup mid-uninstall
Stop-ScheduledTask -TaskName $setupTask -ErrorAction SilentlyContinue
if (Get-ScheduledTask -TaskName $setupTask -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $setupTask -Confirm:$false
Write-Log "Removed task '$setupTask'"
}
# 2. Find the install location (64-bit and 32-bit registry views)
$installLocation = $null
foreach ($view in [Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32) {
$base = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $view)
$key = $base.OpenSubKey('SOFTWARE\Postman\Passport CLI')
if ($key) { $installLocation = $key.GetValue('InstallLocation'); $key.Close() }
$base.Close()
if ($installLocation) { break }
}
$passport = ''
if ($installLocation) {
$candidate = Join-Path $installLocation 'passport.exe'
if (Test-Path -LiteralPath $candidate -PathType Leaf) { $passport = $candidate }
}
if ($passport) { Write-Log "Found $passport" } else { Write-Log 'WARNING: passport.exe not found; skipping the CLI-based teardown.' }
# 3. Resolve the signed-in user and their profile folder
$loggedOn = (Get-CimInstance Win32_ComputerSystem).UserName
$profilePath = $null
if ($loggedOn) {
try {
$sid = (New-Object System.Security.Principal.NTAccount($loggedOn)).Translate([System.Security.Principal.SecurityIdentifier]).Value
$profilePath = (Get-CimInstance Win32_UserProfile -Filter "SID='$sid'").LocalPath
} catch {
Write-Log "WARNING: could not resolve the profile of $loggedOn ($($_.Exception.Message))"
}
}
# 4. Read the CA thumbprints now - the per-user teardown deletes ca.pem
$caThumbprints = @()
if ($profilePath) {
foreach ($stateDir in $stateDirs) {
$pem = Join-Path $profilePath "$stateDir\ca.pem"
if (Test-Path -LiteralPath $pem -PathType Leaf) {
try {
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pem)
$caThumbprints += $cert.Thumbprint
} catch {
Write-Log "WARNING: could not read $pem ($($_.Exception.Message))"
}
}
}
}
# 5. Per-user teardown, run as the signed-in user via a one-shot scheduled task
if (-not $loggedOn -or -not $profilePath) {
Write-Log 'WARNING: No signed-in user; skipping per-user cleanup (daemon, HKCU environment, ~\.passport state remain).'
} else {
$userScript = @'
# Passport-User-Cleanup.ps1
# Runs as the SIGNED-IN USER (launched by the Passport uninstall script via a one-shot scheduled task).
# Log: %LOCALAPPDATA%\Postman\Passport\IntuneUninstall.log
param([string]$PassportExe = '')
$ErrorActionPreference = 'Continue'
$logDir = Join-Path $env:LOCALAPPDATA 'Postman\Passport'
$logFile = Join-Path $logDir 'IntuneUninstall.log'
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
function Write-Log {
param([string]$Message)
$line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message
Add-Content -Path $logFile -Value $line
Write-Host $line
}
function Invoke-Passport {
param([string[]]$Arguments)
$output = & $PassportExe --color off --no-animation @Arguments 2>&1 | Out-String
$exitCode = $LASTEXITCODE
if ($null -eq $exitCode) { $exitCode = 0 }
Write-Log "passport $($Arguments -join ' ') -> exit $exitCode"
if ($output.Trim()) { Write-Log "Output:`r`n$output" }
return $exitCode
}
$failed = $false
try {
Write-Log "Starting. User: $([Security.Principal.WindowsIdentity]::GetCurrent().Name)"
if ($PassportExe -and (Test-Path -LiteralPath $PassportExe -PathType Leaf)) {
# Lens is best-effort: a non-zero exit just means it was not running
[void](Invoke-Passport @('lens', 'stop'))
[void](Invoke-Passport @('lens', 'clean'))
# Stops the daemon, removes the Startup Apps entry, restores HKCU\Environment
if ((Invoke-Passport @('daemon', 'uninstall')) -ne 0) { $failed = $true }
} else {
Write-Log 'WARNING: passport.exe not available; skipping lens/daemon teardown.'
}
# Passport owns these directories outright and they are channel-scoped
foreach ($stateDir in '.passport', '.passport-beta', '.passport-stage') {
$target = Join-Path $env:USERPROFILE $stateDir
if (Test-Path -LiteralPath $target) {
try {
Remove-Item -LiteralPath $target -Recurse -Force -ErrorAction Stop
Write-Log "Removed $target"
} catch {
Write-Log "ERROR: could not remove $target ($($_.Exception.Message))"
$failed = $true
}
}
}
if ($failed) { exit 1 }
Write-Log 'Per-user cleanup complete.'
exit 0
}
catch {
Write-Log "ERROR: $($_.Exception.Message)"
exit 1
}
'@
Set-Content -Path $cleanupPs1 -Value $userScript -Encoding UTF8 -Force
Write-Log "Wrote $cleanupPs1"
try {
$taskArgs = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$cleanupPs1`""
if ($passport) { $taskArgs += " -PassportExe `"$passport`"" }
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $taskArgs
$principal = New-ScheduledTaskPrincipal -UserId $loggedOn -LogonType Interactive -RunLevel Limited
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-ExecutionTimeLimit (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName $cleanupTask -Action $action -Principal $principal -Settings $settings -Force | Out-Null
$startedAt = (Get-Date).AddSeconds(-2)
Start-ScheduledTask -TaskName $cleanupTask
Write-Log "Started per-user cleanup for $loggedOn"
$deadline = (Get-Date).AddMinutes(5)
$finished = $false
while (-not $finished -and (Get-Date) -lt $deadline) {
Start-Sleep -Seconds 2
$state = (Get-ScheduledTask -TaskName $cleanupTask).State
$info = Get-ScheduledTaskInfo -TaskName $cleanupTask
if ($state -ne 'Running' -and $state -ne 'Queued' -and $info.LastRunTime -gt $startedAt) { $finished = $true }
}
$userLog = Join-Path $profilePath 'AppData\Local\Postman\Passport\IntuneUninstall.log'
if (Test-Path -LiteralPath $userLog) { Write-Log "User cleanup log (tail):`r`n$(Get-Content -LiteralPath $userLog -Tail 40 | Out-String)" }
if (-not $finished) {
Write-Log 'ERROR: Per-user cleanup did not finish within 5 minutes.'
Stop-ScheduledTask -TaskName $cleanupTask -ErrorAction SilentlyContinue
$failed = $true
} elseif ($info.LastTaskResult -ne 0) {
Write-Log "ERROR: Per-user cleanup failed with result $($info.LastTaskResult)."
$failed = $true
} else {
Write-Log 'Per-user cleanup succeeded.'
}
} finally {
Unregister-ScheduledTask -TaskName $cleanupTask -Confirm:$false -ErrorAction SilentlyContinue
}
}
# 6. Stop any Passport process still running from the install folder so the MSI can remove it
if ($installLocation) {
Get-Process -Name 'passport', 'passport-daemon' -ErrorAction SilentlyContinue | Where-Object {
$_.Path -and $_.Path.StartsWith($installLocation, [StringComparison]::OrdinalIgnoreCase)
} | ForEach-Object {
Write-Log "Stopping $($_.Name) PID $($_.Id)"
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
}
Start-Sleep -Seconds 2
}
# 7. Remove the Passport CA from LocalMachine\Root (SYSTEM is already elevated)
if ($caThumbprints.Count -gt 0) {
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root', 'LocalMachine')
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
try {
foreach ($thumbprint in ($caThumbprints | Select-Object -Unique)) {
$found = $store.Certificates.Find('FindByThumbprint', $thumbprint, $false)
if ($found.Count -eq 0) { Write-Log "Passport CA $thumbprint is not in LocalMachine\Root"; continue }
foreach ($certificate in $found) { $store.Remove($certificate) }
Write-Log "Removed Passport CA $thumbprint from LocalMachine\Root"
}
} catch {
Write-Log "ERROR: could not remove the Passport CA ($($_.Exception.Message))"
$failed = $true
} finally {
$store.Close()
}
} else {
Write-Log 'No Passport CA found to remove.'
}
# 8. Uninstall the MSI, located by its UpgradeCode
$installer = New-Object -ComObject WindowsInstaller.Installer
$productCodes = @($installer.RelatedProducts($upgradeCode))
if ($productCodes.Count -eq 0) {
Write-Log 'Passport MSI is not installed.'
}
foreach ($productCode in $productCodes) {
Write-Log "Uninstalling MSI $productCode"
$msi = Start-Process -FilePath (Join-Path $env:WINDIR 'System32\msiexec.exe') `
-ArgumentList "/x $productCode /qn /norestart /l*v `"$msiLog`"" -Wait -PassThru
switch ($msi.ExitCode) {
0 { Write-Log 'MSI uninstalled.' }
3010 { Write-Log 'MSI uninstalled; a restart is required to finish.' }
1605 { Write-Log 'MSI was already uninstalled.' }
default { Write-Log "ERROR: msiexec failed with exit code $($msi.ExitCode). See $msiLog"; $failed = $true }
}
}
# 9. Remove the helper scripts (logs are kept)
Remove-Item -LiteralPath $setupPs1, $cleanupPs1 -Force -ErrorAction SilentlyContinue
if ($failed) {
Write-Log 'Passport removal finished with errors; see above.'
exit 1
}
Write-Log 'Passport removed.'
Write-Log 'Remove the HKLM\Software\Policies\Postman\Passport policy separately through Intune/GPO.'
Write-Log 'Restart open applications to clear inherited environment values.'
exit 0
}
catch {
Write-Log "ERROR: $($_.Exception.Message)"
exit 1
}