最近两天我的C盘空间不够了,竟然只剩几K,真的很晕菜,而且即使使用CCleaner清理也没用,第二天又满了。所以下决心要清理Winsxs目录。之前我的winsxs清理脚本编写过一个版本,发布在我cnblogs的博客中,但是有些bug,在win7下不太好使,今天花了半天重新编制了一下。在上代码之前,先把算法逻辑说一下:
c:\windows\winsxs是系统目录,主要是防止臭名昭著的“dll hell”问题而设计的,原先windows升级之后,应用程序常常因为dll被升级了,导致无法正常运行,后来M$启用 winsxs 目录,保存每一个被升级的dll版本,每当应用程序需要使用dll的时候,就把与之对应版本的dll给它用,保证兼容性。
winsxs目录中所有的dll版本都存放在特殊格式的目录中,该目录名由以下的几个部分构成(我自己猜的,不一定解释准确),每个部分之间使用下划线连接:
- 子系统名称,取值一般有x86,amd64,msil等
- 库的全称,名称是全称,包含命名空间和库的名称,其中需要注意的是名称中可能会包含下划线,如果名字太长,会用...缩略
- 库的id,用于唯一标识库的id,因为简单的库的名称可能会有重复,所以分配一个唯一库id
- 版本,库的版本号,包含主版本,次版本,补丁版本和构建号
- 语言,如果没有语言则为none
- dll的id,也可能是校验码,反正我发现可以用来标识唯一的dll
所以清理winsxs的算法就很简单,找到重复的库,留下最高版本的,删除所有低版本的。这里使用windows的批处理,大家可以直接使用。
运行的时候,记得用“管理员权限”运行。
我刚才运行这个工具之后,发现存放清理结果的winsxs_del文件夹里边有“ 1764 个文件 1,404,255,801 字节”,看来成效还是不错的。
代码如下:
@echo off set mxms=a if not "!mxms!" == "a" set mxms=&&%comspec% /V:ON /C %0 %* && goto :EOF set mxms= rem 算法描述: rem windows的winsxs目录中文件夹的格式都是:"类型_名字_库id_版本号_语言_签名" rem 检查所有的文件夹,如果发现有版本不同的文件存在,则删除所有旧版本的文件夹 rem 需要注意:名字部分可能也会包含多个下划线 rem 创建目录 set startT=%TIME% set move_dir=%SystemRoot%\winsxs_del if not exist %move_dir%\nul md %move_dir% set log=%temp%\winsxs-clear.log pushd "%SystemRoot%\winsxs" echo ===================== start %DATE% %TIME% ============================ >> "%log%" rem 遍历winsxs文件夹的所有目录 FOR /F "eol=; tokens=1-4 delims= " %%a in ('dir /ad /o-n %SystemRoot%\winsxs\*.*') do ( if "%%c" == "<DIR>" if exist "%SystemRoot%\winsxs\%%d" call:fnDoClear %%d ) echo ===================== OK! %DATE% %TIME% ============================ >> "%log%" echo clear OK! echo from %startT% to %TIME%. Check your '%move_dir%' please. echo view log from %log% set startT= set move_dir= popd goto :EOF :fnDoClear rem arg: dir_name rem 分解目录名 FOR /F "eol=; tokens=1-14 delims=_" %%g in ("%1") do ( call:fnSplitName %1 if not "%f_type%" == "" call:fnDoClearDir %1 !f_type! !f_name! !f_id! !f_rev! !f_lang! !f_sign! ) goto :EOF :fnSplitName rem arg: dir_name rem return f_xxx vars set f_type= set f_name= set f_id= set f_rev= set f_lang= set f_sign= FOR /F "eol=; tokens=1-14 delims=_" %%g in ("%1") do ( if "%%m" == "" ( set f_type=%%g set f_name=%%h set f_id=%%i set f_rev=%%j set f_lang=%%k set f_sign=%%l ) else ( if "%%n" == "" ( set f_type=%%g set f_name=%%h_%%i set f_id=%%j set f_rev=%%k set f_lang=%%l set f_sign=%%m ) else ( if "%%o" == "" ( set f_type=%%g set f_name=%%h_%%i_%%j set f_id=%%k set f_rev=%%l set f_lang=%%m set f_sign=%%n ) else ( if "%%p" == "" ( set f_type=%%g set f_name=%%h_%%i_%%j_%%k set f_id=%%l set f_rev=%%m set f_lang=%%n set f_sign=%%o ) else ( if "%%q" == "" ( set f_type=%%g set f_name=%%h_%%i_%%j_%%k_%%l set f_id=%%m set f_rev=%%n set f_lang=%%o set f_sign=%%p ) else ( if "%%r" == "" ( set f_type=%%g set f_name=%%h_%%i_%%j_%%k_%%l_%%m set f_id=%%n set f_rev=%%o set f_lang=%%p set f_sign=%%q ) else ( if "%%s" == "" ( set f_type=%%g set f_name=%%h_%%i_%%j_%%k_%%l_%%m_%%n set f_id=%%o set f_rev=%%p set f_lang=%%q set f_sign=%%r ) else ( if "%%t" == "" ( set f_type=%%g set f_name=%%h_%%i_%%j_%%k_%%l_%%m_%%n_%%o set f_id=%%p set f_rev=%%q set f_lang=%%r set f_sign=%%s ) else ( if "%%u" == "" ( set f_type=%%g set f_name=%%h_%%i_%%j_%%k_%%l_%%m_%%n_%%o_%%p set f_id=%%q set f_rev=%%r set f_lang=%%s set f_sign=%%t ) else ( if "%%v" == "" ( set f_type=%%g set f_name=%%h_%%i_%%j_%%k_%%l_%%m_%%n_%%o_%%p_%%q set f_id=%%r set f_rev=%%s set f_lang=%%t set f_sign=%%u ) else ( echo ERROR: can not split "%1" )))))))))) ) goto :EOF :fnDoClearDir rem arg: dir_name type namespace id revision lang sign rem 找到最新版本的目录名 call:fnStdVer %5 set curorgver=%5 set curver=%R_STDVER% set cursign=%7 set R_STDVER= set newver= set neworgver= set newsign= FOR /F "eol=; tokens=1-4 delims= " %%a in ('dir /ad /o-n %2_%3_%4_*_%6_*') do ( if "%%c" == "<DIR>" ( call:fnSplitName %%d if not "%f_type%" == "" call:fnCompVer "!newver!" !f_rev! !f_sign! ) ) rem 如果没找到则返回 if "%newver%" == "" goto :EOF rem 如果找到,则将所有旧版本的清除 echo keep %2_%3_%4 %6 version %neworgver% echo keep %2_%3_%4 %6 version %neworgver% >> "%log%" FOR /F "eol=; tokens=1-4 delims= " %%a in ('dir /ad /o-n %2_%3_%4_*_%6_*') do ( if "%%c" == "<DIR>" ( call:fnSplitName %%d if not "!f_type!" == "" ( if not "!neworgver!" == "!f_rev!" if not "!newsign!" == "!f_sign!" ( echo ... clear %2_%3_%4 %6 version !f_rev! echo ... clear %2_%3_%4 %6 version !f_rev! >> "%log%" call:fnDelDir "%%d" ) ) ) ) goto :EOF :fnCompVer rem oldver newver newsign rem result set var newver rem 分解ver的每一个部分,左侧填充0之后再比较 set V1=%~1 call:fnStdVer %2 set V2=%R_STDVER% set R_STDVER= if "%~1" == "" set neworgver=%2&&set newver=%V2%&&set newsign=%3&& goto :EOF if /I "%V1%" LSS "%V2%" set neworgver=%2&&set newver=%V2%&&set newsign=%3&& goto :EOF goto :EOF :fnStdVer rem arg: ver FOR /F "eol=; tokens=1-10 delims=." %%v in ("%1") do call:fnStdVerImpl %%v %%w %%x %%y %%z goto :EOF :fnStdVerImpl rem major, minor, revision, build rem result set in R_STDVER set svi_1=0000000000%1 set svi_2=0000000000%2 set svi_3=0000000000%3 set svi_4=0000000000%4 set svi_5=0000000000%5 set svi_6=0000000000%6 set R_STDVER=%svi_1:~-10%.%svi_2:~-10%.%svi_3:~-10%.%svi_4:~-10%.%svi_5:~-10%.%svi_6:~-10% goto :EOF :fnDelDir rem arg: dir echo deleting %SystemRoot%\winsxs\%~1 >> "%log%" echo --- takeown >> "%log%" takeown /r /f "%SystemRoot%\winsxs\%~1" >> "%log%" echo --- takeown = %ERRORLEVEL% >> "%log%" echo --- cacls >> "%log%" cacls "%SystemRoot%\winsxs\%~1" /t /e /g everyone:f >> "%log%" echo --- cacls = %ERRORLEVEL% >> "%log%" echo --- move >> "%log%" move "%SystemRoot%\winsxs\%~1" "%move_dir%\%~1" >> "%log%" echo --- move = %ERRORLEVEL% >> "%log%" goto :EOF