Skip to content

How to Clear the Windows Installer Cache in Batch Script

Learn how to safely identify and remove orphaned Windows Installer cache files (.msi, .msp) from C:\Windows\Installer using a batch script with registry cross-referencing to prevent breaking software repairs or uninstalls.

The Windows Installer Cache (C:\Windows\Installer) stores .msi installer files and .msp patch files for every application installed via Windows Installer. These files are needed for repairs, feature changes, and uninstallation. Over time, this folder can grow to 10–30 GB or more, especially on developer workstations with Visual Studio, Office, and frequent SDK updates. However, some files become "orphaned" when their associated software is uninstalled through other means. Only orphaned files can be safely removed; deleting active files breaks the ability to repair or uninstall the associated software.

This guide explains how to safely identify and remove orphaned installer cache files.

Understanding What's at Stake

C:\Windows\Installer\ contains:
  ┌─────────────────────────────────────────────────────┐
  │  ACTIVE files (linked to installed software)        │
  │    → Needed for Repair, Modify, Uninstall           │
  │    → MUST NOT be deleted                            │
  │                                                     │
  │  ORPHANED files (software already uninstalled)      │
  │    → No longer referenced by any installed product  │
  │    → Safe to delete - reclaims disk space           │
  └─────────────────────────────────────────────────────┘

Deleting an ACTIVE file → that software can no longer be
repaired, modified, or cleanly uninstalled via Control Panel.

How files become orphaned:

  • Software was uninstalled by a cleanup tool that didn't remove the cached installer.
  • A product was installed, then a newer version replaced it without cleaning up the old cache entry.
  • An installation was interrupted or failed, leaving partial cache files.
  • A product was removed by manually deleting its folder rather than using the uninstaller.

Method 1: Safe Orphaned File Identification and Removal

This method cross-references the files in the Installer folder against the Windows Installer registry database. Files that exist on disk but are NOT referenced by any registered product are identified as orphans.

@echo off
setlocal EnableDelayedExpansion

echo ============================================================
echo   Windows Installer Cache Cleanup (Safe Mode)
echo ============================================================
echo.
echo Computer: %COMPUTERNAME%
echo User:     %USERNAME%
echo Date:     %date% %time%
echo.
echo ============================================================
echo.

:: ============================================================
:: Check Administrator Privileges
:: ============================================================

echo [PREREQ] Checking administrator privileges...
echo.

net session >nul 2>&1

if !errorlevel! neq 0 (
    echo ============================================================
    echo   ERROR: Administrator Privileges Required
    echo ============================================================
    echo.
    echo [ERROR] Administrator privileges required. >&2
    echo.
    echo The Installer folder is a protected system directory.
    echo.
    echo Right-click and select "Run as administrator"
    pause
    exit /b 1
)

echo [OK] Running with administrator privileges
echo.

:: ============================================================
:: Warning
:: ============================================================

echo ============================================================
echo   WARNING
echo ============================================================
echo.
echo This script will:
echo   - Analyze C:\Windows\Installer
echo   - Identify orphaned MSI/MSP files
echo   - Safely remove files not referenced by installed programs
echo.
echo Impact:
echo   - Frees disk space
echo   - Does NOT affect installed programs
echo   - Only removes unreferenced cache files
echo.

set /p "Continue=Continue? (Y/N): "

if /i not "!Continue!"=="Y" (
    echo.
    echo [CANCELLED] Operation cancelled by user
    exit /b 0
)

echo.

:: ============================================================
:: Analyze Installer Folder
:: ============================================================

echo ============================================================
echo   Step 1/3: Analyzing Installer Cache
echo ============================================================
echo.

echo [INFO] Analyzing C:\Windows\Installer...
echo.

:: Create PowerShell script for analysis
set "PSAnalyze=%TEMP%\installer_analyze_%RANDOM%.ps1"

