Skip to main content

Overview

BCU uses a comprehensive multi-source detection system to find installed applications. Unlike Windows’ built-in “Apps & Features”, BCU looks beyond the registry to detect applications from Windows Store, Steam, portable apps, and orphaned installations.

Detection Architecture

BCU employs a factory pattern with specialized detectors for different application types:
public static IList<ApplicationUninstallerEntry> GetUninstallerEntries(
    ListGenerationProgress.ListGenerationCallback callback)
{
    const int totalStepCount = 8;
    var currentStep = 1;

    // 1. Find MSI products
    var msiProducts = MsiTools.MsiEnumProducts().ToList();

    // 2. Find stuff mentioned in registry
    var registryFactory = new RegistryFactory(msiProducts);
    var registryResults = registryFactory.GetUninstallerEntries(callback);

    // 3. Look for entries on drives
    var driveFactory = new DirectoryFactory(registryResults);
    var driveResults = driveFactory.GetUninstallerEntries(callback);

    // 4. Find apps from stores and other sources
    var otherResults = GetMiscUninstallerEntries(callback);

    // 5. Merge and deduplicate
    MergeResults(mergedResults, otherResults, callback);
    MergeResults(mergedResults, driveResults, callback);

    return mergedResults;
}

Detection Sources

Windows Registry

Primary source for traditionally installed applications

Windows Store

UWP and modern Windows Store applications

Steam

Games and applications from Steam platform

Chocolatey

Package manager installed applications

Scoop

Portable applications managed by Scoop

Oculus

VR applications from Oculus platform

Windows Features

Optional Windows features and components

Directory Scan

Orphaned apps found by scanning Program Files

Registry Detection

The primary source for most applications is the Windows registry:
BCU scans multiple registry paths:
private static readonly string RegUninstallersKeyDirect =
    @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
private static readonly string RegUninstallersKeyWow =
    @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
32-bit Applications on 64-bit Windows: Found in the Wow6432Node path64-bit Applications: Found in the direct pathPer-User Installations: Scanned in both HKLM and HKCU hives

MSI Product Detection

Windows Installer (MSI) applications require special handling:
// Enumerate all MSI products
var msiProducts = MsiTools.MsiEnumProducts().ToList();

// Check if entry is MSI-based
if (ApplicationEntryTools.PathPointsToMsiExec(uninstallString) &&
    MsiTools.MsiEnumProducts().All(g => !g.Equals(entry.BundleProviderKey)))
{
    // Product was uninstalled
    CurrentStatus = UninstallStatus.Completed;
}
MSI Special Handling: BCU re-enumerates MSI products during batch operations to detect when MSI applications are removed by other uninstallers.

Windows Store Apps

Modern Windows Store applications are detected using PowerShell integration:
public class StoreAppFactory : IIndependantUninstallerFactory
{
    private static string HelperPath { get; } = 
        Path.Combine(UninstallToolsGlobalConfig.AssemblyLocation, 
                     @"StoreAppHelper.exe");

    public IList<ApplicationUninstallerEntry> GetUninstallerEntries(
        ListGenerationProgress.ListGenerationCallback progressCallback)
    {
        var results = new List<ApplicationUninstallerEntry>();
        if (!IsHelperAvailable()) return results;

        var output = FactoryTools.StartHelperAndReadOutput(HelperPath, "/query");
        
        foreach (var data in FactoryTools.ExtractAppDataSetsFromHelperOutput(output))
        {
            var fullName = data["FullName"];
            var result = new ApplicationUninstallerEntry
            {
                Comment = fullName,
                RatingId = fullName.Substring(0, fullName.IndexOf("_")),
                UninstallString = $"\"{HelperPath}\" /uninstall \"{fullName}\"",
                RawDisplayName = data["DisplayName"],
                Publisher = data["PublisherDisplayName"],
                UninstallerKind = UninstallerType.StoreApp,
                InstallLocation = data["InstalledLocation"],
                IsProtected = Convert.ToBoolean(data["IsProtected"])
            };
            results.Add(result);
        }
        return results;
    }
}
  • FullName: Complete package identity including version and architecture
  • DisplayName: User-friendly application name (may be localized)
  • PublisherDisplayName: Application publisher
  • InstalledLocation: Installation directory path
  • IsProtected: Whether the app is a system component
  • Logo: Application icon/logo path

Steam Applications

Steam games and applications are detected through Steam’s API:
public class SteamFactory : IIndependantUninstallerFactory
{
    private static bool GetSteamInfo(out string steamLocation)
    {
        if (File.Exists(SteamHelperPath))
        {
            var output = FactoryTools.StartHelperAndReadOutput(
                SteamHelperPath, "steam");
            if (!string.IsNullOrEmpty(output) && Directory.Exists(output))
            {
                steamLocation = output.Trim().TrimEnd('\\', '/');
                return true;
            }
        }
        return false;
    }

    public IList<ApplicationUninstallerEntry> GetUninstallerEntries(
        ListGenerationProgress.ListGenerationCallback progressCallback)
    {
        var results = new List<ApplicationUninstallerEntry>();
        if (!GetSteamInfo(out var steamLocation)) return results;

        var output = FactoryTools.StartHelperAndReadOutput(
            SteamHelperPath, "l /i");
        
        foreach (var data in FactoryTools.ExtractAppDataSetsFromHelperOutput(output))
        {
            if (!int.TryParse(data["AppId"], out var appId)) continue;

            var entry = new ApplicationUninstallerEntry
            {
                DisplayName = data["Name"],
                UninstallString = data["UninstallString"],
                InstallLocation = data["InstallDirectory"],
                UninstallerKind = UninstallerType.Steam,
                RatingId = "Steam App " + appId.ToString("G")
            };

            if (long.TryParse(data["SizeOnDisk"], out var bytes))
                entry.EstimatedSize = FileSize.FromBytes(bytes);

            results.Add(entry);
        }
        return results;
    }
}

