风一样的男孩 发表于 2010-4-11 15:37:53

《封装日志》-2.硬件驱动的处理--自由天空

第二章、硬件驱动的处理

  驱动程序是沟通操作系统和硬件的重要桥梁,通过驱动程序可以让操作系统识别更多的硬件,并使硬件发挥更好的性能。然而封装系统时我们必须要处理一下源计算机的硬件驱动,毕竟你无法确定目标计算机是怎么样的硬件环境。
  处理好源计算机系统中的硬件驱动,可以拓展系统可部署的范围,减少因系统残留源计算机硬件和驱动信息造成的各种不良影响。虽然我没有在微软官方文档中看到必须处理源计算机硬件驱动的内容,但是根据长时间的实践与应用,在无数前辈们的基础上,我们总结出了一套切实可行的源计算机硬件驱动处理方法,这也是本章将要讲述的重点内容。
  Windows操作系统是即插即用的,也就是说当出现新硬件时会自动去识别,如果可能则会自动安装该设备的驱动。所以即使我们不处理源计算机操作系统的驱动程序,当系统部署到新计算机时也会自动的识别新硬件,这也可能是为什么微软没有强调封装部署必须处理驱动程序的原因之一。但现实情况事与愿违,某些硬件驱动由于硬件ID的问题本身就存在冲突,某些时候有些驱动程序的残留会造成不良影响等等,这虽然不是Windows的原因而是驱动制作者的问题,但这些情况实实在在的影响了系统封装与部署。
  我们要讲理论,可也要结合实践,实践才能出真知。本章将结合实践,讲讲在现实中我们应该怎么做。
  2.1 硬件抽象层(HAL)的处理
  2.1.1 什么是HAL
  硬件抽象层(Hardware Abstraction Layer,简称HAL),位于操作系统内核与硬件电路之间的接口层,其目的在于将硬件抽象化。它隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多种平台上进行移植。简单说就是将硬件抽象化,以利于各种操作。
  如果要查看当前计算机的HAL类型,打开设备管理器,展开“计算机”项目,即可看到当前计算机的HAL类型。HAL类型又被很多技术员称为“电源管理模式”或简称“电源”。
  Windows XP的HAL有很多种,有“Advanced Configuration and Power Interface (ACPI) PC”、“ACPI Multiprocessor PC”、“ACPI Uniprocessor PC”、“MPS Multiprocessor PC”、“MPS Uniprocessor PC”、“Standard PC”以及等等,种类很多,而且特殊的计算机硬件还会有特殊的HAL。Windows 2000、Windows Server 2003这些与Windows XP同属于Win5.x的操作系统亦是如此,HAL种类繁多。如果算上32位和64位系统的差别,那么HAL的种类至少还要乘2来计算。
  Windows 7的HAL就简单多了,分为了32位和64位系统两种:“ACPI x86-based PC”和“ACPI x64-based PC”,中文版本可能翻译了其名称,“ACPI基于x86的电脑”和“ACPI基于x64的电脑”。Windows Vista、Windows Server 2008这些与Windows 7同属于Win6.x的操作系统也是一样,HAL简单高效。某些特定机型还有个别专用的HAL,一般用户很少碰到,这里不再介绍。
  根据微软文档的要求,封装与部署不可用于不同HAL的计算机。不同的HAL之间是不完全通用的,如果非要把不适合此计算机的HAL用于此计算机,那么很容易出现各种错误。最常见的例子,如部署有Windows XP某计算机在系统部署后无法正常关机和重启。
  那我们要如何处理HAL,特别是像Windows XP这种HAL非常多的操作系统,难道要手动改吗?莫着急,待我一一道来。
  2.1.2 Windows XP HAL的处理

  Windows XP的HAL最常见的也有6种,既然微软的要求是不同HAL的不可通用,那如果碰到封装时源计算机是A种HAL,而部署的目标计算机是B种HAL,怎么办?手动改?如果部署了500台计算机个个手动改?或者我就封装6种HAL的系统,碰到哪种HAL的计算机就部署哪个系统,这样做不是不可,但一是需要维护至少6个系统映像十分麻烦,二是对于一般的IT人员还真不一定分得开什么计算机用什么HAL。
  那能不能自动判断并自动更改HAL?勇于探索的前辈们还真找到了方法。这个方法源于Windows XP到Windows Vista之间的年代,那时有一个测试版本,叫做Windows Longhorn。Windows Longhorn虽然没有像现在Win6.x那样将HAL简化到只有32位HAL和64位HAL两种,但Windows Longhorn具备了自动判定计算机HAL并自动更改HAL的能力。由于Longhorn的引导方式与XP无异,都是使用NTLDR引导,所以前辈们完美实现了移植Longhorn的HAL判定到XP!
  要准备的文件和程序:
  (1)Windows Longhorn的NTLDR,位于Windows Longhorn的C盘根目录下,默认隐藏。
  (2)SetACL.exe,用于更改部分注册表键值的权限。
  移植Longhorn的HAL自动判定到XP的方法:
  1、在当前系统中提取所需文件
  (1)如果系统安装过Service Pack,那么找到“C:\Windows\Driver Cache\i386”目录下与你当前Service Pack版本相同的.cab文件。例如当前系统是XP SP3,那么找“C:\Windows\Driver Cache\i386\SP3.cab”。如果系统没有安装过Service Pack,那么找“C:\Windows\Driver Cache\i386\driver.cab”。
  (2)新建一个文件夹,如C:\Windows\HalCache。
  (3)将(1)中找到的cab包中的halacpi.dll、halapic.dll、halmps.dll、halaacpi.dll、halmacpi.dll、hal.dll、ntkrnlmp.exe、ntkrnlpa.exe、ntkrpamp.exe、ntoskrnl.exe解压到C:\Windows\HalCache文件夹中。
  (4)将hal.dll改名为halstnd.dll,将ntoskrnl.exe改名为ntkrnlup.exe。
  (5)将C:\Windows\HalCache中所有文件复制到C:\Windows\System32中,覆盖同名文件。
  (6)删除C:\Windows\HalCache。
  2、创建一个.inf文件
  在C:\Windows\Inf文件夹(默认为隐藏属性)中创建一个名为dtecthal.inf的纯文本文件,写入如下内容:
  
  signature="$Windows NT$"
  DriverVer=07/01/2001
  
  MPS_MP=halmps.dll
  MPS_UP=halapic.dll
  E_ISA_UP=halstnd.dll
  ACPIPIC_UP=halacpi.dll
  ACPIAPIC_UP=halaacpi.dll
  ACPIAPIC_MP=halmacpi.dll
  
  ACPIEnable=2
  ACPIBiosDate=01,01,1999
  3、修改启动文件
  C:\NTLDR和C:\Boot.ini均为具有隐藏属性的系统文件,且具有只读属性,请去掉这些属性后再做下列操作。
  (1)备份XP的NTLDR为NTLDR_BAK(这个文件没有后缀名)。
  (2)将Longhorn的NTLDR复制到C盘根目录下。
  (3)将Bootfont.bin改名为Bootfont.bin.bak,由于Longhorn的NTLDR加载XP的Bootfont.bin会出现乱码的情况,所以先改名,以备以后恢复。(如果你是英文版系统则没有Bootfont.bin,可略过此步骤)
  (4)打开Boot.ini,找到XP的启动项,例如:
  multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect
  在最后加入自动检测HAL的参数“/detecthal”:
  multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect /detecthal
  4、处理掉当前的HAL信息
  一直有的方法说是把当前的HAL改成Standard PC可以提高兼容性,的确改成这个之后无论HAL是什么样的都不会出现如不能启动这种问题,至多是不能正常关机而已,可以说这个的确有很高的兼容性。但经过一段时间的观察、测试和应用,发现这种方法只是个权宜之计。我们既然有了HAL自动判定,且我们担心源计算机的HAL信息会影响系统在目标计算机中的部署,那么我们直接把当前计算机的HAL做掉好了。
  注册表中“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\ACPI_HAL”键和“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\ Root\ACPI_HAL”键记录了当前计算机的HAL类型和信息,删除掉它俩就相当于做掉了目标计算机的HAL。
  但这两个键都是有保护的,哪怕是Administrator用户都不能随便删除它们,幸好我们刚才准备了SetACL.exe。在命令提示符中切换到SetACL所在目录,运行如下命令:
  setacl.exe MACHINE\SYSTEM\ControlSet001\Enum\ACPI_HAL /registry /grant everyone /full
  setacl.exe MACHINE\SYSTEM\ControlSet001\Enum\Root\ACPI_HAL /registry /grant everyone /full
  打开注册表编辑器,删除掉“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\ACPI_HAL”键和“HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\ Root\ACPI_HAL”键。