(
echo $installerPath = 'C:\Windows\Installer'
echo.
echo if (-not (Test-Path $installerPath^)^) {
echo     Write-Output 'NOT_FOUND~0~0'
echo     exit 1
echo }
echo.
echo try {
echo     $allFiles = Get-ChildItem $installerPath -Include *.msi,*.msp -Recurse -Force -ErrorAction SilentlyContinue
echo     $fileCount = @($allFiles^).Count
echo     $totalBytes = ($allFiles ^| Measure-Object Length -Sum^).Sum
echo     $totalMB = [math]::Round($totalBytes / 1MB, 1^)
echo     
echo     Write-Output "$fileCount~$totalMB"
echo     exit 0
echo } catch {
echo     Write-Output "ERROR~0~0"
echo     exit 1
echo }
) > "%PSAnalyze%"

set "TotalFiles=0"
set "TotalSizeMB=0"

for /f "tokens=1,2 delims=~" %%a in (
    'powershell -NoProfile -ExecutionPolicy Bypass -File "%PSAnalyze%" 2^>nul'
) do (
    set "TotalFiles=%%a"
    set "TotalSizeMB=%%b"
)

del "%PSAnalyze%" >nul 2>&1

if "!TotalFiles!"=="NOT_FOUND" (
    echo [ERROR] C:\Windows\Installer folder not found. >&2
    pause
    exit /b 1
)

if "!TotalFiles!"=="ERROR" (
    echo [ERROR] Failed to analyze Installer folder. >&2
    pause
    exit /b 1
)

echo   Total MSI/MSP files:  !TotalFiles!
echo   Total cache size:     !TotalSizeMB! MB
echo.

if !TotalFiles! equ 0 (
    echo [INFO] Installer cache is empty
    pause
    exit /b 0
)

:: ============================================================
:: Scan for Orphaned Files
:: ============================================================

echo ============================================================
echo   Step 2/3: Scanning for Orphaned Files
echo ============================================================
echo.

echo [INFO] Cross-referencing installer cache against registry...
echo [INFO] This may take 1-5 minutes depending on system configuration
echo.

set "PreviewFile=%~dp0orphaned_files_preview.txt"
set "PSOrphan=%TEMP%\installer_orphan_%RANDOM%.ps1"

:: Create PowerShell script for orphan detection
(
echo $installerPath = 'C:\Windows\Installer'
echo $previewFile = '%PreviewFile%'
echo.
echo # Initialize
echo $registeredPaths = @(^)
echo.
echo # Collect registered package paths from MSI registry
echo Write-Host 'Scanning registry for registered installers...'
echo.
echo # HKLM per-machine installations
echo $regBase = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products'
echo if (Test-Path $regBase^) {
echo     Get-ChildItem $regBase -ErrorAction SilentlyContinue ^| ForEach-Object {
echo         $_ ^| Get-ChildItem -ErrorAction SilentlyContinue ^| ForEach-Object {
echo             try {
echo                 $props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
echo                 if ($props.LocalPackage^) {
echo                     $registeredPaths += $props.LocalPackage.ToLower(^)
echo                 }
echo             } catch {}
echo         }
echo     }
echo }
echo.
echo # HKCU per-user installations
echo $userBase = 'HKCU:\SOFTWARE\Microsoft\Installer\Products'
echo if (Test-Path $userBase^) {
echo     Get-ChildItem $userBase -ErrorAction SilentlyContinue ^| ForEach-Object {
echo         try {
echo             $props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
echo             if ($props.LocalPackage^) {
echo                 $registeredPaths += $props.LocalPackage.ToLower(^)
echo             }
echo         } catch {}
echo     }
echo }
echo.
echo Write-Host "Found $($registeredPaths.Count^) registered installer packages"
echo Write-Host ''
echo.
echo # Get all installer cache files
echo Write-Host 'Analyzing cache files...'
echo $allFiles = Get-ChildItem $installerPath -Include *.msi,*.msp -Recurse -Force -ErrorAction SilentlyContinue
echo.
echo # Find orphans
echo $orphans = @(^)
echo foreach ($file in $allFiles^) {
echo     if ($registeredPaths -notcontains $file.FullName.ToLower(^^)^) {
echo         $orphans += $file
echo     }
echo }
echo.
echo # Calculate stats
echo $orphanCount = $orphans.Count
echo $orphanBytes = ($orphans ^| Measure-Object Length -Sum^).Sum
echo $orphanMB = [math]::Round($orphanBytes / 1MB, 1^)
echo.
echo Write-Host ''
echo Write-Host "Orphaned files found:  $orphanCount"
echo Write-Host "Reclaimable space:     $orphanMB MB"
echo Write-Host ''
echo.
echo if ($orphanCount -eq 0^) {
echo     Write-Host '[OK] No orphaned files found. The installer cache is clean.' -ForegroundColor Green
echo     exit 0
echo }
echo.
echo # Save preview list
echo if (Test-Path $previewFile^) { Remove-Item $previewFile -Force }
echo.
echo Write-Host 'Orphaned files:'
echo foreach ($file in $orphans^) {
echo     $sizeMB = [math]::Round($file.Length / 1MB, 2^)
echo     Write-Host "  [ORPHAN] $($file.Name^) ($sizeMB MB^)"
echo     "$($file.FullName^)`t$sizeMB MB" ^| Out-File -Append -FilePath $previewFile -Encoding UTF8
echo }
echo.
echo Write-Host ''
echo Write-Host "Preview saved to: $previewFile" -ForegroundColor Cyan
echo.
echo # Output count for batch script
echo Write-Output "$orphanCount~$orphanMB"
echo exit 0
) > "%PSOrphan%"

