您好,登錄后才能下訂單哦!
今天看到一臺(tái)windows 7 的計(jì)算機(jī),C盤(pán)分了50GB,結(jié)果installer 目錄有47GB,幸虧我對(duì)該目錄啟用過(guò)壓縮,壓縮后實(shí)際占用32GB的樣子,但也足夠大了,已經(jīng)導(dǎo)致C盤(pán)滿了,我刪了下TEMP目錄,清了c:\users\下一些很久沒(méi)用的用戶配置文件,救回2GB出來(lái)。但這個(gè)Installer目錄為什么會(huì)占用這么多空間?什么樣的靠譜方法可以縮減該尺寸。
列舉下一些之前嘗試的方法,這些方法安全,但是收效甚微。
windows 清理程序,即使使用了隱藏的高級(jí)功能,但是清理掉的空間不是很多。cmd.exe /c Cleanmgr /sageset:65535 /sagerun:65535
sageset會(huì)彈出窗口讓選擇清理的項(xiàng)目,選擇后會(huì)保留在注冊(cè)表中,后面sagerun就會(huì)使用這個(gè)注冊(cè)表里存儲(chǔ)的選項(xiàng)執(zhí)行靜默的清理。
OK,一般這種狀況下會(huì)找些流行的專用軟件來(lái)干這個(gè)事,畢竟術(shù)業(yè)有專攻,第一次使用了WICleanup。
WICleanup列出了冗余文件,而且我的文件清單上有多個(gè)文件大小都是一個(gè)尺寸,我說(shuō)這個(gè)軟件難道是可以算出重復(fù)文件的功能,然后把重復(fù)文件刪掉?!,然后鑒于桌面說(shuō)用過(guò),我覺(jué)得應(yīng)該至少問(wèn)題不大吧,看了下目錄下有個(gè)命令行的版本帶-s 可以靜默清理,我試了一下,發(fā)現(xiàn)清掉30GB多的空間,到installer 目錄一看,我就知道壞了,里面的MSP、MSI文件全干掉了。
這個(gè)軟件我后面看開(kāi)發(fā)時(shí)間也是超級(jí)古老了,最近還有人發(fā)Blog介紹這個(gè)工具,而且評(píng)論區(qū)還有好多人反饋清理了好多.............沒(méi)發(fā)現(xiàn)副作用很大么?。
pjl6523853
愛(ài)武俠的程序員2018-04-09 20:37:08#4樓
太感謝博主了!幫我清理了30G!請(qǐng)問(wèn)博主可以轉(zhuǎn)載嘛
Maxwell_STU
Maxwell_STU2018-03-11 00:45:01#3樓
感謝博主分享,突然就清理出來(lái)10G以上的空間,感覺(jué)清爽了超級(jí)多,壓力一下子就小了。
KEVIN_LI_MY
KEVIN_LI_MY2017-11-29 08:59:26#2樓
我清理出了3g,也不少了。
我稍后測(cè)試了下用Wicleanup清理過(guò)的計(jì)算機(jī),控制面板中部分程序的卸載、修復(fù),windows 更新均有問(wèn)題,主要問(wèn)題是彈對(duì)話框提示找文件。
一篇看起很高深的文章指引,又是解構(gòu)msi文件,又是C++清注冊(cè)表,主要是一個(gè)操作,就是刪除HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\***********\Patches
注冊(cè)表,然后一股腦刪installer目錄的下的MSP、MSI文件。
有了上面教訓(xùn),我想我得了解下此類軟件的原理,然后確定可行后才能使用。首先我參考了微軟的員工的解決方法,算是廖勝于無(wú)吧,大概意思就是官方僅支持通過(guò)卸載軟件的方式來(lái)清理installer目錄,這個(gè)Blog在評(píng)論區(qū)討論了很多次,但似乎沒(méi)有什么好的結(jié)論,也沒(méi)有提供太多有價(jià)值的信息。
在我訪問(wèn)類似superuser 上的討論時(shí),我發(fā)現(xiàn)了這個(gè)軟件patchCleaner,為啥說(shuō)可能還靠譜,因?yàn)橄旅妫?/p>
總結(jié)了上面的一些信息,我目前有下列問(wèn)題,需要讓實(shí)際的數(shù)據(jù)說(shuō)話:
我的計(jì)劃:
自己寫(xiě)了powershell 腳本,按照patchCleaner的思路,自己過(guò)濾出孤立的安裝文件,這部分孤立文件我后來(lái)只過(guò)濾出MSI、MSP后綴的文件(這部分文件占用最大)。其他后綴的Installer目錄下的文件,我們不去動(dòng)它(因?yàn)榭赡鼙灰茫热鏘CON文件或者EXE等文件)。MSI,MSP文件當(dāng)中會(huì)有一些除了發(fā)布者為微軟的安裝文件,比如Adobe的文件用get-msisummaryinfo 獲取不到信息,我們也過(guò)濾掉(在PatchCleaner中也默認(rèn)過(guò)濾掉了adobe的安裝文件),過(guò)濾后的孤立安裝文件大概如下圖所示。
下面截圖中時(shí)我的win10 的installer目錄的分析情況。使用之前,用過(guò)windows 清理程序的高級(jí)功能清過(guò)。即使使用清理程序清理過(guò),我們也可以看見(jiàn)孤立的文件還有大概4GB,我手動(dòng)測(cè)試了兩個(gè)安裝程序在清理后的工作情況,一個(gè)是AMD的顯卡軟件的,一個(gè)是微軟的c++ 2012 redistribution ,我先把文件從installer 目錄剪切走,然后執(zhí)行卸載或者修復(fù)功能,都沒(méi)有報(bào)錯(cuò)或者彈框要文件。
當(dāng)上面數(shù)據(jù)很明了清晰時(shí),我們是否可以寫(xiě)出自己的工具來(lái)..大致臆想了自己工具的功能和執(zhí)行步驟
列下PatchCleaner存在的不足的地方:
1.沒(méi)有辦法導(dǎo)出列表。
2.需要.net framwork4,不便于攜帶。
3.似乎沒(méi)有辦法可以對(duì)篩選后的孤立的文件再做選擇性操作。
由于windows 上有PSMSI 這個(gè)powershell 模組,所以最開(kāi)始省去我大部分代碼,把主要精力放在測(cè)試上(反復(fù)考慮后,還是自己寫(xiě)powershell 調(diào)用Installer Com 接口的函數(shù)用于獲取信息,雖然比較困難,全程要用反射功能來(lái)操作Installer COM,而且讀取MSP文件額度問(wèn)題已經(jīng)解決,讀MSP時(shí),數(shù)據(jù)庫(kù)的Openmode需要指定其他值,這樣可以不依賴外部模組)。
$Installer = New-Object -ComObject WindowsInstaller.Installer
$Type = $Installer.GetType()
function Get-MsiProducts {
$Products = $Type.InvokeMember('Products', "GetProperty", $null, $Installer, $null)
foreach ($Product In $Products) {
$hash = @{}
$hash.ProductCode = $Product
$Attributes = @('Language', 'ProductName', 'PackageCode', 'Transforms', 'AssignmentType', 'PackageName', 'InstalledProductName', 'VersionString', 'RegCompany', 'RegOwner', 'ProductID', 'ProductIcon', 'InstallLocation', 'InstallSource', 'InstallDate', 'Publisher', 'LocalPackage', 'HelpLink', 'HelpTelephone', 'URLInfoAbout', 'URLUpdateInfo')
foreach ($Attribute In $Attributes) {
$hash."$($Attribute)" = $null
}
foreach ($Attribute In $Attributes) {
try {
$hash."$($Attribute)" = $Type.InvokeMember('ProductInfo',"GetProperty", $null, $Installer, @($Product, $Attribute))
} catch [System.Exception] {
}
}
if($hash."LocalPackage"){
if(test-path $hash."LocalPackage"){
$hash.size=$(get-item $hash."LocalPackage").Length
}
}
New-Object -TypeName PSObject -Property $hash
}
}
function Get-MsiPatch {
[cmdletbinding()]
param(
$product
)
$Patches = $Type.InvokeMember('Patches',"GetProperty", $null, $Installer, @($product))
foreach ($Patch In $Patches) {
$hash = @{}
$hash.ProductCode = $Product
$hash.PatchCode=$Patch
$Attributes = @('LocalPackage')
foreach ($Attribute In $Attributes) {
$hash."$($Attribute)" = $null
}
foreach ($Attribute In $Attributes) {
try {
$hash."$($Attribute)" = $Type.InvokeMember('PatchInfo', 'GetProperty', $null, $Installer, @($Patch, $Attribute))
} catch [System.Exception] {
#$error[0]|format-list –force
}
}
if($hash."LocalPackage"){
if(test-path $hash."LocalPackage"){
$hash.size=$(get-item $hash."LocalPackage").Length
}
}
New-Object -TypeName PSObject -Property $hash
}
}
function Get-MSIFileInfo {
[cmdletbinding()]
param
(
[Parameter(Mandatory = $true)]$Path
)
try {
if(test-path $path){
$path=get-item $path
$extension=$path.Extension.ToLower()
$DBOPENMODE=0
$TABLENAME='Property'
if($extension -eq '.msp'){
$DBOPENMODE=32
$TABLENAME="MsiPatchMetadata"
}
$msiProps = @{}
$Database = $Type.InvokeMember("OpenDatabase", "InvokeMethod", $Null, $Installer, @($Path.FullName, $DBOPENMODE))
$Query = "SELECT Property,Value FROM $TABLENAME"
$View = $Database.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $Database, ($Query))
$View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)|Out-Null
$record=$view.gettype().invokemember("Fetch","InvokeMethod",$null,$view,$null)
# Loop thru the table
while($record -ne $null) {
$propName=$null
$propValue=$null
$propName=$record.gettype().invokeMember("StringData","GetProperty",$null,$record,1)
$propValue= $record.gettype().invokeMember("StringData","GetProperty",$null,$record,2)
$msiProps[$propName] =$propValue
$record=$view.gettype().invokemember("Fetch","InvokeMethod",$null,$view,$null)
}
$view.gettype().invokemember("Close","InvokeMethod",$null,$view,$null)|Out-Null
# Compose a unified object to express the MSI and MSP information
# MSP 'DisplayName','ManufacturerName','Description', 'MoreInfoURL','TargetProductName'
# MSI 'ProductName','Manufacturer','ProductVersion','ProductCode','UpgradeCode'
if($extension -eq '.msi'){
New-Object -TypeName PSObject -Property @{
'DisplayName'=$msiProps['ProductName']
'Manufacturer'=$msiProps['Manufacturer']
'Version'=$msiProps['ProductVersion']
'PackageCode'=$msiProps['ProductCode']
'Description'=$msiProps['Description']
'TargetProductName'=$msiProps['TargetProductName']
'MoreInfoURL'=$msiProps['MoreInfoURL']
'Size'=$path.Length
'Path'=$path.FullName
'CreationTime'=$path.CreationTime
}
}elseif($extension -eq ".msp"){
New-Object -TypeName PSObject -Property @{
'DisplayName'=$msiProps['DisplayName']
'Manufacturer'=$msiProps['ManufacturerName']
'Version'=$msiProps['BuildNumber']
'PackageCode'=$msiProps['ProductCode']
'Description'=$msiProps['Description']
'TargetProductName'=$msiProps['TargetProductName']
'MoreInfoURL'=$msiProps['MoreInfoURL']
'Size'=$path.Length
'Path'=$path.FullName
'CreationTime'=$path.CreationTime
}
}
}
} catch {
Write-Error $_.Exception.Message
}
}
function filter_product{
param(
$productName
)
$PRODUCT_FILTER=@("adobe")
$r=$PRODUCT_FILTER|?{$productName -like "*$_*"}
if($r){
return $true
}else{
return $false
}
}
$products=Get-MsiProducts
$patches=$products|%{Get-MsiPatch -product $_.ProductCode}
$productsHash=@{}
$products|?{$_.LocalPackage}|%{$productsHash.add($_.LocalPackage,$true)}
$patchesHash=@{}
$patches|?{$_.LocalPackage}|%{if(!$patchesHash.ContainsKey($_.localPackage)){$patchesHash.add($_.LocalPackage,$true)}}
$InstallFolder="$($env:SystemRoot)\installer"
$files=dir -Recurse -Include "*.msi","*.msp" -path $InstallFolder
$Files2=$files|%{
if($productsHash.ContainsKey($_.FullName)){
$_|Add-Member -MemberType NoteProperty -Name "installerState" -Value "InstalledProduct"
}elseif($patchesHash.ContainsKey($_.FullName)){
$_|Add-Member -MemberType NoteProperty -Name "installerState" -Value "InstalledPatch"
}else{
$_|Add-Member -MemberType NoteProperty -Name "installerState" -Value "Orphaned"
}
$_
}
$groups=$files2|Group-Object -Property "installerState"
$groups|%{
@{$($_.name)=($_.group|Measure-Object -Property Length -Sum).Sum}
}
$OrphanedFiles=$($groups|?{$_.name -eq 'Orphaned'}).Group
if($OrphanedFiles){
$ValidOrphanedFiles=($OrphanedFiles|%{
$item=Get-MSIFileInfo -path $_.FullName;
if((filter_product $item.DisplayName) -or (filter_product $item.Manufacturer)){
# do nothing for this filtered products
}else{
$item
}
})
$selectedOrphanedFiles=$ValidOrphanedFiles|select DisplayName,Manufacturer,Size,Path,CreationTime|Out-GridView -PassThru -Title "select the Orphaned Files to delete"
if($ValidOrphanedFiles){
$ValidOrphanedFiles|Export-Csv -Path $PSScriptRoot\ValidOrphanedFiles.$((get-date).ToString('yyyyMMddhhmmss')).csv -NoClobber -NoTypeInformation -Encoding UTF8
}
if($selectedOrphanedFiles){
$selectedOrphanedFiles|Export-Csv -Path $PSScriptRoot\CleanedOrphanedFiles.$((get-date).ToString('yyyyMMddhhmmss')).csv -NoClobber -NoTypeInformation -Encoding UTF8
# delete code
#$selectedOrphanedFiles|remove-item -Force
}
}
使用上面的powershell 腳本在另外一臺(tái)計(jì)算機(jī)運(yùn)行,發(fā)現(xiàn)輸出如下圖,大部分是office的更新,還有4個(gè)關(guān)于7zip的,所以我又瞄了一眼添加刪除程序里的信息。
7z 在添加刪除里顯示占用空間1.91GB。有點(diǎn)奇怪,我找到這篇參考 還有這篇blog,讓我們找找7zip的注冊(cè)表設(shè)置。
######### 我們需要看看HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\[IdentifyingNumber]\EstimatedSize
######### 通過(guò)win32_product 可以獲取程序的IdentifyingNumber
Caption : 7-Zip 9.20 (x64 edition)
Description : 7-Zip 9.20 (x64 edition)
HelpLink : http://www.7-zip.org/support.html
HelpTelephone :
IdentifyingNumber : {23170F69-40C1-2702-0920-000001000000}
InstallDate : 20180516
InstallDate2 :
InstallLocation :
InstallSource : C:\Windows\ccmcache\7\
Language : 1033
LocalPackage : C:\Windows\Installer\623473.msi
PackageCache : C:\Windows\Installer\623473.msi
PackageCode : {23170F69-40C1-2702-0920-000002000000}
PackageName : 7z920-x64.msi
然后我們發(fā)現(xiàn)7zip的EstimatedSize為2004912,而symantec的EstimatedSize為608916
####### 發(fā)現(xiàn)7zip 應(yīng)該是在注冊(cè)表中寫(xiě)錯(cuò)數(shù)值了,可能是對(duì)EstimatedSize單位理解不對(duì),正確單位是1kb的單位。
PS E:\> 594mb/608916
1022.89009321483
PS E:\> 2004912kb/1gb
1.91203308105469
PS E:\> 608916kb/1mb
594.64453125
PS E:\>
原來(lái)7Z只是軟件BUG導(dǎo)致的尺寸顯示問(wèn)題,那我們接著查補(bǔ)丁相關(guān)的問(wèn)題。
然后我們對(duì)孤立的安裝文件按照尺寸排序,看看這些文件的詳細(xì)內(nèi)容。PSMSI模組的get-msisummaryinfo 工作似乎不是很好,所以我們看不到msp的詳細(xì)信息(比如我關(guān)注的KB編號(hào)),沒(méi)有關(guān)系,我們有專門(mén)工具Orca可以看MSI、MSP的信息。
以dbeb3e.msp 為例,我們看看orca的顯示,切到msipatchMetadata表,這個(gè)補(bǔ)丁的KB編號(hào)為KB4011169
讓我們看看這個(gè)KB編號(hào)的補(bǔ)丁打了沒(méi)有,在已安裝的更新中搜索這個(gè)KB編號(hào),沒(méi)有找到。
該補(bǔ)丁對(duì)應(yīng)的ProductID 是{643AA346-D215-46E8-89B5-152AD0B7034E},在目標(biāo)計(jì)算機(jī)的注冊(cè)表中搜索這個(gè)ProductID 也找不到結(jié)果。
那么我們?cè)赪SUS看看這個(gè)KB4011169補(bǔ)丁的信息。
####### 最上面的這個(gè)補(bǔ)丁編號(hào)是最新的取代該補(bǔ)丁的補(bǔ)丁編號(hào),這個(gè)補(bǔ)丁對(duì)我這臺(tái)正在排錯(cuò)的計(jì)算機(jī)已經(jīng)安裝。
Kb4018389
KB4018330
Kb4018297
KB4011690
Kb4011636
Kb4011279
Kb4011229
這里我找到一個(gè)方法可以方便的批量看MSP的信息,而不用圖形的Orca 工具。下面腳本遍歷所有MSP文件,然后提取MsiPatchMetadata中的Displayname屬性(包含微軟補(bǔ)丁的KB編號(hào))
########## 我這里使用了PSMI模組里的get-msitable
$msps=dir -recurse -path c:\windows\instaler\ -include *.msp
$xxx=$msps|%{
$displayname=Get-MSITable -Path $_.fullname -Table MsiPatchMetadata|?{$_.property -eq 'displayname'}|Select-Object -ExpandProperty "value";
[PSCustomObject]@{"displayname"=$displayname;Path=$_.fullname}
}
$xxx|out-gridview
然后在窗口里搜索以上列的取代KB4011169的補(bǔ)丁鏈KB編號(hào),發(fā)現(xiàn)每個(gè)歷史補(bǔ)丁都在。
這里假設(shè)下孤立安裝文件產(chǎn)生的主要原因是因?yàn)檠a(bǔ)丁取代導(dǎo)致,那我們驗(yàn)證下這個(gè)測(cè)試:
把KB4011169的對(duì)應(yīng)的孤立文件dbeb3e.msp 移走,然后因?yàn)槿〈撗a(bǔ)丁的最新補(bǔ)丁KB為Kb4018389,且在正在排錯(cuò)的這個(gè)計(jì)算機(jī)上有安裝。那我們測(cè)試卸載這個(gè)Kb4018389補(bǔ)丁,看是否有問(wèn)題。沒(méi)有問(wèn)題,因?yàn)檫@個(gè)補(bǔ)丁是補(bǔ)丁替代鏈上的最后一個(gè)補(bǔ)丁
卸載KB4018389后,其替代的補(bǔ)丁KB4018330是否會(huì)在已安裝的更新列表中呢?(windows 是否會(huì)還原上一個(gè)版本的補(bǔ)丁?)是的,第一次卸載這個(gè)補(bǔ)丁花了4-5小時(shí),重啟后發(fā)現(xiàn)前一個(gè)版本的補(bǔ)丁在已安裝的更新中。
如果我們卸載了KB4018389,那么對(duì)應(yīng)Kb4018389現(xiàn)在對(duì)應(yīng)的MSP文件,c:\windows\installer\dd988e.msp 是否會(huì)被刪掉?是的,該文件在補(bǔ)丁卸載后在installer目錄不再存在。
假設(shè)卸載了KB4018389,我們又通過(guò)運(yùn)行windows更新又把它更新上了,那么新安裝的KB4018389對(duì)應(yīng)的MSP文件名字是否有變化?名字有變化,變?yōu)閐c993.msp
把KB4018389(kb4011196對(duì)應(yīng)的最新補(bǔ)丁)的前一個(gè)版本補(bǔ)丁(KB4018330)對(duì)應(yīng)的安裝文件ddf836.msp 刪除,那我們測(cè)試卸載這個(gè)Kb4018389補(bǔ)丁,看是否有問(wèn)題。同時(shí)注意KB4018330或Kb4018297是否會(huì)出現(xiàn)在已安裝更新里。 KB4018389 可以正常卸載沒(méi)有問(wèn)題,KB4018330由于我們把其對(duì)應(yīng)的補(bǔ)丁的MSP文件移走所以在已安裝的更新中看不到。但是我看到了KB4018297在已安裝的更新當(dāng)中,有意思的發(fā)現(xiàn)。另外這個(gè)是否如果檢查更新的話,你會(huì)看到有個(gè)兩個(gè)更新可用(KB4018389,KB4018330)看來(lái)我得加一個(gè)測(cè)試
我想已經(jīng)有足夠的信息去弄明白為什么Installer目錄會(huì)變得這么大了,因?yàn)閣indows 保留了多個(gè)補(bǔ)丁的歷史替代版本,當(dāng)你卸載一個(gè)補(bǔ)丁A時(shí),它還原上一個(gè)版本的補(bǔ)丁B,如果這個(gè)B還有上一個(gè)版本C,當(dāng)你再卸載B時(shí),它會(huì)還原C。
系統(tǒng)應(yīng)該有信息保留著補(bǔ)丁鏈的信息,因此如果找到這些信息的存放位置,可以構(gòu)建一個(gè)工具來(lái)保留特定數(shù)目的補(bǔ)丁鏈,比如只保留一個(gè)歷史版本。
按照現(xiàn)有的計(jì)算機(jī)的情況分析,instaler的空間大部分是被windows的 更新所占用,特別是補(bǔ)丁有多個(gè)替代版本時(shí)。其中office 補(bǔ)丁最多。
Windows Disk Cleaner 有清理歷史補(bǔ)丁的功能,但是不確定它的邏輯,比如我自己的windows 10 機(jī)器,使用了磁盤(pán)清理功能后,還有較多的歷史補(bǔ)丁存在。如果要弄明白windows disk cleaner的機(jī)制,可能還要做很多的實(shí)驗(yàn)才有結(jié)果。
powershell 使用的PSMSI的cmdlet get-msisummaryinfo 感覺(jué)在不同操作系統(tǒng)上顯示的信息不同,不是太可靠。所以自己最后決定還是寫(xiě)powershell 函數(shù)來(lái)提取關(guān)鍵信息,最終完成單腳本不再使用PSMSI模組。改良后的腳本最后發(fā)現(xiàn)了大量的SilverLight 更新殘留包,占了大概12GB,因?yàn)橹笆褂玫膅et-msiSummaryinfo 獲取silverlight 相關(guān)補(bǔ)丁信息時(shí),獲取不到標(biāo)題。所以會(huì)被認(rèn)為是adobe的包跳過(guò)。
腳本已經(jīng)轉(zhuǎn)成可自解壓執(zhí)行的exe文件 [下載](http://down.51cto.com/data/2447291)
放個(gè)github地址吧,方便更新維護(hù)。
https://github.com/yoke88/InstallerClean
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。