Directory Scanner

BCU can detect orphaned applications by scanning installation directories:
public class DirectoryFactory : IUninstallerFactory
{
    public IList<ApplicationUninstallerEntry> GetUninstallerEntries(
        ListGenerationProgress.ListGenerationCallback progressCallback)
    {
        var results = new List<ApplicationUninstallerEntry>();
        
        // Scan common installation directories
        foreach (var programFilesDir in GetProgramFilesDirs())
        {
            foreach (var subDir in Directory.GetDirectories(programFilesDir))
            {
                // Check if already known from registry
                if (AlreadyKnown(subDir)) continue;
                
                // Try to create entry from directory
                var entry = TryCreateFromDirectory(
                    new DirectoryInfo(subDir));
                if (entry != null)
                    results.Add(entry);
            }
        }
        
        return results;
    }
}

Supported Installer Types

BCU detects and handles various installer technologies:

Windows Installer (MSI)

Standard Windows Installer packages with GUID-based identification

NSIS (Nullsoft)

Nullsoft Scriptable Install System with quiet uninstall detection

Inno Setup

Inno Setup installers with silent parameter generation

InstallShield

InstallShield installers with response file support

Wise Installer

Wise Installation System packages

Custom Uninstallers

Application-specific uninstall executables

Simple Delete

Direct folder deletion for portable/orphaned apps

PowerShell

PowerShell-based uninstall scripts (Store apps)

Uninstaller Type Detection

BCU automatically identifies the installer type:
public enum UninstallerType
{
    Unknown = 0,
    Msiexec,
    Nsis,
    InnoSetup,
    InstallShield,
    SteamApp,
    StoreApp,
    Chocolatey,
    Scoop,
    WindowsFeature,
    WindowsUpdate,
    SimpleDelete,
    PowerShell
}

Information Enrichment

After initial detection, BCU enriches entries with additional information:
Extracts digital signature information:
public X509Certificate2 GetCertificate()
{
    if (!_certificateGotten)
    {
        _certificateGotten = true;
        _certificate = CertificateGetter.TryGetCertificate(this);
        
        if (_certificate != null)
            _certificateValid = _certificate.Verify();
    }
    return _certificate;
}
Determines installation size:
  • Reads EstimatedSize from registry (in KB)
  • Scans installation directory if not available
  • Uses fast size calculation for large directories
  • Caches results for performance
Gets application icons:
  1. Parse DisplayIcon registry value
  2. Extract icon from executable
  3. Look for .ico files in install directory
  4. Use default application icon as fallback
Finds main application executables:
internal string[] SortedExecutables { get; set; }

public IEnumerable<string> GetSortedExecutables()
{
    if (SortedExecutables == null)
        return Enumerable.Empty<string>();
        
    var output = SortedExecutables.AsEnumerable();
    if (!string.IsNullOrEmpty(UninstallerFullFilename))
        output = output.OrderBy(x => 
            x.Equals(UninstallerFullFilename, 
                    StringComparison.InvariantCultureIgnoreCase));
    return output;
}
Generates silent uninstall commands:
  • NSIS: Adds /S parameter
  • Inno Setup: Adds /VERYSILENT /SUPPRESSMSGBOXES
  • MSI: Uses msiexec /x {GUID} /qn
  • Custom: Checks for common silent switches

Deduplication and Merging

Multiple sources may detect the same application:
internal static void MergeResults(
    ICollection<ApplicationUninstallerEntry> baseEntries,
    ICollection<ApplicationUninstallerEntry> newResults, 
    ListGenerationProgress.ListGenerationCallback progressCallback)
{
    var newToAdd = new List<ApplicationUninstallerEntry>();
    
    foreach (var entry in newResults)
    {
        var matchedEntry = baseEntries
            .Select(x => new { 
                x, 
                score = ApplicationEntryTools.AreEntriesRelated(x, entry) 
            })
            .Where(x => x.score >= 1)
            .OrderByDescending(x => x.score)
            .Select(x => x.x)
            .FirstOrDefault();

        if (matchedEntry != null)
        {
            // Merge information into existing entry
            InfoAdder.CopyMissingInformation(matchedEntry, entry);
            continue;
        }

        // Add as new entry
        newToAdd.Add(entry);
    }
}

Matching Algorithm

Applications are matched based on:
  1. Registry Key Name: Exact match on GUID or key name
  2. Install Location: Same installation directory
  3. Display Name + Publisher: Similar name and same publisher
  4. Bundle Provider Key: Matching MSI product GUID

Caching System

BCU caches detected application information:
if (UninstallToolsGlobalConfig.UninstallerFactoryCache != null)
{
    ApplyCache(registryResults, 
               UninstallToolsGlobalConfig.UninstallerFactoryCache, 
               InfoAdder);
}

// After enrichment, update cache
foreach (var entry in mergedResults)
    UninstallToolsGlobalConfig.UninstallerFactoryCache.TryCacheItem(entry);
Performance: The cache significantly speeds up subsequent scans by storing expensive-to-calculate information like file sizes and certificates.

Bulk Uninstall

Uninstalling detected applications in bulk

Export Lists

Exporting detected application lists

Build docs developers (and LLMs) love