找出佔用Installer 目錄空間的元兇

找出佔用Installer 目錄空間的元兇

爲什麼談這個話題

今天看到一臺windows 7 的計算機,C盤分了50GB,結果installer 目錄有47GB,幸虧我對該目錄啓用過壓縮,壓縮後實際佔用32GB的樣子,但也足夠大了,已經導致C盤滿了,我刪了下TEMP目錄,清了c:\users\下一些很久沒用的用戶配置文件,救回2GB出來。但這個Installer目錄爲什麼會佔用這麼多空間?什麼樣的靠譜方法可以縮減該尺寸。

你以爲我沒做過嘗試

列舉下一些之前嘗試的方法,這些方法安全,但是收效甚微。

  • windows 清理程序,即使使用了隱藏的高級功能,但是清理掉的空間不是很多。cmd.exe /c Cleanmgr /sageset:65535 /sagerun:65535 sageset會彈出窗口讓選擇清理的項目,選擇後會保留在註冊表中,後面sagerun就會使用這個註冊表裏存儲的選項執行靜默的清理。

  • ntfs壓縮installer 目錄。文章開頭已經說了,47GB,壓縮到32GB,雖然壓縮比率也算較大了,但是其實並沒有解決回答爲什麼增長這麼大的問題,其實我覺得應該有辦法可以清理。只是不好操作而已,一旦我們必要的信息收集的差不多了,清理這個操作雖然危險,但是應該是可以做的。

不靠譜的清理軟件

OK,一般這種狀況下會找些流行的專用軟件來幹這個事,畢竟術業有專攻,第一次使用了WICleanup。

WICleanup列出了冗餘文件,而且我的文件清單上有多個文件大小都是一個尺寸,我說這個軟件難道是可以算出重複文件的功能,然後把重複文件刪掉?!,然後鑑於桌面說用過,我覺得應該至少問題不大吧,看了下目錄下有個命令行的版本帶-s 可以靜默清理,我試了一下,發現清掉30GB多的空間,到installer 目錄一看,我就知道壞了,裏面的MSP、MSI文件全乾掉了。

界面

這個軟件我後面看開發時間也是超級古老了,最近還有人發Blog介紹這個工具,而且評論區還有好多人反饋清理了好多.............沒發現副作用很大麼?。


pjl6523853
愛武俠的程序員2018-04-09 20:37:08#4樓
太感謝博主了!幫我清理了30G!請問博主可以轉載嘛

Maxwell_STU
Maxwell_STU2018-03-11 00:45:01#3樓
感謝博主分享,突然就清理出來10G以上的空間,感覺清爽了超級多,壓力一下子就小了。

KEVIN_LI_MY
KEVIN_LI_MY2017-11-29 08:59:26#2樓
我清理出了3g,也不少了。

我稍後測試了下用Wicleanup清理過的計算機,控制面板中部分程序的卸載、修復,windows 更新均有問題,主要問題是彈對話框提示找文件。

不靠譜的參考文檔

一篇看起很高深的文章指引,又是解構msi文件,又是C++清註冊表,主要是一個操作,就是刪除HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\***********\Patches註冊表,然後一股腦刪installer目錄的下的MSP、MSI文件。

  • 刪Patch 註冊表這事,會導致系統裏對歷史更新的記錄不存在。在更新軟件時會直接安裝新軟件進行覆蓋,而不會清理歷史版本,這樣可能因爲殘留導致問題。
  • 第三方軟件也放MSP、MSI文件在這個目錄,但是產品註冊表不一定老老實實的寫在上面那個位置,比如Adobe。
  • 還被系統正常使用的MSP、MSI等文件不應當被刪除(且看後面的軟件如何確認孤立的安裝文件,應該有區分的清理,而不是一股腦全刪)。

一個看起來可能還靠譜的軟件patchCleaner

有了上面教訓,我想我得了解下此類軟件的原理,然後確定可行後才能使用。首先我參考了微軟的員工的解決方法,算是廖勝於無吧,大概意思就是官方僅支持通過卸載軟件的方式來清理installer目錄,這個Blog在評論區討論了很多次,但似乎沒有什麼好的結論,也沒有提供太多有價值的信息。

在我訪問類似superuser 上的討論時,我發現了這個軟件patchCleaner,爲啥說可能還靠譜,因爲下面:

  • 最近更新時間 3/03/2016,相對較新
  • 有官方網站還可以訪問
  • 有FAQ,還在sourceforge上有個portable版本,儘管我還是沒有找到源碼。
  • 有原理解釋,它說使用WMI來查詢程序產品的信息,然後匹對installer 目錄下的文件,清理沒有在WMI中登記的信息(實際發現應該是用windows installer 的COM對象來查詢的相關msi、msp文件的信息)。
  • 我在程序目錄找到了一個WMIProducts.vbs 程序,它會使用installer的COM來列出一些程序和組件所使用的MSI、MSP信息。
  • 提供兩種選項,DELETE 和MOVE,但是MOVE是推薦的操作,給用戶選擇且考慮了風險,所以這是我覺得靠譜的地方。我覺得可以提供第三種,COMPRESS(利用ntfs壓縮技術可以節省一部分空間)或者第四種,刪除前壓縮文件到一個文件,這個文件可以後續用於還原。這個文件可以丟在其他地方,比如移動硬盤或者備份的地方。
  • 程序的界面還利用了一些MSI的技術可以顯示MSI、MSP文件的詳細信息。

MSP信息

準備讓數據說話

總結了上面的一些信息,我目前有下列問題,需要讓實際的數據說話:

  • 登記在系統中的MSP、MSI文件和孤立的MSP、MSI文件大小彙總佔的比率有多大,比如我這個47GB installer的文件夾的情況下孤立的MSP、MSI文件尺寸是什麼比率?知道這個信息可以讓我們確定如果我們的工具只清理孤立的MSP,MSI的話,這個工具的作用程度可能會有多大。
  • 多個這種非常大的installer 的計算機中,孤立的MSP、MSI文件尺寸是什麼比率?有什麼特徵麼? 如果孤立安裝文件所佔比率較大在多個計算機有共性,那麼說明這種工具有普遍需求性。
  • 孤立的MSP、MSI文件的相關信息有哪些共同點,多臺計算機上這些匹配出的孤立文件的相關信息有哪些共同點,比如發行者,文件版本等等。是否特定的程序造成了以上問題,如果多個計算機都因爲某個軟件或者更新導致Installer目錄較大,那麼我們是否可以通過集中的策略進行控制或者避免?

我的計劃:

  • 隨機抽取一批windows 計算機,按照操作系統大版本分類。 測試驗證了自己的win10
  • 使用patchCleaner提供的VBS文件,蒐集多個計算機上的在WMI和MSI數據庫中已經登記的MSP、MSI文件列表。 自己寫了一個ps 可以統計以上信息
  • 統計每個計算機上的installer 目錄下的文件、位置、尺寸,發行者、文件版本、title等信息。 大部分是微軟自己的更新
  • 彙總統計每個計算機上的已登記的MSP、MSI文件的總尺寸,孤立的MSP、MSI文件的總尺寸,兩者的尺寸比率分佈。 個人計算機上孤立的尺寸佔了總空間的一半多,而且大部分是一個KB2345678 的更新,我在已安裝的更新中搜索了這個KB,發現這個更新是沒有裝的(這些文件的日期是2015-2017都有,這個補丁應該出來很久了)

自己寫了powershell 腳本,按照patchCleaner的思路,自己過濾出孤立的安裝文件,這部分孤立文件我後來只過濾出MSI、MSP後綴的文件(這部分文件佔用最大)。其他後綴的Installer目錄下的文件,我們不去動它(因爲可能被引用,比如ICON文件或者EXE等文件)。MSI,MSP文件當中會有一些除了發佈者爲微軟的安裝文件,比如Adobe的文件用get-msisummaryinfo 獲取不到信息,我們也過濾掉(在PatchCleaner中也默認過濾掉了adobe的安裝文件),過濾後的孤立安裝文件大概如下圖所示。

下面截圖中時我的win10 的installer目錄的分析情況。使用之前,用過windows 清理程序的高級功能清過。即使使用清理程序清理過,我們也可以看見孤立的文件還有大概4GB,我手動測試了兩個安裝程序在清理後的工作情況,一個是AMD的顯卡軟件的,一個是微軟的c++ 2012 redistribution ,我先把文件從installer 目錄剪切走,然後執行卸載或者修復功能,都沒有報錯或者彈框要文件。

Installer 文件夾統計

patchCleaner 發現的信息統計

自己的工具?

當上面數據很明瞭清晰時,我們是否可以寫出自己的工具來..大致臆想了自己工具的功能和執行步驟

  • 首先使用cleanmgr 清理一次,看看用戶是否還要繼續。
  • 使用壓縮功能,壓縮一次,看看用戶是否還要繼續。
  • 算出孤立文件的數量(需要驗證安全性),優先MOVE (注意juncpoint的兼容問題),再進行刪除。我覺得刪除的時候是否可以提供選項把刪除的文件打包存放在其他位置,出問題時可以用來恢復。
  • 導出孤立文件列表、記錄刪除的孤立文件清單,用於後續分析。

列下PatchCleaner存在的不足的地方:
1.沒有辦法導出列表。
2.需要.net framwork4,不便於攜帶。
3.似乎沒有辦法可以對篩選後的孤立的文件再做選擇性操作。

由於windows 上有PSMSI 這個powershell 模組,所以最開始省去我大部分代碼,把主要精力放在測試上(反覆考慮後,還是自己寫powershell 調用Installer Com 接口的函數用於獲取信息,雖然比較困難,全程要用反射功能來操作Installer COM,而且讀取MSP文件額度問題已經解決,讀MSP時,數據庫的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 腳本在另外一臺計算機運行,發現輸出如下圖,大部分是office的更新,還有4個關於7zip的,所以我又瞄了一眼添加刪除程序裏的信息。

找出佔用Installer 目錄空間的元兇

7z 在添加刪除裏顯示佔用空間1.91GB。有點奇怪,我找到這篇參考 還有這篇blog,讓我們找找7zip的註冊表設置。

找出佔用Installer 目錄空間的元兇

######### 我們需要看看HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\[IdentifyingNumber]\EstimatedSize
######### 通過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

然後我們發現7zip的EstimatedSize爲2004912,而symantec的EstimatedSize爲608916


####### 發現7zip 應該是在註冊表中寫錯數值了,可能是對EstimatedSize單位理解不對,正確單位是1kb的單位。
PS E:\> 594mb/608916
1022.89009321483
PS E:\> 2004912kb/1gb
1.91203308105469
PS E:\> 608916kb/1mb
594.64453125
PS E:\>

原來7Z只是軟件BUG導致的尺寸顯示問題,那我們接着查補丁相關的問題。

然後我們對孤立的安裝文件按照尺寸排序,看看這些文件的詳細內容。PSMSI模組的get-msisummaryinfo 工作似乎不是很好,所以我們看不到msp的詳細信息(比如我關注的KB編號),沒有關係,我們有專門工具Orca可以看MSI、MSP的信息。

找出佔用Installer 目錄空間的元兇

以dbeb3e.msp 爲例,我們看看orca的顯示,切到msipatchMetadata表,這個補丁的KB編號爲KB4011169

找出佔用Installer 目錄空間的元兇

讓我們看看這個KB編號的補丁打了沒有,在已安裝的更新中搜索這個KB編號,沒有找到。

該補丁對應的ProductID 是{643AA346-D215-46E8-89B5-152AD0B7034E},在目標計算機的註冊表中搜索這個ProductID 也找不到結果。

那麼我們在WSUS看看這個KB4011169補丁的信息。

  • 補丁已經被其他更新取代,需要的計算機只有3臺,然而這三臺計算機並不包含我現在正在排錯的這個計算機。
  • 取代此更新的補丁清單中KB4018389這臺計算機上已經安裝。
  • 取代KB4011169的補丁鏈爲:
####### 最上面的這個補丁編號是最新的取代該補丁的補丁編號,這個補丁對我這臺正在排錯的計算機已經安裝。
Kb4018389
KB4018330
Kb4018297
KB4011690
Kb4011636
Kb4011279
Kb4011229

找出佔用Installer 目錄空間的元兇

這裏我找到一個方法可以方便的批量看MSP的信息,而不用圖形的Orca 工具。下面腳本遍歷所有MSP文件,然後提取MsiPatchMetadata中的Displayname屬性(包含微軟補丁的KB編號)

########## 我這裏使用了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的補丁鏈KB編號,發現每個歷史補丁都在。

這裏假設下孤立安裝文件產生的主要原因是因爲補丁取代導致,那我們驗證下這個測試:

  1. 把KB4011169的對應的孤立文件dbeb3e.msp 移走,然後因爲取代該補丁的最新補丁KB爲Kb4018389,且在正在排錯的這個計算機上有安裝。那我們測試卸載這個Kb4018389補丁,看是否有問題。沒有問題,因爲這個補丁是補丁替代鏈上的最後一個補丁

  2. 卸載KB4018389後,其替代的補丁KB4018330是否會在已安裝的更新列表中呢?(windows 是否會還原上一個版本的補丁?)是的,第一次卸載這個補丁花了4-5小時,重啓後發現前一個版本的補丁在已安裝的更新中。

  3. 如果我們卸載了KB4018389,那麼對應Kb4018389現在對應的MSP文件,c:\windows\installer\dd988e.msp 是否會被刪掉?是的,該文件在補丁卸載後在installer目錄不再存在。

  4. 假設卸載了KB4018389,我們又通過運行windows更新又把它更新上了,那麼新安裝的KB4018389對應的MSP文件名字是否有變化?名字有變化,變爲dc993.msp

  5. 把KB4018389(kb4011196對應的最新補丁)的前一個版本補丁(KB4018330)對應的安裝文件ddf836.msp 刪除,那我們測試卸載這個Kb4018389補丁,看是否有問題。同時注意KB4018330或Kb4018297是否會出現在已安裝更新裏。 KB4018389 可以正常卸載沒有問題,KB4018330由於我們把其對應的補丁的MSP文件移走所以在已安裝的更新中看不到。但是我看到了KB4018297在已安裝的更新當中,有意思的發現。另外這個是否如果檢查更新的話,你會看到有個兩個更新可用(KB4018389,KB4018330)看來我得加一個測試

  6. 使用腳本刪除找出的所有孤立的MSP、MSI文件,然後卸載KB4018297,看看是否報錯?(這個時候應該沒有任何歷史版本可以用了),另外卸載後檢查更新看看更新會檢查到幾個?(涉及到歷史補丁在WSUS的維護工作)。 卸載不會報錯,檢查更新後發現有三個補丁,KB4018389,KB4018330 同時存在,我選擇更新KB4018389和另外一個,看看重啓後是否還有讓更新KB4018330,重啓後發現不再提示KB4018330的更新

寫在最後

  • 我想已經有足夠的信息去弄明白爲什麼Installer目錄會變得這麼大了,因爲windows 保留了多個補丁的歷史替代版本,當你卸載一個補丁A時,它還原上一個版本的補丁B,如果這個B還有上一個版本C,當你再卸載B時,它會還原C。

  • 系統應該有信息保留着補丁鏈的信息,因此如果找到這些信息的存放位置,可以構建一個工具來保留特定數目的補丁鏈,比如只保留一個歷史版本。

  • 按照現有的計算機的情況分析,instaler的空間大部分是被windows的 更新所佔用,特別是補丁有多個替代版本時。其中office 補丁最多。

  • Windows Disk Cleaner 有清理歷史補丁的功能,但是不確定它的邏輯,比如我自己的windows 10 機器,使用了磁盤清理功能後,還有較多的歷史補丁存在。如果要弄明白windows disk cleaner的機制,可能還要做很多的實驗纔有結果。

  • powershell 使用的PSMSI的cmdlet get-msisummaryinfo 感覺在不同操作系統上顯示的信息不同,不是太可靠。所以自己最後決定還是寫powershell 函數來提取關鍵信息,最終完成單腳本不再使用PSMSI模組。改良後的腳本最後發現了大量的SilverLight 更新殘留包,佔了大概12GB,因爲之前使用的get-msiSummaryinfo 獲取silverlight 相關補丁信息時,獲取不到標題。所以會被認爲是adobe的包跳過。

  • 看來我也得包裝下標題,免得被審覈的小編歧視...................

放個做好的exe文件吧

腳本已經轉成可自解壓執行的exe文件 [下載](http://down.51cto.com/data/2447291

放個github地址吧,方便更新維護。
https://github.com/yoke88/InstallerClean

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章