A pasta C:\Windows\Installer é uma pasta do Windows que passa despercebida, até o dia você descobre o quanto ela está ocupando (e normalmente isso ocorreu porque está faltando espaço livre no disco). Apesar do nome sugerir que ali ficam apenas instaladores descartáveis, o diretório armazena arquivos para atualização, reparo e desinstalação de aplicativos, por isso que apagar esse conteúdo é uma péssima ideia. A própria Microsoft explica que esses arquivos podem ser necessários no futuro e que a exclusão pode inviabilizar e corromper o uso de programas.
E aí está o problema: A pasta cresce (e bastante), mas o Windows não oferece uma ferramenta para limpar seu conteúdo, mesmo que ao longo do tempo começem a ter arquivos de instaladores e patches que já não são mais necessários. Para muitos a solução é formatar o computador, vamos propor algumas ações corretivas.
Opção 1 – O Adobe Acrobat Reader
Infelizmente não é possível fazer esse post sem referenciar diretamente o programa que (dentre os instalados normalmente em diversos computadores) é o que mais faz download de atualizações (e o que mais deixa usa espaço na pasta Installer). Tanto que em seu próprio site ela informa chaves de registros para evitar que a ferramenta faça atualização. A estrutura geral é (altere em cinza, conforme sua realidade, consulte o seu registro e veja quais as variáveis, caso tenha dúvidas):
- Em HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Adobe\produto\versão\FeatureLockDown\ criar bUpdater (tipo DWORD) com dado 0
- Em HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Policies\Adobe\produto\versão\FeatureLockDown\ criar bUpdater (tipo DWORD) com dado 0
Caso queira controlar (a frequencia) do update, sem bloqueá-lo totalmente, pode ser configurado:
- Em HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Adobe\Adobe ARM\Legacy\produto\codigo\ criar Mode (tipo DWORD) com dado 0
- Em HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Adobe\Adobe ARM\Legacy\produto\codigo\ criar Check (tipo DWORD) com dado 720 (esse número é em decimal, sendo a quantidade em horas. Nesse caso, seriam 30 dias)
Opção 2 – Ferramentas específicas de Limpeza
Uma das ferramentas conhecidas para identificar esses arquivos é o PatchCleaner., que compara os arquivos de instalação existentes com a relação de instaladores e patches que o Windows reconhece, procurando possíveis arquivos órfãos e move esses arquivos.
Opção 3 – Script PowerShell de Limpeza
Seguindo o fluxo proposto pelo PatchCleaner, encontramos um script PowerShell do gakamor com opções interessantes (auditar, mover, deletar). Mas para usar nosso método de Comparando membros de dois arrays no PowerShell (que é muito mais rápido do que fazer “loop” e comparar item a item). O código abaixo precisa ser executado como administrador.
#Requires -RunAsAdministrator
# BASED ON: https://github.com/gakamor/public-scripts/blob/main/Cleanup-WindowsInstallerDirectory.ps1
clear-host
$folderDialog = New-Object -ComObject Shell.Application
$folder = $folderDialog.BrowseForFolder(0, "Choose the folder to save Orphaned Files", 0, 0)
$Destination= $folder.Self.Path
$excludedProducts = @(
'Adobe',
'ExampleProduct'
)
# FUNCTION: GET FILE METADATA
# ============================================================
function Get-FileMetaData {
[CmdletBinding()]
param (
[Parameter(
Position = 0,
ValueFromPipeline
)]
[Object] $File,
[switch] $Signature
)
Process {
foreach ($F in $File) {
$MetaDataObject = [ordered] @{}
if ($F -is [string]) { $FileInformation = Get-ItemProperty -Path $F }
elseif ($F -is [System.IO.DirectoryInfo]) { continue }
elseif ($F -is [System.IO.FileInfo]) { $FileInformation = $F }
else {
Write-Warning "Get-FileMetaData - Only files are supported. Skipping $F."
continue
}
$ShellApplication = New-Object -ComObject Shell.Application
$ShellFolder = $ShellApplication.Namespace($FileInformation.Directory.FullName)
$ShellFile = $ShellFolder.ParseName($FileInformation.Name)
$MetaDataProperties = [ordered] @{}
20..22 | ForEach-Object -Process {
$DataValue = $ShellFolder.GetDetailsOf($null,$_)
$PropertyValue = (Get-Culture).TextInfo.ToTitleCase($DataValue.Trim()).Replace(' ','')
if ($PropertyValue -ne '') { $MetaDataProperties["$_"] = $PropertyValue }
}
foreach ($Key in $MetaDataProperties.Keys) {
$Property = $MetaDataProperties[$Key]
$Value = $ShellFolder.GetDetailsOf($ShellFile,[int] $Key)
if ( $Property -in 'Attributes','Folder','Type','SpaceFree','TotalSize','SpaceUsed' ) { continue }
if ( ($null -ne $Value) -and ($Value -ne '') ) { $MetaDataObject["$Property"] = $Value }
}
# Retrieve digital signature information when requested.
if ($Signature) {
$DigitalSignature = Get-AuthenticodeSignature -FilePath $FileInformation.FullName
$MetaDataObject[
'SignatureCertificateSubject'
] = $DigitalSignature.SignerCertificate.Subject
}
[PSCustomObject] $MetaDataObject
}
}
}
Write-Output "Reading Windows Installer information..."
# CREATE WINDOWS INSTALLER COM OBJECT
# ============================================================
$Installer = New-Object -ComObject WindowsInstaller.Installer
# ADD HELPER METHODS TO WINDOWS INSTALLER COM OBJECT
# ============================================================
$Installer | Add-Member -Name 'InvokeMethod' -MemberType ScriptMethod -Value {
$type = $this.GetType()
$index = $args.Count - 1
$methodargs = $args[1..$index]
$type.invokeMember(
$args[0],
[System.Reflection.BindingFlags]::InvokeMethod,
$null,
$this,
$methodargs
)
}
$Installer | Add-Member -Name 'GetProperty' -MemberType ScriptMethod -Value {
$type = $this.GetType()
$index = $args.Count - 1
$methodargs = $args[1..$index]
$type.invokeMember(
$args[0],
[System.Reflection.BindingFlags]::GetProperty,
$null,
$this,
$methodargs
)
}
# GET INSTALLED MSI PRODUCTS
# ============================================================
$InstallerProducts = $Installer.ProductsEx("","",7)
$InstalledProducts = foreach ($Product in $InstallerProducts) {
try {
[PSCustomObject]@{
ProductCode = $Product.ProductCode()
LocalPackage = $Product.InstallProperty("LocalPackage")
VersionString = $Product.InstallProperty("VersionString")
ProductName = $Product.InstallProperty("ProductName")
}
}
catch {
# Some products may have missing or inaccessible properties.
# Ignore those products.
}
}
# GET INSTALLED MSP PATCHES
# ============================================================
$products = $Installer.GetProperty('Products')
$InstalledPatches = foreach ($productCode in $products) {
$patches = $Installer.GetProperty('Patches',$productCode)
if ($patches) {
foreach ($patchCode in $patches) {
$location = $Installer.GetProperty('PatchInfo',$patchCode,'LocalPackage')
if ($location) {
$productName = $Installer.GetProperty('ProductInfo',$productCode,'ProductName')
[PSCustomObject]@{
ProductCode = $productCode
PatchCode = $patchCode
LocalPackage = $location
ProductName = $productName
}
}
}
}
}
Write-Output "Building index..."
# CREATE HASHSET FOR INSTALLED MSI PACKAGES
# ============================================================
$InstalledMsiPaths = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
foreach ($product in $InstalledProducts) {
if ( -not [string]::IsNullOrWhiteSpace($product.LocalPackage) ) {
try {
$path = [System.IO.Path]::GetFullPath($product.LocalPackage)
[void]$InstalledMsiPaths.Add($path)
}
catch {
# Ignore invalid paths.
}
}
}
# CREATE HASHSET FOR INSTALLED MSP PATCHES
# ============================================================
$InstalledMspPaths = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
foreach ($patch in $InstalledPatches) {
if ( -not [string]::IsNullOrWhiteSpace($patch.LocalPackage) ) {
try {
$path = [System.IO.Path]::GetFullPath($patch.LocalPackage)
[void]$InstalledMspPaths.Add($path)
}
catch {
# Ignore invalid paths.
}
}
}
Write-Output "Scanning..."
# GET FILES
# ============================================================
$InstallationFiles = @(Get-ChildItem -Path "$env:windir\Installer" -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*.msi" -or $_.Name -like "*.msp" })
# COMPARE FILES AGAINST INSTALLED PACKAGES
# ============================================================
$OrphanedFiles = @($InstallationFiles | Where-Object { -not ($InstalledMsiPaths.Contains($_.FullName) -or $InstalledMspPaths.Contains($_.FullName)) })
# PROCESS ORPHANED FILES
# ============================================================
$MovedFiles = New-Object 'System.Collections.Generic.List[PSCustomObject]'
foreach ($file in $OrphanedFiles) {
$fullname = $file.FullName
$fileSize = $file.Length
# Get metadata only for orphaned files
# --------------------------------------------------------
try {
$metaData = $file | Get-FileMetaData -Signature | Select-Object Authors,Title,Subject,SignatureCertificateSubject
}
catch {
Write-Warning "Unable to retrieve metadata for: $fullname"
$metaData = [PSCustomObject]@{}
}
# Move file
# --------------------------------------------------------
try {
Write-Output "Moving: $fullname"
Move-Item -LiteralPath $fullname -Destination $Destination -Force -ErrorAction Stop
$MovedFiles.Add(
[PSCustomObject]@{
FileName = $fullname
Size = $fileSize
Destination = $Destination
}
)
}
catch {
Write-Warning "Failed to move '$fullname': $($_.Exception.Message)"
}
}
# REPORT
# ============================================================
if ($MovedFiles.Count -gt 0) {
Write-Output ""
Write-Output "Moved Files:"
Write-Output "------------"
$MovedFiles | Format-Table FileName,Size,Destination -AutoSize
}
Write-Host ""
Write-Host "SUMMARY"
Write-Host "============================================================"
Write-Host "Application files scanned : $($InstallationFiles.Count)"
Write-Host "Files moved : $($MovedFiles.Count)"
Write-Host ""
Write-Host "Reboot the computer and check if everything is OK. If so, clean the application installation files from the $destination folder."
Fontes/Referências
NVLAN – Comparando membros de dois arrays no PowerShell
https://adobe.com/devnet-docs/acrobatetk/tools/PrefRef/Windows/Updater-Win.html
https://github.com/gakamor/public-scripts
https://homedev.com.au/free/patchcleaner
https://learn.microsoft.com/pt-br/troubleshoot/sql/database-engine/install/windows/restore-missing-windows-installer-cache-files
https://learn.microsoft.com/pt-br/troubleshoot/windows-client/application-management/missing-windows-installer-cache
Mais Informações
Esperamos ter te ajudado e estaremos sempre a disposição para mais informações.
Se você tem interesse em algum assunto específico, tem alguma dúvida, precisa de ajuda, ou quer sugerir um post, entre em contato conosco pelo e-mail equipe@nvlan.com.br.