:: Execute orphan scan
set "OrphanCount=0"
set "OrphanSizeMB=0"

powershell -NoProfile -ExecutionPolicy Bypass -File "%PSOrphan%"

set "ScanResult=!errorlevel!"

:: Parse output
if exist "%TEMP%\orphan_result.txt" (
    for /f "tokens=1,2 delims=~" %%a in (%TEMP%\orphan_result.txt) do (
        set "OrphanCount=%%a"
        set "OrphanSizeMB=%%b"
    )
    del "%TEMP%\orphan_result.txt" >nul 2>&1
)

del "%PSOrphan%" >nul 2>&1

echo.

if !ScanResult! equ 0 (
    if !OrphanCount! equ 0 (
        echo [OK] No orphaned files found. Cache is clean.
        pause
        exit /b 0
    )
)

:: ============================================================
:: Confirm Deletion
:: ============================================================

echo ============================================================
echo   Step 3/3: Confirm Deletion
echo ============================================================
echo.

echo [WARNING] Found orphaned files to remove
echo.
echo   Orphaned files:    !OrphanCount!
echo   Reclaimable space: !OrphanSizeMB! MB
echo.
echo Preview file: !PreviewFile!
echo.
echo Review the preview file before proceeding.
echo Type YES (in capitals) to confirm deletion.
echo.

set /p "Confirm=Delete orphaned files? (YES/no): "

if not "!Confirm!"=="YES" (
    echo.
    echo [CANCELLED] No files were deleted
    echo.
    echo Review the preview file: !PreviewFile!
    pause
    exit /b 0
)

:: ============================================================
:: Perform Deletion
:: ============================================================

echo.
echo [ACTION] Removing orphaned files...
echo.

set "PSDelete=%TEMP%\installer_delete_%RANDOM%.ps1"

