Mapped drives reported Unavailable on endpoints where the operator could see them fine. The data path was intact; the collection was running under the wrong token. Mapped drives are per logon token, so an elevated probe enumerates a different drive table than the interactive user. Collection: - Pin the user-context spool path so the probe and service agree on one location, and persist/overwrite it instead of deleting on read. - Refuse to collect while elevated (exit code 2) rather than emitting a confident wrong answer, and stamp elevated/username on the spool. - Reject spools stamped elevated or from an unexpected account on read, so an admin run cannot masquerade as valid. - Log netuse/smbmapping/userlistener counts so each run leaves evidence of what it actually saw. Install and launch context: - Move collection to a 'ShopDB user-context probe' scheduled task under S-1-5-32-545 at RunLevel Limited, the only mechanism that reliably runs in the operator's own session. Verify registration and record it in the install ledger. - Stop starting the tray as a child of the elevated installer; explorer.exe de-elevates without changing ACCOUNT, so an admin install produced an admin tray. The Run key only fires at logon, which left the tray dead on a machine already signed in, so add a transient 'ShopDB tray first launch' task that starts it once in the existing session and unregisters itself. - Hide the console window during --collect-usercontext. The agent is a console exe; the tray used to mask this with CreateNoWindow, but the scheduled task invokes it directly and flashed a window every cycle. Installer edits are in _install_script() in plugins/endpoints/api/siteagent.py, which generates the install.ps1 that actually ships. The standalone agent/installer/install-shopdbagent.ps1 was never shipped once the package moved to the UI builder and is deleted here to stop the divergence that caused an earlier fix to land in a file no endpoint ever ran. Versions: agent 1.10.1, tray 1.3.0. These share the pinned spool path and must be deployed together. Pre-existing test failures unrelated to this work (8, identical on a clean tree): docs publishable/versions, plugin contract symlink scan, import guard tamper check, security headers upload sandbox, and three endpoint timestamp tests.
232 lines
9.9 KiB
PowerShell
232 lines
9.9 KiB
PowerShell
# uninstall-shopdbagent.ps1
|
|
# ShopDB Endpoint Agent uninstaller. Run from an ELEVATED (admin) PowerShell on
|
|
# the endpoint. Reverses everything the site agent generator's install.ps1
|
|
# creates:
|
|
# - stops and deletes the Windows service
|
|
# - kills any running agent/tray processes
|
|
# - removes the tray logon Run key (64-bit and 32-bit views)
|
|
# - unregisters the ShopDB user-context probe scheduled task
|
|
# - removes the credential hive HKLM\SOFTWARE\GE\ShopDB (both views)
|
|
# - removes the Defender path exclusion for the install directory
|
|
# - deletes the install directory and the ProgramData data/log tree
|
|
#
|
|
# Every step is best-effort and idempotent: a missing service, key, process, or
|
|
# folder is a no-op, so the script is safe to re-run and safe on a partially
|
|
# installed host. Nothing here fails the run just because an artifact is already
|
|
# gone; only an unexpected error on a present artifact surfaces as a warning.
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string] $InstallDir = "C:\Program Files\ShopDB\EndpointAgent",
|
|
[string] $ServiceName = "ShopDbEndpointAgent",
|
|
[string] $TrayExeName = "shopdb-endpoint-tray.exe",
|
|
|
|
# Scheduled task that runs the user-context probe in the operator's session.
|
|
# Must match the installer's -UserContextTaskName.
|
|
[string] $UserContextTaskName = "ShopDB user-context probe",
|
|
|
|
# Also remove any server TLS certificate the site-agent installer imported
|
|
# into the machine Trusted Root store. Off by default because a root cert is
|
|
# shared trust material: only pull it when you are sure no other service on
|
|
# this host relies on it. Match is by subject CN substring.
|
|
[string] $RemoveRootCertMatching = "",
|
|
|
|
# Keep the ProgramData logs (C:\ProgramData\ShopDB) for post-mortem instead
|
|
# of deleting them with everything else.
|
|
[switch] $KeepLogs
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
throw "This script must be run from an elevated (Run as administrator) PowerShell."
|
|
}
|
|
|
|
# Delete an HKLM value or subkey from BOTH the 64-bit and 32-bit registry views.
|
|
# The installer writes both views (a stale WOW6432Node copy would otherwise be
|
|
# read first by the agent), so a clean uninstall has to clear both.
|
|
function Remove-HklmBothViews {
|
|
param(
|
|
[Parameter(Mandatory)] [string] $SubKey,
|
|
[string] $Name
|
|
)
|
|
foreach ($view in @([Microsoft.Win32.RegistryView]::Registry64,
|
|
[Microsoft.Win32.RegistryView]::Registry32)) {
|
|
$base = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
|
|
[Microsoft.Win32.RegistryHive]::LocalMachine, $view)
|
|
try {
|
|
if ($Name) {
|
|
$key = $base.OpenSubKey($SubKey, $true)
|
|
if ($key) {
|
|
try {
|
|
if ($null -ne $key.GetValue($Name)) { $key.DeleteValue($Name, $false) }
|
|
}
|
|
finally { $key.Dispose() }
|
|
}
|
|
}
|
|
else {
|
|
if ($base.OpenSubKey($SubKey)) {
|
|
$base.DeleteSubKeyTree($SubKey, $false)
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning " registry cleanup ($view $SubKey $Name): $($_.Exception.Message)"
|
|
}
|
|
finally { $base.Dispose() }
|
|
}
|
|
}
|
|
|
|
Write-Host "== ShopDB endpoint agent uninstall ==" -ForegroundColor Cyan
|
|
Write-Host "InstallDir : $InstallDir"
|
|
Write-Host "ServiceName : $ServiceName"
|
|
|
|
# 1. Stop and delete the Windows service.
|
|
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
|
if ($service) {
|
|
Write-Host "[1/6] Stopping and deleting service $ServiceName ..."
|
|
if ($service.Status -ne "Stopped") {
|
|
try { Stop-Service -Name $ServiceName -Force -ErrorAction Stop }
|
|
catch { Write-Warning " could not stop service: $($_.Exception.Message)" }
|
|
}
|
|
sc.exe delete $ServiceName | Out-Null
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
else {
|
|
Write-Host "[1/6] Service $ServiceName not present; skipping."
|
|
}
|
|
|
|
# 2. Kill any lingering agent/tray processes (service host, tray, and anything
|
|
# still running out of the install directory holding files open).
|
|
Write-Host "[2/6] Stopping agent and tray processes ..."
|
|
$trayBase = [System.IO.Path]::GetFileNameWithoutExtension($TrayExeName)
|
|
foreach ($name in @("shopdbagent", $trayBase)) {
|
|
foreach ($proc in (Get-Process -Name $name -ErrorAction SilentlyContinue)) {
|
|
try { Stop-Process -Id $proc.Id -Force -ErrorAction Stop; Write-Host " killed $name (pid $($proc.Id))" }
|
|
catch { Write-Warning " could not stop $name (pid $($proc.Id)): $($_.Exception.Message)" }
|
|
}
|
|
}
|
|
foreach ($proc in (Get-Process -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Path -and $_.Path.StartsWith($InstallDir, [StringComparison]::OrdinalIgnoreCase) })) {
|
|
try { Stop-Process -Id $proc.Id -Force -ErrorAction Stop; Write-Host " killed $($proc.ProcessName) from install dir (pid $($proc.Id))" }
|
|
catch { Write-Warning " could not stop pid $($proc.Id): $($_.Exception.Message)" }
|
|
}
|
|
Start-Sleep -Seconds 1
|
|
|
|
# 3. Remove the tray logon Run key entry (both views) and the credential hive.
|
|
Write-Host "[3/6] Removing registry entries ..."
|
|
Remove-HklmBothViews -SubKey "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "ShopDbEndpointTray"
|
|
|
|
# Unregister the probe task. Left behind it would keep firing at every logon
|
|
# against a deleted exe, which shows up as a recurring task-failure history on a
|
|
# machine nobody believes has the agent on it any more.
|
|
try {
|
|
if (Get-ScheduledTask -TaskName $UserContextTaskName -ErrorAction SilentlyContinue) {
|
|
Unregister-ScheduledTask -TaskName $UserContextTaskName -Confirm:$false -ErrorAction Stop
|
|
Write-Host " removed scheduled task '$UserContextTaskName'"
|
|
}
|
|
else {
|
|
Write-Host " scheduled task '$UserContextTaskName' not present."
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning " could not remove scheduled task '$UserContextTaskName': $($_.Exception.Message)"
|
|
}
|
|
|
|
# The install's one-shot tray launcher is transient and normally removes itself,
|
|
# but an install that failed partway can leave it behind.
|
|
try {
|
|
if (Get-ScheduledTask -TaskName "ShopDB tray first launch" -ErrorAction SilentlyContinue) {
|
|
Unregister-ScheduledTask -TaskName "ShopDB tray first launch" -Confirm:$false -ErrorAction Stop
|
|
Write-Host " removed leftover scheduled task 'ShopDB tray first launch'"
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning " could not remove scheduled task 'ShopDB tray first launch': $($_.Exception.Message)"
|
|
}
|
|
Remove-HklmBothViews -SubKey "SOFTWARE\GE\ShopDB"
|
|
|
|
# Remove the All-Users Start Menu shortcut the site-agent installer created.
|
|
$commonPrograms = [Environment]::GetFolderPath([Environment+SpecialFolder]::CommonPrograms)
|
|
$shortcutPath = Join-Path $commonPrograms "ShopDB Agent.lnk"
|
|
if (Test-Path $shortcutPath) {
|
|
try { Remove-Item -Path $shortcutPath -Force -ErrorAction Stop; Write-Host " removed Start Menu shortcut $shortcutPath" }
|
|
catch { Write-Warning " could not remove Start Menu shortcut: $($_.Exception.Message)" }
|
|
}
|
|
|
|
# Drop the parent GE key only if we emptied it (some sites nest siblings under
|
|
# GE such as SFLD; never tree-delete a key we do not own).
|
|
foreach ($view in @([Microsoft.Win32.RegistryView]::Registry64,
|
|
[Microsoft.Win32.RegistryView]::Registry32)) {
|
|
$base = [Microsoft.Win32.RegistryKey]::OpenBaseKey(
|
|
[Microsoft.Win32.RegistryHive]::LocalMachine, $view)
|
|
try {
|
|
$ge = $base.OpenSubKey("SOFTWARE\GE")
|
|
if ($ge -and $ge.SubKeyCount -eq 0 -and $ge.ValueCount -eq 0) {
|
|
$ge.Dispose()
|
|
$base.DeleteSubKey("SOFTWARE\GE", $false)
|
|
Write-Host " removed empty SOFTWARE\GE ($view)"
|
|
}
|
|
elseif ($ge) { $ge.Dispose() }
|
|
}
|
|
catch { Write-Warning " GE key cleanup ($view): $($_.Exception.Message)" }
|
|
finally { $base.Dispose() }
|
|
}
|
|
|
|
# 4. Remove the Defender path exclusion the installer added for the install dir.
|
|
Write-Host "[4/6] Removing Defender exclusion ..."
|
|
if (Get-Command Remove-MpPreference -ErrorAction SilentlyContinue) {
|
|
try {
|
|
Remove-MpPreference -ExclusionPath $InstallDir -ErrorAction Stop
|
|
Write-Host " removed Defender exclusion for $InstallDir"
|
|
}
|
|
catch { Write-Warning " could not remove Defender exclusion (tamper protection or policy?): $($_.Exception.Message)" }
|
|
}
|
|
else {
|
|
Write-Host " Defender cmdlets not present; skipping."
|
|
}
|
|
|
|
# 5. Optionally remove an imported server root certificate.
|
|
if (-not [string]::IsNullOrWhiteSpace($RemoveRootCertMatching)) {
|
|
Write-Host "[5/6] Removing Trusted Root certs matching '$RemoveRootCertMatching' ..."
|
|
$matches = Get-ChildItem Cert:\LocalMachine\Root -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Subject -like "*$RemoveRootCertMatching*" }
|
|
foreach ($cert in $matches) {
|
|
try {
|
|
Remove-Item -Path $cert.PSPath -Force -ErrorAction Stop
|
|
Write-Host " removed $($cert.Subject) [$($cert.Thumbprint)]"
|
|
}
|
|
catch { Write-Warning " could not remove $($cert.Thumbprint): $($_.Exception.Message)" }
|
|
}
|
|
if (-not $matches) { Write-Host " no matching root certs found." }
|
|
}
|
|
else {
|
|
Write-Host "[5/6] Skipping root-cert removal (pass -RemoveRootCertMatching to enable)."
|
|
}
|
|
|
|
# 6. Delete the install directory and the ProgramData data/log tree.
|
|
Write-Host "[6/6] Removing files ..."
|
|
if (Test-Path $InstallDir) {
|
|
try { Remove-Item -Path $InstallDir -Recurse -Force -ErrorAction Stop; Write-Host " removed $InstallDir" }
|
|
catch { Write-Warning " could not remove $InstallDir (a file may still be locked): $($_.Exception.Message)" }
|
|
}
|
|
else { Write-Host " $InstallDir not present." }
|
|
|
|
$dataDir = Join-Path $env:ProgramData "ShopDB"
|
|
if (-not $KeepLogs -and (Test-Path $dataDir)) {
|
|
try { Remove-Item -Path $dataDir -Recurse -Force -ErrorAction Stop; Write-Host " removed $dataDir" }
|
|
catch { Write-Warning " could not remove $dataDir : $($_.Exception.Message)" }
|
|
}
|
|
elseif ($KeepLogs) { Write-Host " keeping $dataDir (-KeepLogs)." }
|
|
|
|
# Also prune an empty C:\Program Files\ShopDB parent left behind.
|
|
$parent = Split-Path -Parent $InstallDir
|
|
if ((Test-Path $parent) -and -not (Get-ChildItem -Path $parent -Force -ErrorAction SilentlyContinue)) {
|
|
try { Remove-Item -Path $parent -Force -ErrorAction Stop; Write-Host " removed empty $parent" } catch { }
|
|
}
|
|
|
|
Write-Host "`nUninstall complete." -ForegroundColor Green
|
|
Write-Host "Verify with: Get-Service $ServiceName ; Get-Process shopdbagent,$trayBase ; reg query 'HKLM\SOFTWARE\GE\ShopDB'"
|