Skip to main content

How to Clear the Windows Installer Cache in Batch Script

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.
Never Blindly Delete from C:\Windows\Installer

Running del /f /q C:\Windows\Installer\* will permanently break the repair and uninstallation capability of virtually every MSI-installed program on the system. This includes Microsoft Office, Visual Studio, SQL Server, and many other critical applications. Only orphaned files, those no longer linked to any installed product, should be removed.

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:

Always Preview Before Deleting

The script first identifies orphans and saves them to a preview file (orphaned_files_preview.txt) WITHOUT deleting anything. The operator can review the list, verify the identified files are indeed not needed, and only then confirm deletion. This two-step approach prevents accidental removal of active installer files that might not be properly registered in the registry.

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
PatchCleaner Moves Instead of Deleting

PatchCleaner's default behavior is to move orphaned files to a backup directory rather than deleting them outright. This provides a recovery option if a file was incorrectly identified as orphaned. After confirming that no application repair/uninstall issues arise (wait at least a week), you can delete the backup directory to reclaim the space.

When to use each method:

ScenarioMethod
One-time audit (how much space?)Method 2 (report only)
Automated cleanup with previewMethod 1 (built-in PowerShell)
Maximum safety / GUI preferredMethod 3 (PatchCleaner)
Fleet-wide automated deploymentMethod 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
Do NOT Take Ownership of the Entire Folder

Running takeown /r on the entire C:\Windows\Installer directory changes the security context of all files, which can prevent Windows Installer from functioning correctly. Only take ownership of specific files that are confirmed orphans and refuse to delete.

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.