(
echo $installerPath = 'C:\Windows\Installer'
echo.
echo # Re-scan for registered paths
echo $registeredPaths = @(^)
echo $regBase = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products'
echo if (Test-Path $regBase^) {
echo     Get-ChildItem $regBase -EA SilentlyContinue ^| ForEach-Object {
echo         $_ ^| Get-ChildItem -EA SilentlyContinue ^| ForEach-Object {
echo             try {
echo                 $props = Get-ItemProperty $_.PSPath -EA SilentlyContinue
echo                 if ($props.LocalPackage^) { $registeredPaths += $props.LocalPackage.ToLower(^) }
echo             } catch {}
echo         }
echo     }
echo }
echo.
echo $allFiles = Get-ChildItem $installerPath -Include *.msi,*.msp -Recurse -Force -EA SilentlyContinue
echo.
echo $deleted = 0
echo $failed = 0
echo $savedBytes = 0
echo.
echo foreach ($file in $allFiles^) {
echo     if ($registeredPaths -notcontains $file.FullName.ToLower(^^)^) {
echo         try {
echo             $size = $file.Length
echo             Remove-Item $file.FullName -Force -EA Stop
echo             $deleted++
echo             $savedBytes += $size
echo             Write-Host "  [DEL] $($file.Name^)"
echo         } catch {
echo             $failed++
echo             Write-Host "  [FAIL] $($file.Name^): $($_.Exception.Message^)" -ForegroundColor Red
echo         }
echo     }
echo }
echo.
echo $savedMB = [math]::Round($savedBytes / 1MB, 1^)
echo.
echo Write-Host ''
echo Write-Host "Deleted: $deleted  Failed: $failed  Space freed: $savedMB MB" -ForegroundColor Green
) > "%PSDelete%"

powershell -NoProfile -ExecutionPolicy Bypass -File "%PSDelete%"

del "%PSDelete%" >nul 2>&1

echo.
echo ============================================================
echo   Cleanup Complete
echo ============================================================
echo.

:: Log the operation
set "LogFile=%~dp0installer_cleanup.log"

for /f "delims=" %%t in (
    'powershell -NoProfile -Command "Get-Date -Format 'yyyy-MM-dd HH:mm:ss'"'
) do set "Timestamp=%%t"

(
    echo [!Timestamp!] INSTALLER CACHE CLEANUP
    echo   Computer:       %COMPUTERNAME%
    echo   User:           %USERNAME%
    echo   Orphans found:  !OrphanCount!
    echo   Space freed:    !OrphanSizeMB! MB
) >> "!LogFile!"

echo [LOG] Operation logged to: !LogFile!
echo.

pause
endlocal
exit /b 0

How the orphan detection works:

  1. Registry scan: Reads all LocalPackage values from the Windows Installer registry database (HKLM:\...\UserData\S-1-5-18\Products). These are the file paths that Windows Installer considers "active."
  2. Filesystem scan: Lists all .msi and .msp files in C:\Windows\Installer.
  3. Cross-reference: Any file on disk that is NOT in the registry's LocalPackage list is an orphan, its associated product has been uninstalled, and the cache file is no longer needed.

Why the preview-then-delete approach:

Method 2: Report Only (No Deletion)

For auditing, see how much space could be reclaimed without making any changes.

@echo off
setlocal EnableDelayedExpansion

echo ============================================================
echo   Installer Cache Report (Audit Mode - No Changes Made)
echo ============================================================
echo.
echo Date: %date% %time%
echo Computer: %COMPUTERNAME%
echo.

:: Quick admin check
net session >nul 2>&1

if !errorlevel! neq 0 (
    echo [ERROR] Administrator privileges required >&2
    pause
    exit /b 1
)

echo [ANALYZING] Scanning installer cache...
echo.

:: Create compact PowerShell analysis script
set "PS=$p='C:\Windows\Installer';if(!(Test-Path $p)){exit 1};"
set "PS=!PS!$a=gci $p -Include *.msi,*.msp -Recurse -Force -EA 0;"
set "PS=!PS!$r=@();gci 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products' -Recurse -EA 0|gp -EA 0|?{$_.LocalPackage}|%%{$r+=$_.LocalPackage.ToLower()};"
set "PS=!PS!$act=$a|?{$r -contains $_.FullName.ToLower()};$orp=$a|?{$r -notcontains $_.FullName.ToLower()};"
set "PS=!PS!$t=[math]::Round(($a|measure Length -Sum).Sum/1MB,1);$am=[math]::Round(($act|measure Length -Sum).Sum/1MB,1);$om=[math]::Round(($orp|measure Length -Sum).Sum/1MB,1);"
set "PS=!PS!Write-Host '============================================================';Write-Host '  Cache Statistics' -ForegroundColor Cyan;Write-Host '============================================================';Write-Host '';"
set "PS=!PS!Write-Host \"Total Files:      $($a.Count) files ($t MB)\";Write-Host '';"
set "PS=!PS!Write-Host \"Active Files:     $($act.Count) files ($am MB)\" -ForegroundColor Green;Write-Host \"                  (Required by installed programs - DO NOT DELETE)\" -ForegroundColor Gray;Write-Host '';"
set "PS=!PS!if($orp.Count -gt 0){Write-Host \"Orphaned Files:   $($orp.Count) files ($om MB)\" -ForegroundColor Yellow;Write-Host \"                  (Safe to remove - not referenced by any program)\" -ForegroundColor Gray}else{Write-Host 'Orphaned Files:   0 files (0 MB)' -ForegroundColor Green;Write-Host '                  (Cache is clean)' -ForegroundColor Gray};Write-Host '';Write-Host '============================================================';Write-Host '  Recommendation' -ForegroundColor Cyan;Write-Host '============================================================';Write-Host '';"
set "PS=!PS!if($om -gt 500){Write-Host \"  [SIGNIFICANT] $om MB can be freed\" -ForegroundColor Yellow;Write-Host '';Write-Host '  Action: Run cleanup script to reclaim space' -ForegroundColor White}elseif($om -gt 100){Write-Host \"  [MODERATE] $om MB can be freed\" -ForegroundColor Cyan;Write-Host '';Write-Host '  Action: Consider cleanup if disk space is low' -ForegroundColor White}elseif($orp.Count -eq 0){Write-Host '  [EXCELLENT] Cache is clean' -ForegroundColor Green;Write-Host '';Write-Host '  Action: No cleanup needed' -ForegroundColor White}else{Write-Host \"  [MINIMAL] Only $om MB available\" -ForegroundColor Cyan;Write-Host '';Write-Host '  Action: Cleanup optional' -ForegroundColor White};"
set "PS=!PS!Write-Host '';Write-Host '============================================================'"

powershell -NoProfile -Command "!PS!"

echo.
echo [INFO] This was a read-only analysis - no files were modified
echo.

pause
exit /b 0

Method 3: Using PatchCleaner (Third-Party Approach)

For environments where the PowerShell registry-scanning approach feels too risky, the free tool PatchCleaner by HomeDev provides a GUI and command-line interface specifically designed for this task. It uses the same registry cross-referencing technique but has been extensively tested against edge cases.

@echo off
setlocal

:: Check if PatchCleaner is installed
if not exist "C:\Program Files\PatchCleaner\PatchCleaner.exe" (
    echo [INFO] PatchCleaner is not installed.
    echo [INFO] Download from: https://www.homedev.com.au/free/patchcleaner
    echo.
    echo [INFO] Alternatively, use Method 1 (built-in PowerShell approach^).
    endlocal
    exit /b 1
)

echo [ACTION] Running PatchCleaner analysis...

:: PatchCleaner can move orphaned files instead of deleting
:: (safer, you can restore if something was incorrectly identified)
"C:\Program Files\PatchCleaner\PatchCleaner.exe" /a

endlocal
exit /b 0

When to use each method:

Scenario Method
One-time audit (how much space?) Method 2 (report only)
Automated cleanup with preview Method 1 (built-in PowerShell)
Maximum safety / GUI preferred Method 3 (PatchCleaner)
Fleet-wide automated deployment Method 1 (no third-party dependency)

How to Avoid Common Errors

Wrong Way: Deleting All Files in the Installer Folder

:: CATASTROPHIC: breaks repair/uninstall for ALL MSI-installed software
del /f /q /s C:\Windows\Installer\*

This deletes both active and orphaned files. Programs like Office, Visual Studio, SQL Server, and hundreds of other MSI-based applications will no longer be repairable or cleanly uninstallable.

Correct Way: Only delete files confirmed as orphaned by cross-referencing the registry (Methods 1 and 2).

Wrong Way: Deleting by Age

:: WRONG - age does not determine whether a file is active or orphaned
forfiles /P C:\Windows\Installer /M *.msi /D -365 /C "cmd /c del @file"

An installer file from 3 years ago may be actively needed by software still installed on the system (e.g., Office 2019 installed in 2019 still needs its cached installer in 2024). Age is not a valid criterion.

Correct Way: Use registry cross-referencing (Method 1). The only safe criterion is whether the file is referenced in the Windows Installer database.

Wrong Way: Deleting Based on File Size

:: WRONG - large files are often the most important (Office, Visual Studio)
:: Deleting them breaks the most critical software

The largest files in the Installer cache are typically the most important, they belong to large applications like Office (hundreds of MB) and Visual Studio (GB). Deleting them based on size is backwards.

Correct Way: Size is irrelevant. Use registry cross-referencing.

Problem: Access Denied on Some Files

Even with administrator privileges, some files in the Installer folder may have restrictive ACLs set by the SYSTEM account.

Solution: PowerShell's Remove-Item -Force handles most cases. For persistent access issues:

:: Take ownership and grant access (use only as a last resort)
takeown /f "C:\Windows\Installer\problematic_file.msi" >nul 2>&1
icacls "C:\Windows\Installer\problematic_file.msi" /grant Administrators:F >nul 2>&1

Problem: False Positives in Orphan Detection

The registry cross-referencing method may occasionally misidentify an active file as orphaned if:

  • The product was installed per-user (different registry location)
  • The product uses a non-standard registration path
  • The registry entry is stored under a different user SID

Solution: Method 1 checks both HKLM (machine-wide) and HKCU (per-user) registration. The preview step allows manual verification before deletion. When in doubt, do NOT delete the file.

Best Practices and Rules

1. Always Preview Before Deleting

Method 1's two-step approach (preview → confirm → delete) prevents accidental removal. Review the orphaned file list before confirming deletion.

2. Never Delete by Age, Size, or Name Pattern

The ONLY safe criterion for deletion is registry cross-referencing. An installer file is active if and only if it appears as a LocalPackage value in the Windows Installer registry database.

3. Keep a Record of What Was Deleted

Save the list of deleted files (Method 1's preview file) in case you need to identify which file was removed if an application later reports installer cache issues.

4. Run the Report First

Before any cleanup, run Method 2 to understand the scale: how large is the cache, how much is active, and how much is reclaimable. If the orphaned files total only 50 MB, the risk of cleanup may not be worth the savings.

5. Test Application Repair After Cleanup

After removing orphaned files, test that critical applications can still be repaired:

Control Panel → Programs → right-click an application → Repair

If "Repair" succeeds, the cleanup was safe. If it fails, the removed file may have been misidentified, restore from backup if available.

6. Consider PatchCleaner for High-Risk Environments

Method 3's PatchCleaner moves files instead of deleting them, providing a built-in recovery option. For production servers or machines with business-critical software, this safer approach is worth the third-party dependency.

Conclusion

Clearing the Windows Installer cache can reclaim significant disk space, often 2–20 GB on developer workstations, but it must be done with surgical precision. Only files confirmed as orphaned through registry cross-referencing should be removed. The preview-then-delete workflow, combined with logging and post-cleanup testing, ensures that disk space is reclaimed without breaking the repair and uninstall capabilities of installed software. Never delete by age, size, or blanket wildcard, the registry is the only source of truth for which installer files are still needed.

Navigation