Skip to content

Instantly share code, notes, and snippets.

@asheroto
Created July 21, 2026 16:14
Show Gist options
  • Select an option

  • Save asheroto/1bd1abeed3820e10f36e7813eb758cef to your computer and use it in GitHub Desktop.

Select an option

Save asheroto/1bd1abeed3820e10f36e7813eb758cef to your computer and use it in GitHub Desktop.
Forcefully removes Bitdefender Endpoint Security Tools when uninstall tool doesn't work.

Force-Remove Bitdefender Endpoint Security Tools

A PowerShell script to forcefully remove a stuck Bitdefender Endpoint Security Tools install when the built-in uninstaller fails or refuses to run.

Built for unattended fleet use (NinjaRMM and similar), but runs fine standalone from an elevated prompt.

What it does

  1. Kills any installer.exe running from under the Bitdefender folder.
  2. Aborts (exit 1) if any Bitdefender service is still running — it will not force-stop a live agent, so nothing gets torn out from under a running process.
  3. Deletes the stopped Bitdefender services (epag, EPIntegrationService, EPMaintenanceService, EPSecurityService, EPUpdateService).
  4. Renames C:\Program Files\Bitdefender to Bitdefender.old, taking file ownership (takeown + icacls) if Bitdefender's deny ACLs block the move.
  5. Deletes Bitdefender's registry footprint — the uninstall entry and the HKLM\SOFTWARE\Bitdefender config hive — seizing key ownership if the keys are protected by deny ACLs.

Requirements

  • Windows, elevated (Administrator or SYSTEM). The script declares #Requires -RunAsAdministrator.
  • Windows PowerShell 5.1 or later (uses sc.exe for service deletion, so no PowerShell 6+ dependency).

Usage

# From an elevated PowerShell prompt
powershell -ExecutionPolicy Bypass -File ".\Uninstall BitDefender.ps1"

Or paste it into a NinjaRMM (or other RMM) script and run it as SYSTEM.

Reboot afterward. Deleting the services and files does not unload a Bitdefender kernel/minifilter driver that is already loaded — a restart clears it and finalizes the removal.

Notes & caveats

  • Exit codes: 0 on success, 1 if a service is still running or a service delete fails.
  • Services list is hardcoded. If your Bitdefender build registers different service names, update the $services array.
  • .old folder: the renamed folder is left in place so you can inspect or recover it. Delete it manually once you've confirmed the removal.
  • If a file delete still fails with a sharing violation (not access-denied), a file is genuinely loaded — reboot and re-run.
  • Tested against Bitdefender Endpoint Security Tools 6.6.x.

Warning

This bypasses Bitdefender's tamper protection by design. Only run it on machines you administer, when the supported uninstall path has failed. It permanently deletes services and registry keys.