打开设备管理器,可看到以前的“计算机”项目不见了,对,源计算机的HAL已经被我们删除了。当此系统部署到目标计算机时,由于我们已经移植了Longhorn的HAL自动判定,被删除的HAL注册表键会自动创建,且与目标计算机的HAL相匹配。
  经过一番折腾,我们成功的将Longhorn的HAL自动判定功能移植给了XP,由于这种HAL自动判定技术也完全来自于微软,所以正确率和稳定性毋庸置疑。至此,XP由于HAL类型造成的部署适用性问题完美解决。
  2.1.3 Windows XP HAL的自动处理
  虽然操作一次2.1.2中的过程并不复杂,但很多人为了达到完美封装经常要几遍甚至几十遍的调整系统并做系统封装测试,那么2.1.2中的过程就变的麻烦了。为了达到便利的操作,减少手工操作可能带来的错误,我书写了一段AU3程序来代替这个过程。
  注意,为了方便大家的理解,我加入了足够的注释。但这仍是一段简单的代码,为的是给大家展示自动化过程,为大家提供一份参考而已,不具备各种纠错功能。仅算作是抛砖引玉吧。
  ;将要用到的SetACL.exe和Longhorn的NTLDR编译进此AU3程序
  #Region
  #AutoIt3Wrapper_Res_File_Add=NTLDR_Longhorn
  #AutoIt3Wrapper_Res_File_Add=SetACL.exe
  #EndRegion
  ;执行主函数,而后退出程序
  _DetecHAL_Main()
  Exit
  ;主函数,调用其他函数完成任务
  Func _DetecHAL_Main()
     ;(1)提取文件,改名,并放置到相应位置
    _DetecHAL_HalFiles()
     ;(2)创建dtecthal.inf及其内容
     _DetecHAL_HalInf()
    ;(3)修改Boot.ini并替换NTLDR
    _DetecHAL_HalBootFiles()
    ;(4)删除源计算机HAL信息
     _DetecHAL_KillCurrentHAL()
    MsgBox(0, "", "完成")
  EndFunc   ;==>_DetecHAL_Main
  ;由cab包中提取文件,并修改与复制
  Func _DetecHAL_HalFiles()
    ;检测当前Service Pack版本
     Local $SP = @OSServicePack
    Local $Package
   If @OSServicePack = "" Then
     ;如果检测不到SP版本则使用driver.cab
     $Package = @WindowsDir & "\driver cache\i386\driver.cab"
   Else
     ;如果检测的到SP版本,则使用与SP版本相匹配的sp.cab包
     Local $n = StringRight($SP, 1)
    $Package = @WindowsDir & "\driver cache\i386\SP" & $n & ".cab"
  EndIf
   ;使用extrac32.exe解压cab包中的文件
   Local $Extrac = @WindowsDir & "\System32\extrac32.exe"
   ;创建存放文件的临时文件夹
  Local $CacheDir = @WindowsDir & "\Temp\HalCache"
   DirCreate($CacheDir)
  ;运行extrac32.exe到cab包中解压所需文件
   RunWait($Extrac & " /e /y " & '"' & $Package & '"' & " " & _
    "halacpi.dll halapic.dll halmps.dll halaacpi.dll halmacpi.dll hal.dll " & _
     "ntkrnlmp.exe ntkrnlpa.exe ntkrpamp.exe ntoskrnl.exe" & _
     " /l " & '"' & $CacheDir & '"', "", @SW_HIDE)
    ;将hal.dll和ntoskrnl.exe改名
    FileMove($CacheDir & "\hal.dll", $CacheDir & "\halstnd.dll", 1)
    FileMove($CacheDir & "\ntoskrnl.exe", $CacheDir & "\ntkrnlup.exe", 1)
    ;复制临时目录下的所有文件到Syste32中,覆盖同名文件
     FileCopy($CacheDir & "\*.*", @WindowsDir & "\System32\*.*", 1)
     ;删除刚才使用的临时文件夹
    DirRemove($CacheDir, 1)
  EndFunc   ;==>_DetecHAL_HalFiles
  ;创建dtecthal.inf,写入内容
  Func _DetecHAL_HalInf()
    Local $Inf = @WindowsDir & "\inf\dtecthal.inf"
     ;如果发现已存在同名文件,删除后重新创建
     If FileExists($Inf) Then FileDelete($Inf)
     ;写入Inf内容
     IniWrite($Inf, "Version", "signature", '"' & "$Windows NT[      DISCUZ_CODE_0      ]quot; & '"')
     IniWrite($Inf, "Version", "DriverVer", "07/01/2001")
    IniWrite($Inf, "hal", "MPS_MP", "halmps.dll")
     IniWrite($Inf, "hal", "MPS_UP", "halapic.dll")
    IniWrite($Inf, "hal", "E_ISA_UP", "halstnd.dll")
    IniWrite($Inf, "hal", "ACPIPIC_UP", "halacpi.dll")
    IniWrite($Inf, "hal", "ACPIAPIC_UP", "halaacpi.dll")
    IniWrite($Inf, "hal", "ACPIAPIC_MP", "halmacpi.dll")
    IniWrite($Inf, "ACPIOptions", "ACPIEnable", "2")
    IniWrite($Inf, "ACPIOptions", "ACPIBiosDate", "01,01,1999")
  EndFunc   ;==>_DetecHAL_HalInf
  ;修改与Longhorn HAL自动判定相关的启动文件
  Func _DetecHAL_HalBootFiles()
     ;去掉系统目录下NTLDR的只读、系统和隐藏权限
    FileSetAttrib(@HomeDrive & "\NTLDR", "-RSH")
    ;将XP的NTLDR改名为NTLDR_BAK
     FileMove(@HomeDrive & "\NTLDR", @HomeDrive & "\NTLDR_BAK", 1)
    ;装载Longhorn的NTLDR到C盘根目录
     FileInstall("NTLDR_Longhorn", @HomeDrive & "\NTLDR")
    ;恢复NTLDR的只读、系统和隐藏权限
     FileSetAttrib(@HomeDrive & "\NTLDR", "+RSH")
    ;去掉系统目录下Boot.ini的只读、系统和隐藏权限
    FileSetAttrib(@HomeDrive & "\Boot.ini", "-RSH")
    ;读取当前的启动信息
     Local $s = IniRead(@HomeDrive & "\Boot.ini", "operating systems", _
      "multi(0)disk(0)rdisk(0)partition(1)\WINDOWS", "")
    ;如果当前的启动参数中不包括/detecthal,则为其添加此参数
     If Not (StringInStr($s, "/detecthal")) Then
       $s = $s & " /detecthal"
       IniWrite(@HomeDrive & "\Boot.ini", "operating systems", _
           "multi(0)disk(0)rdisk(0)partition(1)\WINDOWS", $s)
    EndIf
    ;恢复系统目录下Boot.ini的只读、系统和隐藏权限
    FileSetAttrib(@HomeDrive & "\Boot.ini", "+RSH")
     ;如果系统目录下村咋诶Bootfont.bin则执行如下操作
     ;(英文系统没有Bootfont.bin)
     If FileExists(@HomeDrive & "\Bootfont.bin") Then
       ;去掉系统目录下Bootfon.bin的只读、系统和隐藏权限
       FileSetAttrib(@HomeDrive & "\Bootfont.bin", "-RSH")
       ;将Bootfont.bin改名为Bootfont.bin.bak
       FileMove(@HomeDrive & "\Bootfont.bin", @HomeDrive & "\Bootfont.bin.bak", 1)
     EndIf
  EndFunc   ;==>_DetecHAL_HalBootFiles
  ;删除源计算机系统的HAL信息
  Func _DetecHAL_KillCurrentHAL()
    ;确定临时目录的存在性
    DirCreate(@WindowsDir & "\Temp")
     ;装载SetACL.exe到临时目录
     FileInstall("setacl.exe", @WindowsDir & "\Temp\setacl.exe", 1)
     ;去掉ACPI_HAL的权限保护
     RunWait(@WindowsDir & "\Temp\setacl.exe" & " " & _
       "MACHINE\SYSTEM\ControlSet001\Enum\ACPI_HAL " & _
      "/registry /grant everyone /full", _
      "", @SW_HIDE)
     RunWait(@WindowsDir & "\Temp\setacl.exe" & " " & _
      "MACHINE\SYSTEM\ControlSet001\Enum\Root\ACPI_HAL " & _
      "/registry /grant everyone /full", _
      "", @SW_HIDE)
     ;删除刚才装载的SetACL.exe
     FileDelete(@WindowsDir & "\Temp\setacl.exe")
     ;删除ACPI_HAL键
    RegDelete("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\ACPI_HAL")
    RegDelete("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\Root\ACPI_HAL")
  EndFunc   ;==>_DetecHAL_KillCurrentHAL
  

2.1.4 Windows 7 HAL的处理
  在2.1.1节中已述,Win7的常见的HAL种类只有x86(32位)和x64(64位)两种:“ACPI x86-based PC”和“ACPI x64-based PC”,这为我们的封装与部署操作提供了很大的方便。我这里对x86和x64两种系统的适用范围再啰嗦几句:
  早期的CPU大多数是32位的,拥有32位CPU的计算机只能安装x86的操作系统。绝大多数软件都是基于32位系统设计的,所以一般而言32位系统对软件的兼容性要优于x64的操作系统。但是32位操作系统的最大内存支持仅有约3.25G(拥有内存扩展寻址的x86服务器版Windows除外),x86的Windows 7虽然会显示您所有的内存容量,但最大仅能使用约3.25G。
  而现今的CPU大多数是64位的,64位的CPU兼容32位,这也就是说拥有64位CPU的计算机可以部署x86的操作系统,也可以部署x64的操作系统。x64的操作系统可以发挥64位CPU的最大潜能,支持64位的应用程序,且兼容32位程序——绝大多数为32位的应用程序可以运作于x64操作系统下(某些古老的、国产的、与驱动相关的应用程序除外)。x64的操作系统拥有更大的内存寻址范围,理论上支持2的64次方约16384PB(单位:PB>TB>GB>MB)。对普通用户而言,如果您使用的是x64的操作系统,基本不用考虑内存支持的上限是多少。
  总结一下,一般而言:
  (1)如果CPU是32位的,只能部署x86的操作系统;
  (2)如果CPU是64位的,可以部署x64的操作系统,亦可以部署x86的操作系统;
  (3)x86的操作系统只能运行32位软件,但兼容性较高;
  (4)x64的操作系统可以运行64位软件,亦可以运行32位的软件,但对某些32位的软件可能不支持;
  (5)x64操作系统可支持的内存上限高于x86操作系统;
  (6)上述内容对windows操作系统普遍有效,不只针对Windows 7。
  说完了x86与x64的操作系统,我们再来回头看看Win7的HAL。在排除其他因素(如磁盘控制器驱动、兼容性问题等)的影响后:
  (1)如果我们封装了具有x86 HAL的Win7操作系统,可以部署于所有拥有32位和64位CPU的计算机。但只支持3.25G内存,只支持32位软件。
  (2)如果我们封装了具有x64 HAL的Win7操作系统,只可以部署于拥有64位CPU的计算机。支持更大的内存,同时支持32位和64位软件。
这样看,只要我们避免将拥有x64 HAL的Win7系统映像部署于32位的计算机中就可以了,不用像WinXP时那么麻烦了!

风一样的男孩 发表于 2010-4-11 15:39:26

2.2 硬件驱动程序的处理
  在本文的开篇我已经简单介绍了为什么要卸载源计算机Windows操作系统中的驱动程序。Windows具备即插即用(PNP)能力,即添加或更改一个硬件后会自动的尽可能的为其安装硬件。所以将系统映像部署到目标计算机,对于Windows来说不过是一次性更换了大量硬件而已。所以一定层面上讲,使卸载驱动成为必须要做的过程的并不是Windows本身,而是硬件厂商开发驱动的能力。
  各硬件厂商的实力不同,自然对驱动程序的开发能力也不相同,有时某些驱动本身就存在一定的BUG(如早年ATI和VIA的驱动),有时不同厂商之间的配合出现一定问题(如Realtek和ADI曾经存在硬件ID冲突问题),有时在源计算机中正常的驱动到了目标计算机上会因为硬件的不同而造成问题(如Intel PPM驱动,到AMD CPU的机器上会蓝屏),这一系列的问题让我们必须要对驱动程序做做文章了。

  2.2.1 一般的驱动卸载方法和注意事项

  硬件驱动卸载方法我相信大家都会,打开“设备管理器”,展开所有项目逐一卸载就可以了,但是注意下述项目:

  (1)关于“IDE ATA/ATAPI控制器”
  曾经盛传需要将“IDE ATA/ATAPI控制器”下凡是“某某控制器”一律改为“标准双通道PCI IDE 控制器”,据说可以加强兼容性。但经过长时间的观察与实践,结合相关理论后,发现这个说法存在一定的谬误。
  即使都是“标准双通道 PCI IDE 控制器”,但是此“标准双通道 PCI IDE 控制器”非彼“标准双通道 PCI IDE 控制器”。
  需要为一个硬件安装什么驱动,是由硬件ID(HWID)来决定的。HWID类似人的身份证一样,理论上是一种硬件的唯一编号,而我们再设备管理器中所看到的硬件设备名,都是通过驱动INF中HWID与其名称对应关系来决定的。就像我们有同名但身份证号不同的人一样,存在着都叫做“标准双通道 PCI IDE 控制器”但HWID不同的硬件,且不在少数。
  所以即使源计算机中改为“标准双通道 PCI IDE 控制器”,但如果只是和目标计算机中的控制器同名不同HWID,那么就是一种徒劳,且这样会让我们残留“IDE ATA/ATAPI控制器”的驱动,可能会对目标计算机的部署造成影响。
  那么我们对“IDE ATA/ATAPI控制器”要做的是卸载,而不是修改!对“IDE ATA/ATAPI控制器”执行卸载操作已经经过了实践验证,未发现问题。

  (2)关于“计算机”(HAL,或俗称“电源管理”、“电源”)
  曾经盛传将HAL改为Standard PC可以提高兼容性,这是仅次于(1)的一个谬误。HAL改为Standard PC倒是还有一点根据,Standard PC是具有最高兼容性的HAL,就像一个人虽然做事做不好,但至少不会出错。使用Standard PC如若出现HAL不匹配问题至多是计算机无法正常关机,计算机电源管理能力下降而已。
  但是,在2.1.2和2.1.4节中我们已经学习了WinXP和Win7的HAL处理方式,现在我想您也可以看出这个说法的谬误。
  对于WinXP,我们移植了Longhorn的HAL自动判定,使WinXP同样具备了HAL自动判定能力。同时,我们又使用了其他方法直接删除了源计算机的HAL,干净利索无残留。所以我们针对WinXP的HAL,至多做个卸载工作,如果您不想做也无所谓,反正我们有方法可以直接删除当前计算机的HAL类型。
  对于Win7,2.1.4节中已经详细说明,只要我们搞清楚我们封装的系统是x86还是x64,要部署的计算机的CPU是32位还是64位就可以了,根本无需专门处理HAL。当然,我们也不要卸载Win7的HAL,这是画蛇添足的行为。
  总结,根据我们对HAL的认识和已有的处理方法,对于WinXP,至多做个卸载工作,亦可以不做(当然,必须做过2.1.2节中的处理);对于Win7,无需卸载HAL,不用管它。

  (3)哪些驱动不需要卸载?
  有些驱动经过长期实践发现是无需卸载的,如下:
  WinXP:PS2键盘、PS2鼠标、系统设备;
  Win7:PS2键盘、PS2鼠标、系统设备、HAL。
  (严格讲,还应包括“遗留设备(LegacyDriver)”,后述)

  (4)还有哪些需要特别注意?
  第一,某些驱动的卸载是有顺序的,建议按照设别管理器从下到上的顺序执行卸载,某些驱动的卸载可能触发其他驱动的安装。
  第二,Windows\\Inf下的Oem*.inf是已安装驱动的INF文件,建议卸载驱动前删除它们,避免个别驱动在被触发搜寻驱动时使用Oem*.inf中的信息执行自动安装。系统部署安装驱动时会自动再生成,无需担心。
  第三,Windows\\Inf下的*.pnf,是驱动INF的预编译文件,建议全部删除,系统部署时会自动再生成,无需担心。
  第四,注意清理一下HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Network下的{4D36E972-E325-11CE-BFC1-08002BE10318}和{6BDD1FC5-810F-11D0-BEC7-08002BE2092F}键,即删除后重建,否则部署后可能出现“本地连接2”。
  第五,将HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\intelppm键的Start键值改为3,即将此服务改为手动。IntelPPM驱动是用于Intel CPU的,如果使用Intel CPU的计算机作为源计算机,此服务即会启动,而将此系统封装后部署于AMD CPU的计算机就会造成蓝屏宕机问题。而如果使用AMD CPU的计算机作为源计算机,WinXP下将没有此服务,Win7下则自动设为手动,无需额外处理。
  第六,尽可能使用硬件较少的计算机作为源计算机,这样可以最大程度的减少被安装驱动的数量。如使用实机,建议在BIOS里关掉不必要的项目;如果使用虚拟机,则尽最大可能所需硬件的数量。建议使用VMware虚拟机,只留下必须硬件,并在BIOS里关掉无用的硬件。传闻中所谓“双核计算机封装的系统好”、“老计算机封装的系统好”等等传闻,绝大多数都是根据表象产生的谬误,技术到家了,用什么都能封好系统!

  学习了上述注意事项后,您可以开始着手源计算机驱动程序的卸载了。

  2.2.2 驱动自动卸载

  (1)DevCon.exe
  DevCon.exe是由微软官方提供的,用于在命令行模式下管理硬件设备的工具。体积很小,但功能很全很强大。有兴趣可以运行一下“DevCon.exe /?”看看它的全部功能,而我们只使用它的驱动卸载功能。
使用DevCon.exe卸载一个硬件驱动的方法为:
DevCon.exe remove
  这也就意味着我们只要有这个硬件的硬件ID(HWID),既可以使用DevCon.exe来执行它的卸载。那我们怎么获得所有需卸载硬件的HWID呢?

  (2)去哪找计算机硬件的ID?
  通过设备管理器,展开某分类,选中其中某个设备,查看其“属性”中的“详细信息”选项卡,属性项目选择“硬件Id”,即可看到此硬件的HWID。但这是我们手动操作时要做的,自动化程序要怎么做?
  打开注册表编辑器,展开HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Enum键,可以看到其下按设备类分类了设备。展开其中一个设备类,可以看到其中的设备。再展开设备,看它其下的键,单击一下,看到左侧有个名为HardwareID的子键,它的键值即为此设备的所有HWID!
  例如我的:
  HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Enum\\PCI\\VEN_1002&DEV_9505&SUBSYS_E630174B&REV_00\\4&204eac5&0&0010
  这是我显卡设备的键,4&204eac5&0&0010下的HardwareID子键的键值为:
  PCI\\VEN_1002&DEV_9505&SUBSYS_E630174B&REV_00
  PCI\\VEN_1002&DEV_9505&SUBSYS_E630174B
  PCI\\VEN_1002&DEV_9505&CC_030000
  PCI\\VEN_1002&DEV_9505&CC_0300
  这四个都是我显卡的HWID。
  这样,我们通过遍历HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Enum键下的所有子键,读取HardwareID的键值即可获取计算机中所有的HWID!

  (3)驱动自动卸载的实现

  ;将devcon.exe的32位和64位版本均编译进本AU3程序
  #Region ;**** 参数创建于 ACNWrapper_GUI ****
  #AutoIt3Wrapper_Res_File_Add=devcon_x64.exe
  #AutoIt3Wrapper_Res_File_Add=devcon_x86.exe
  #EndRegion ;**** 参数创建于 ACNWrapper_GUI ****

  ;运行主函数,而后退出
  _DrvUnins_Main()
  Exit

  ;硬件驱动程序自动卸载,主程序
  Func _DrvUnins_Main()

    ;规定一个临时文件夹
    Local $TempDir = @WindowsDir & "\\Temp\\DrvUnins"
     DirCreate($TempDir)

     ;获取当前操作系统中所有需卸载的硬件ID
    Local $HwidList = _DrvUnins_ReadHwids()
     ;_ArrayDisplay($HwidList,"$HwidList")
    Local $Hwids = _DrvUnins_TidyHwids($HwidList)
     ;_ArrayDisplay($Hwids,"$Hwids")

    ;根据当前操作系统位宽,装载合适的DevCon.exe
     Local $DevCon = $TempDir & "\\DevCon.exe"
     If @OSArch = "x86" Then
       FileInstall("DevCon_x86.exe", $DevCon, 1)
     ElseIf @OSArch = "x64" Then
       FileInstall("DevCon_x64.exe", $DevCon, 1)
     EndIf
     If Not (FileExists($DevCon)) Then
       Return 0
    EndIf

    ;还原注册表中记录的驱动文件所在位置键值到默认值
     RegDelete("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion", _
      "DevicePath")
    RegWrite("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion", _
      "DevicePath", "REG_EXPAND_SZ", "%SystemRoot%\\inf")
    ;清理Windows\\Inf下的搜有驱动预编译文件*.pnf
     FileSetAttrib(@WindowsDir & "\\inf\\*.pnf", "-RSH")
     FileDelete(@WindowsDir & "\\inf\\*.pnf")
    ;清理Windows\\Inf下的所有Oem*.inf
     FileSetAttrib(@WindowsDir & "\\inf\\oem*.inf", "-RSH")
    FileDelete(@WindowsDir & "\\inf\\oem*.inf")
     ;预先处理上述项目可以一定程度上避免在驱动卸载过程中个别驱动自动重新安装
     ;并可以有效的减少对系统体积的占用
    ;根据硬件ID,使用DevCon.exe自动卸载所有驱动
    _DrvUnins_Unins($Hwids, $DevCon)

    ;删除如下注册表键值,避免出现“本地连接2”
     RegDelete("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Network\\" & _
       "{4D36E972-E325-11CE-BFC1-08002BE10318}")
    RegWrite("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Network\\" & _
         "{4D36E972-E325-11CE-BFC1-08002BE10318}")
     RegDelete("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Network\\" & _
         "{6BDD1FC5-810F-11D0-BEC7-08002BE2092F}")
     RegWrite("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Network\\" & _
         "{6BDD1FC5-810F-11D0-BEC7-08002BE2092F}")

    ;服务调整
    ;如果使用的是Intel CPU机器执行的封装,将intelppm服务的启动方式改为手动
     ;否则,如果部署到AMD机器上,会出现蓝屏宕机的情况
     If RegRead("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\intelppm", _
         'Start') <> '' Then

    RegWrite("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\intelppm", _
          'Start', 'REG_DWORD', '3')
     EndIf

     ;删除驱动回滚
     RegDelete("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Reinstall")

     ;删除DeviceClasses 保存本机设备信息
    RegDelete("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\DeviceClasses")

    ;删除临时文件夹
    DirRemove($TempDir, 1)

    Return 1
  EndFunc   ;==>_DrvUnins_Main

  ;粗略读取所有需卸载设备的硬件ID
  Func _DrvUnins_ReadHwids()
     ;定义一个名为$HwidList的二维动态数组,用于存储注册表键值和硬件ID
    Local $HwidList, $p = 1
     ;定义根键
     Local $RootKey = "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Enum"
     ;使用三重循环遍历$RootKey下的每个设备键值,获取HWID
     Local $i = 1
     While 1
       Local $SubKey1 = RegEnumKey($RootKey, $i)
       If @error = -1 Then ExitLoop
      $SubKey1 = $RootKey & "\\" & $SubKey1
       Local $j = 1
       While 1
           Local $SubKey2 = RegEnumKey($SubKey1, $j)
           If @error = -1 Then ExitLoop
           $SubKey2 = $SubKey1 & "\\" & $SubKey2
           Local $k = 1
           While 1
              Local $SubKey3 = RegEnumKey($SubKey2, $k)
              If @error = -1 Then ExitLoop
             $SubKey3 = $SubKey2 & "\\" & $SubKey3

              ;$SubKey3为要目标键,读取此键值对应设备的所属类
              Local $Cls = RegRead($SubKey3, "Class")
              ;定义一个标志,用于排除不需要卸载的设备
              Local $Flag
              If @OSVersion = "WIN_XP" = "WIN_2000" Or _
                    @OSVersion = "WIN_XP" Or _
                  @OSVersion = "WIN_2003" Then
                 ;如果系统为Win5.x系列,保留的无需卸载的设备类为:
               ;Mouse类(鼠标)、Keyboard类(键盘)、
                ;System类(系统设备)、LegacyDriver类(遗留设备)
                $Flag = $Cls <> "Mouse" And _
                    $Cls <> "Keyboard" And _
                     $Cls <> "System" And _
                     $Cls <> "LegacyDriver" And _
                     $Cls <> ""
              Else
               ;如果系统为Win6.x系列,保留的无需卸载的设备类为:
               ;Mouse类(鼠标)、Keyboard类(键盘)、
               ;System类(系统设备)、LegacyDriver类(遗留设备)、
               ;Computer类(HAL)
               $Flag = $Cls <> "Mouse" And _
                     $Cls <> "Keyboard" And _
                     $Cls <> "System" And _
                     $Cls <> "LegacyDriver" And _
                     $Cls <> "Computer" And _
                    $Cls <> ""
              EndIf
              ;如果此设备的类不属于要排除的类型,则将它的键和ID写入数组$HwidList
              ;并重新定义数组,为数组增加一个元素长度
             If $Flag Then
               ReDim $HwidList[$p + 1]
               $HwidList[$p] = $SubKey3
               $HwidList[$p] = RegRead($SubKey3, "HardwareID")
               $p += 1
              EndIf

              $k += 1
           WEnd
           $j += 1
      WEnd
      $i += 1
     WEnd
     Return $HwidList
  EndFunc   ;==>_DrvUnins_ReadHwids

  ;整理_DrvUnins_ReadHwids()函数生成的硬件ID列表
  Func _DrvUnins_TidyHwids($HwidList)
  ;定义一个动态数组$tHwidList,用于暂时收集整理后的硬件ID
   Local $tHwidList, $tp = 1

   ;由于$HwidList数组中读取到的硬件ID是以“@LF”分隔的字符串
   ;我们要获取每个ID,就要以“@LF”为分隔符来提取每个硬件ID
   Local $i
  For $i = 1 To UBound($HwidList, 1) - 1
     Local $Hwids = $HwidList[$i]
    If $Hwids <> "" Then
         Local $tArr = StringSplit($Hwids, @LF)
         If IsArray($tArr) And $tArr > 1 Then
            Local $j
            For $j = 1 To UBound($tArr) - 1
              ReDim $tHwidList[$tp + 1]
             $tHwidList[$tp] = $tArr[$j]
             $tp += 1
            Next
         EndIf
    EndIf
  Next
   ;_ArrayDisplay($tHwidList)

  ;删除数组中重复的硬件ID
  Local $i, $j
   For $i = 1 To UBound($tHwidList) - 2
     For $j = $i + 1 To UBound($tHwidList) - 1
      If $tHwidList[$i] <> "" And $tHwidList[$i] = $tHwidList[$j] Then
           $tHwidList[$j] = ""
         EndIf
    Next
   Next
   ;_ArrayDisplay($tHwidList)

  ;新建$Hwids数组,逐一读取$tHwidList中粗略整理的硬件ID
   ;不读取空元素,形成数据紧凑的数组$Hwids
  Local $Hwids, $p = 1
   Local $i
  For $i = 1 To UBound($tHwidList) - 1
    If $tHwidList[$i] <> "" Then
         ReDim $Hwids[$p + 1]
         $Hwids[$p] = $tHwidList[$i]
         $p += 1
    EndIf
   Next
   ;_ArrayDisplay($Hwids)

   ;所有硬件ID读取并整理完毕,回传$Hwids数组
  ;$Hwids中包含了所有需要卸载设备的硬件ID
   Return $Hwids
EndFunc   ;==>_DrvUnins_TidyHwids

;使用DevCon.exe,根据硬件ID列表,逐一卸载驱动
Func _DrvUnins_Unins($Hwids, $DevCon)
   ;启用一个进度条来显示驱动卸载的进度
  ProgressOn("驱动卸载", "正在卸载驱动...")

  ;遍历数组中的每个元素,执行卸载操作
   Local $i
   Local $Max = UBound($Hwids) - 1
  Local $per = 1
   For $i = 1 To $Max
    ;显示卸载进度
     $per = Int($i / $Max * 100)
     ProgressSet($per, StringReplace($Hwids[$i], "&", "&&"), "正在卸载驱动... (" & $per & "%)")
    Local $Index
    If $i < 10 Then
      $Index = "0" & String($i)
    Else
           $Index = String($i)
      EndIf

      ;使用DevCon.exe卸载驱动
      RunWait($DevCon & " remove " & $Hwids[$i], "", @SW_HIDE)
     Next
     ProgressOff()
  EndFunc   ;==>_DrvUnins_Unins

  这个代码包含了自动卸载驱动,还包含了之前所讲的几条注意事项的处理。此代码仅作参考。

风一样的男孩 发表于 2010-4-11 15:39:59

2.3 本章总结  
本章主要讲述了对源计算机中操作系统硬件驱动方面的处理,包括硬件抽象层的处理和驱动程序的处理两部分。本章讲述了很多概念,充实学习者的知识库,沿着从理论到基本实现、从基本实现到自动化实现的路线,循序渐进的与大家一同构建头脑中的知识构架。
    本章不拘泥于已有理论和各种“传说”,在理论与实践相结合的基础上,提出了很多新概念和新方法。我们要大胆怀疑、大胆尝试,以理论为依据,以实践为基石,扎实而有创新性的完成我们所追求的目标!
页: [1]
查看完整版本: 《封装日志》-2.硬件驱动的处理--自由天空