(一)简介
封装前都会对封装用计算机(源计算机)中的硬件设备驱动进行处理。所谓处理,主要是卸载驱动,以及一些对驱动程序的调整等。
为什么要卸载源计算机的硬件设备驱动程序?理论上说,卸载源计算机的驱动不是必要的步骤,Windows本身就具有对硬件的即插即用能力,例如你添加一块网卡、更换一块显卡系统都会自动帮你搜寻适合的驱动并尝试安装。系统封装与部署技术是将源计算机中的系统封装后部署到其他计算机上,对系统来说并不认为是系统部署到了不同计算机中,而是发现了更多的即插即用设备,系统会为这些“多出来”的设备自动搜索与安装驱动。
但是事与愿违,只有实践才能检验理论。在实际的系统封装与部署中,由于硬件不卸载带来的一系列的小问题层出不穷。这些问题中有些的确是由于硬件商造成的,例如XP时代经典的声卡ID冲突问题;而也有一些也是由于Windows本身的功能造成的,例如出现“本地连接2”等。再加之每个用户都不想要一个“不干净”的系统,所以驱动处理逐渐的成为了必须要做的事情。
(二)原理
处理驱动程序不是什么难事,估计没有人不会卸载驱动与更改驱动。所以,在本文里我绝对不会和大家探讨怎么一步步的在设备管理器里点鼠标卸载驱动,这无异于浪费大家的时间和精力。我们要讨论的是怎么自动的卸载这些驱动程序,毕竟大家都喜欢双击一个程序解决一切问题的舒适感。今天我们要做的,就是要了解这种舒适感背后的技术。
每种硬件都有它们自己的ID,要查看硬件的ID很容易,如下图:
从图中可以看到,硬件不只有一个ID,一般来说一个硬件的硬件ID(HWID)会有1~4个。HWID就像身份证号一样独立标识着每类硬件,所以我们很容易的可以想到会存在一组硬件与HWID的对应关系列表。如何获得这个列表?打开注册表:
HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Enum6
可以看到其下的分类,打开某一个分类,例如Display,再选取下面的硬件子类,例如我计算机中的:
HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Enum/DISPLAY/BNQ76D9
打开后会发现其下有以设备编号命名的设备键,例如我计算机中的:
HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Enum/DISPLAY/BNQ76D9/5&5db3def&0&UID268435459
这其实是我的BENQ显示器,显示器的标识是“5&5db3def&0&UID268435459”,我们来看一下“5&5db3def&0&UID268435459”下的子键,找到“HardwareID”键,它的键值即是我的BENQ显示器的HWID。
当然您的显示器不可能完全与我的一样,但您可以在类似的位置找到您显示器的HWID。如果您已经找到了,那么可以以此类推看看其他硬件的HWID。您会发现HardwareID键包括多个HWID键值,就像我们在设备管理器里看到的一样。这也就是说,我们可以通过注册表来获取本机所有硬件设备的HWID列表了!
说到这里您大概会有如下两个问题了:
1、我们有了HWID又能做什么?有HWID就能自动卸载驱动吗?
2、怎么获取这个列表,难道要我们手工一个个的复制粘贴吗?
下面我们来逐步解决这些问题,我们要用到命令行工具的操作基础和一定的AU3基础,如果您这两方面比较薄弱,那么建议先多学习一下这方面的知识再来学习如下内容。
三)DevCon.exe
DevCon.exe是微软的可代替设备管理器的命令行工具。虽然是个命令行工具,但实际上讲,它有着比设备管理器更强大的性能。如果对此工具有更多兴趣,可以查阅微软官方支持:http://support.microsoft.com/kb/311272/zh-cn。
01.devcon.exe [-r] [-m://<machine>] <command> [<arg>...]
02.-r 如果指定它,在命令完成后若需要则重新启动计算机。
03.<machine> 是目标计算机的名称。
04.<command> 是将要执行的命令(如下所示)。
05.<arg>... 是命令需要的一个或多个参数。
06.要获取关于某一特定命令的帮助,请键入:devcon.exe help <command>
07.classfilter,允许修改类别筛选程序。
08.classes,列出所有设备安装类别。
09.disable,禁用与指定的硬件或实例 ID 匹配的设备。
10.driverfiles,列出针对设备安装的驱动程序文件。
11.wdrivernodes,列出设备的所有驱动程序节点。
12.Venable,启用与指定的硬件或实例 ID 匹配的设备。
13.find,查找与指定的硬件或实例 ID 匹配的设备。
14.C- Sfindall,查找设备,包括那些未显示的设备。
15.Zhelp,显示此信息。
16.hwids,列出设备的硬件 ID。
17.winstall,手动安装设备。
18.Flistclass,列出某一安装类别的所有设备。
19.Kreboot,重新启动本地计算机。
20.xremove,删除与特定的硬件或实例 ID 匹配的设备。
21.rescan,扫描以发现新的硬件。
22.cresources,列出设备的硬件资源。
23.restart,重新启动与特定的硬件或实例 ID 匹配的设备。
24.stack,列出预期的设备驱动程序堆栈。
25.status,列出设备的运行状态。
26.update,手动更新设备。
手动更新设备,无用户提示
添加、删除和更改根枚举设备的硬件 ID 的顺序。
这的确是一个功能很多的工具,但今天我们只需要它的一个功能就可以了,毕竟我们只需要用DevCon.exe来卸载驱动。我们来看DevCon.exe的“remove”功能,此功能的语法是:复制代码
01.DevCon.exe remove [HWID]
DevCon.exe的这个功能的主要目的,是移除使用本HWID的硬件驱动程序。即,我们只要知道设备的HWID,就可以通过DevCon.exe的命令行模式将其卸载。例如我们要卸载一个HWID为复制代码
复制代码
01.“PCI/VEN_1002&DEV_4393&CC_0104”
设备的驱动,则:
复制代码
01.DevCon.exe remove PCI/VEN_1002&DEV_4393&CC_0104
这为我们自动卸载驱动做了最好的铺垫。
(四)自动卸载5
自动获取HWID列表
既然我们只要有硬件的HWID就能卸载该硬件的驱动,那么我们现在就要想办法怎么弄到计算机中所有硬件的HWID列表了。前文已述,可以通过读取注册表的方法获取HWID,那么我们就可以通过AU3实现
复制代码
01.Func _DrvUnins_ReadHwids()
02. Local $HwidList[1][2], $p = 1
03. Local $RootKey = "HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Enum"
04. Local $i = 1
05. While 1
06. Local $SubKey1 = RegEnumKey($RootKey, $i)
07. If @error = -1 Then ExitLoop
08. $SubKey1 = $RootKey & "/" & $SubKey1
09. Local $j = 1
10. While 10 W5 L" ~; {3 h3 e6 j" s8 f4 G
11. Local $SubKey2 = RegEnumKey($SubKey1, $j)
12. If @error = -1 Then ExitLoop
13. $SubKey2 = $SubKey1 & "/" & $SubKey2
14. Local $k = 1
15. While 1
16. Local $SubKey3 = RegEnumKey($SubKey2, $k)
17. If @error = -1 Then ExitLoop
18. $SubKey3 = $SubKey2 & "/" & $SubKey3
19. Local $Cls = RegRead($SubKey3, "Class")
20. ;
21. If $Cls <> "Mouse" And _
22. $Cls <> "Keyboard" And _
23. $Cls <> "System" And _
24. $Cls <> "LegacyDriver" And _
25. $Cls <> "Computer" And _
26. $Cls <> "" Then
27. ReDim $HwidList[$p + 1][2]
28. $HwidList[$p][0] = $SubKey3
29. $HwidList[$p][1] = RegRead($SubKey3, "HardwareID")
30. $p += 1
31. EndIf4 C2 u% @ |6 K% I; Y3 h/ z
32. $k += 12 q /5 T6 O# E E3 W
33. WEnd
34. $j += 1
35. WEnd
36. $i += 1
37. WEnd
38. Return $HwidList
39.EndFunc ;==>_DrvUnins_ReadHwids
函数_DrvUnins_ReadHwids的目的,即是列举本机HWID列表。我做一下简单的说明:
(1)本函数中通过三层循环,依次读取Enum键下的设备类、设备子类、设备信息,最终获取每个设备的HWID;
(2)本函数最初创建一个$HwidList数组,随着读取工作的进行而逐步扩大数据量,$HwidList数组中保存的即为本机HWID列表;
(3)在决定要读取某个设备的HWID之前,还要判定一下该设备属于哪类设备,通过读取设备信息键值下的Class键值实现;)
(4)在Windows7下有5类设备没必要卸载,分别是PS2鼠标(Mouse)、PS2键盘(Keyboard)、系统设备(System)、系统遗留驱动(LegacyDriver)、硬件抽象层(HAL、或称计算机类型、或俗称电源管理)(Computer),所以只要设备属于这5类则不加入硬件ID列表。
(5)本函数的返回值为一个数组,数组内容即为本机HWID列表。
2、整理HWID列表
同类硬件有相同的HWID,例如两个相同的网卡,例如一个CPU的多个核心。HWID是定位到硬件类,而不是像网卡的MAC地址一样定位到每一个硬件,所以我们获得的HWID列表中很大可能性的存在相同的HWID。
虽然这些相同的HWID不会影响我们的驱动卸载质量,但我们没必要增加这额外的重复工作,所以我们有必要整理一下我们刚才获得的HWID列表。另外,由于我们刚才读取的HardwareID键值是一个“REG_MULTI_SZ”类型的键值,所以我们读取的硬件ID是这么一种形式:
HWID1[换行符] HWID2[换行符]……
所以我们在整理HWID之前必须将刚才的数据进行一定的转化。
复制代码
复制代码
01.Func _DrvUnins_TidyHwids($HwidList)
02. Local $tHwidList[1], $tp = 1
03. Local $i
04. For $i = 1 To UBound($HwidList, 1) - 1
05. Local $Hwids = $HwidList[$i][1]
06. If $Hwids <> "" Then
07. Local $tArr = StringSplit($Hwids, @LF)
08. If IsArray($tArr) And $tArr[0] > 1 Then
09. Local $j
10. For $j = 1 To UBound($tArr) - 1
11. ReDim $tHwidList[$tp + 1]
12. $tHwidList[$tp] = $tArr[$j]
13. $tp += 1
14. Next
15. EndIf
16. EndIf
17. Next
18. ;_ArrayDisplay($tHwidList)
19. Local $i, $j
20. For $i = 1 To UBound($tHwidList) - 2
21. For $j = $i + 1 To UBound($tHwidList) - 1
22. If $tHwidList[$i] <> "" And _
23. $tHwidList[$i] = $tHwidList[$j] Then
24. $tHwidList[$j] = ""
25. EndIf
26. Next
27. Next
28. ;_ArrayDisplay($tHwidList)
29. Local $Hwids[1], $p = 1
30. Local $i
31. For $i = 1 To UBound($tHwidList) - 1
32. If $tHwidList[$i] <> "" Then
33. ReDim $Hwids[$p + 1]
34. $Hwids[$p] = $tHwidList[$i]
35. $p += 1
36. EndIf
37. Next
38. ;_ArrayDisplay($Hwids)
39. Return $Hwids
40.EndFunc ;==>_DrvUnins_TidyHwids
41.
42.
函数_DrvUnins_TidyHwids的目的是整理刚才我们获得的HWID列表,简单介绍一下:
(1)本函数的第一个循环,将挤在一起的以换行符间隔的HWID转化成独立的;
(2)本函数的第二个循环,通过遍历将重复的ID设置为空字符串
(3)本函数的第三个循环,将刚才整理过的HWID列表重新填入$Hwids,并跳过空字符串。
(4)本函数的最终目的是返回一个整洁的本机HWID列表。
3、自动卸载的执行
有了HWID列表,再根据刚才说的DevCon.exe的命令行卸载驱动的功能,我们可以写一个自动根据HWID列表调用DevCon.exe卸载本机驱动的函数了。
01.Func _DrvUnins_Unins($Hwids, $DevCon)
02. ProgressOn("驱动卸载", "正在卸载驱动...")
03. Local $i
04. Local $Max = UBound($Hwids) - 1
05. Local $per = 1
06. For $i = 1 To $Max
07. $per = Int($i / $Max * 100)
08. ProgressSet($per, StringReplace($Hwids[$i], "&", "&&"), _
09. "正在卸载驱动... (" & $per & "%)")
10. RunWait($DevCon & " remove " & $Hwids[$i], "", @SW_HIDE)
11. Next
12. ProgressOff()
13.EndFunc ;==>_DrvUnins_Unins
_DrvUnins_Unins简介:
(1)本函数有两个形式参数,一个是本机HWID列表$Hwids,一个是DevCon.exe程序的所在位置$DevCon;
(2)本函数通过枚举HWID列表中的每一个HWID来卸载驱动程序
(3)本函数无返回值,目的是写在本机所有驱动程序。
好了,有了这三个函数,再加上DevCon.exe的强大性能,我们完成了对本机驱动的自动卸载工作
(五)常见问题问答
1、为什么执行完毕后我在设备管理器里还是看到很多驱动没有卸载掉?
很多设备是正在被使用的,例如你的CPU、显卡,这些设备如果被卸载掉会直接造成一系列的问题,所以Windows对于这类设备的卸载有着特殊的方针。当硬件正在被使用时其驱动不可被卸载,但这些设备的驱动将在你重启计算机后自动卸载掉。所以无需担心这些设备的驱动。
2、保留PS2鼠标(Mouse)、PS2键盘(Keyboard)、系统设备(System)、系统遗留驱动(LegacyDriver)、硬件抽象层(Computer)设备驱动的目的是什么?
(1)PS2鼠标和PS2键盘,这是没有必要卸载的,你见过谁的机器因为换了个PS2设备蓝屏或冲突吗?那为什么USB的键盘鼠标要卸载掉,这个问题比较难解释,一是很多USB键盘鼠标并不被识别为鼠标,而是被识别为人体学工程设备,在分辨上有些困难;二是某些USB键盘鼠标如果不被卸载,其残留的驱动在部署后对新接入的USB设备有影响(实践中发现的问题,具体原因不明)、
(2)系统设备,这是从XP时代就公认不用卸载的,经过历年实践的检验,完全可以放心。
(3)系统遗留驱动,这个翻译不是十分准确,LegacyDriver设备一般是以前安装过但现在并不在使用的,这类设备的存在不会影响系统部署的成功率。
(4)硬件抽象层(HAL),在上一章中已经叙述,Windows7的HAL只分为X86和X64两个版本,所以我们无需对HAL进行任何更改了。
3、为什么没有将PCI IDE控制器更改为“标准PCI IDE控制器”?
(1)Win6.x这代系统对驱动认证更为严格,而且很多驱动不是你关闭认证就可以真不需要认证的,大家有兴趣可以试试。这为自动更改“标准PCI IDE控制器”行成了阻碍。
(2)更改“标准PCI IDE控制器”这个“行业标准”建立于遥远的XP封装最初时代,这么做的目的已经被传的没有其本意了。经过理论求证和实践操作,一定程度上证明这种更改是没有太大实际意义的,所以我个人一直倾向于将其卸载。大家可以再通过实践来进一步求证。!
4、这些代码是否完美?
我不能保证代码的完美性,理论上说这只是一种自动卸载驱动的实现途径,如果大家有兴趣完全可以研究更强大的。但这里必须说一句,不管您有什么想法和理论,必须有长期实践为其撑腰。
5、驱动卸载后我的分区看不到了?
硬盘的分区也是靠驱动来实现的,系统通过驱动看到每个硬盘分区,而如果你在卸载驱动时某个硬盘分区没有正在被使用,则该分区的驱动会立刻被卸载,那么你就看不到这个分区了。这是个很正常的现象,就像卸载声卡后看不到声卡一样,无需担心硬盘分区内的数据,你只是暂时看不到他们了而已。
6、手工卸载VS自动卸载?
如果你实践过本章的代码,你可以很清晰的发现设备管理器里所看到的设备不是当前系统里的所有设备,所以即使你手工根据设备管理器卸载驱动,那么你卸载的是全部驱动吗?当然,自动卸载也不是完美的,人写的程序总会有错误,程序只会按照人的既定思路去做事,不会变通,但不应以此就判定自动卸载是垃圾的,虽然程序是人写的就会有错误,但程序是人写的就可以通过人去完善!
7、驱动卸载要多干净?
之前已经说过,驱动卸载不是必要的过程,而是为了避免细微的问题。而在以往的经历中我发现很多人过于苛求驱动的干净了,很多人往往为了一两个所谓的“幽灵硬件”忧心忡忡,这其实是毫无必要的。
你可以尝试为你的计算机更换一块网卡,你立刻就会看到你前网卡的“幽灵硬件”。“幽灵硬件”这种说法迷惑了太多人,这其实是Windows的一种机制,目的是你再将这个设备换回本计算机时可以立刻启用,简化驱动搜索与安装过程。“幽灵硬件”是一个具有一定偏颇性的叫法,无需过于担心它们的存在,但也别过于放心,某些时候它们的确能引起莫名其妙的问题。