#Requires -RunAsAdministrator
# Forcefully remove a stuck Bitdefender install when the uninstall tool fails.
# 1. Kill installer.exe running from under the Bitdefender folder.
# 2. Abort if any Bitdefender service is still running.
# 3. Delete the stopped Bitdefender services.
# 4. Rename C:\Program Files\Bitdefender to .old (takes ownership if ACL-locked).
# 5. Delete Bitdefender's registry footprint (uninstall entry + config hive),
# seizing key ownership if protected by deny ACLs.
# Requires admin. Reboot afterward to fully unload any remaining kernel driver.
$ErrorActionPreference = 'Stop'
$bdPath = Join-Path $env:ProgramFiles 'Bitdefender'
$services = 'epag','EPIntegrationService','EPMaintenanceService','EPSecurityService','EPUpdateService'
# 1. Kill any installer.exe running from under the Bitdefender folder.
$installers = Get-CimInstance Win32_Process -Filter "Name = 'installer.exe'" |
Where-Object { $_.ExecutablePath -like "$bdPath\*" }
foreach ($p in $installers) {
Write-Host "Stopping installer.exe (PID $($p.ProcessId)) -> $($p.ExecutablePath)"
Stop-Process -Id $p.ProcessId -Force
}
# 2. Any of our services still running is a hard stop.
$running = Get-Service -Name $services -ErrorAction SilentlyContinue |
Where-Object { $_.Status -ne 'Stopped' }
if ($running) {
Write-Error "Service(s) not stopped: $($running.Name -join ', '). Aborting."
exit 1
}
# 3. Delete the (stopped) services. sc.exe: Remove-Service is PS 6+ only.
foreach ($name in $services) {
if (Get-Service -Name $name -ErrorAction SilentlyContinue) {
Write-Host "Deleting service $name"
sc.exe delete $name | Out-Null
if ($LASTEXITCODE -ne 0) { Write-Error "sc.exe delete $name failed ($LASTEXITCODE)"; exit 1 }
}
}
# Force-delete a Bitdefender-protected folder: take ownership + grant rights first,
# because its files carry deny ACLs that block Remove-Item even as admin.
function Remove-Protected($path) {
takeown.exe /F $path /R /D Y | Out-Null
icacls.exe $path /grant "*S-1-5-32-544:F" /T /C | Out-Null # BUILTIN\Administrators
Remove-Item $path -Recurse -Force
}
# 4. Rename the install folder out of the way.
if (Test-Path $bdPath) {
$old = "$bdPath.old"
if (Test-Path $old) { Remove-Protected $old }
Rename-Item $bdPath $old
Write-Host "Renamed $bdPath -> $old"
}
# --- Registry teardown -----------------------------------------------------
# Enable the privileges needed to seize ownership of protected registry keys:
# the registry counterpart of the takeown/icacls trick used on the folder.
Add-Type @'
using System;
using System.Runtime.InteropServices;
public static class Priv {
[DllImport("advapi32.dll", SetLastError=true)]
static extern bool OpenProcessToken(IntPtr h, int access, out IntPtr token);
[DllImport("advapi32.dll", SetLastError=true)]
static extern bool LookupPrivilegeValue(string host, string name, ref long luid);
[DllImport("advapi32.dll", SetLastError=true)]
static extern bool AdjustTokenPrivileges(IntPtr token, bool disableAll, ref TokPriv tp, int len, IntPtr prev, IntPtr relen);
[DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct TokPriv { public int Count; public long Luid; public int Attr; }
public static void Enable(string name) {
IntPtr token;
OpenProcessToken(GetCurrentProcess(), 0x28, out token); // ADJUST|QUERY
TokPriv tp; tp.Count = 1; tp.Luid = 0; tp.Attr = 0x2; // SE_PRIVILEGE_ENABLED
LookupPrivilegeValue(null, name, ref tp.Luid);
AdjustTokenPrivileges(token, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
}
}
'@
[Priv]::Enable('SeTakeOwnershipPrivilege')
[Priv]::Enable('SeRestorePrivilege')
$admins = New-Object System.Security.Principal.SecurityIdentifier 'S-1-5-32-544' # BUILTIN\Administrators
# Recursively take ownership of an HKLM key tree and grant Administrators full control.
function Reset-RegTreeOwnership([string]$sub) {
$root = [Microsoft.Win32.Registry]::LocalMachine
# Owning requires only the privilege, not prior access.
$k = $root.OpenSubKey($sub, 'ReadWriteSubTree', 'TakeOwnership')
if (-not $k) { return }
$acl = $k.GetAccessControl(); $acl.SetOwner($admins); $k.SetAccessControl($acl); $k.Close()
# As owner, grant ourselves full control.
$k = $root.OpenSubKey($sub, 'ReadWriteSubTree', 'ChangePermissions')
$acl = $k.GetAccessControl()
$acl.ResetAccessRule((New-Object System.Security.AccessControl.RegistryAccessRule(
$admins, 'FullControl', 'ContainerInherit', 'None', 'Allow')))
$k.SetAccessControl($acl); $k.Close()
# Now readable: recurse into children.
$k = $root.OpenSubKey($sub)
foreach ($child in $k.GetSubKeyNames()) { Reset-RegTreeOwnership "$sub\$child" }
$k.Close()
}
function Remove-RegKey([string]$sub) {
$psPath = "HKLM:\$sub"
if (-not (Test-Path $psPath)) { return }
try {
Remove-Item $psPath -Recurse -Force
} catch {
Write-Host "ACL-protected, seizing ownership: HKLM\$sub"
Reset-RegTreeOwnership $sub
Remove-Item $psPath -Recurse -Force
}
Write-Host "Deleted HKLM\$sub"
}
# 5. Remove Bitdefender's registry footprint (orphaned once the folder is gone).
Remove-RegKey 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Endpoint Security'
Remove-RegKey 'SOFTWARE\Bitdefender'
Write-Host "Done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment