$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
}