讓我們繼續...
現在我們快完成了!我們可以在 WDS 服務器上啟動并從中部署映像,但不能從 MDT 執行此操作。
為什么?原因有二:
然后,讓我們創建第一個任務序列!
在部署共享中,右鍵單擊"任務序列"和"新建任務序列"。
首先,我們必須輸入任務序列 ID和任務序列名稱。指定 ID 和名稱,以便你輕松了解此任務序列與哪個操作系統鏈接。
之后,我們從中選擇要配置此任務序列的模板。在這里,我們選擇標準客戶端任務序列。
選擇要使用此任務序列部署的操作系統后。
我們繼續指定產品密鑰。
此時不指定產品密鑰
如果選擇此選項,則在部署過程中將提示您輸入產品密鑰。
指定用于激活此操作系統的多次激活密鑰(MAK 密鑰)
如果您有此操作系統的批量許可證產品密鑰,則可以在此處輸入。
指定此操作系統
的產品密鑰輸入鏈接到此操作系統/單臺計算機的零售許可證。
選擇所需的選項,然后單擊"下一步"。
在"操作系統設置"窗口中,我們指定"全名","組織",您還可以定義Internet Explorer主頁。然后單擊"下一步"。
輸入要使用此任務序列部署的操作系統的管理員密碼。單擊"下一步"。
有關此任務序列的配置的摘要。檢查它,然后單擊"下一步"。
在我們有一個進度窗口后,當任務完成后,您單擊"下一步",您將到達"確認"窗口。您可以單擊"完成"。
現在,我們可以使用 MDT 和剛剛創建的任務序列來部署操作系統,但是,我們將對其進行一些調整,以便部署操作系統以同時從WSUS獲取更新。
你可以查看此WSUS 教程,了解如何安裝它。
首先,右鍵單擊任務序列,然后單擊"屬性"。
第一個選項卡與任務序列常規的信息(如 ID、名稱等)有關。
還可以從此處啟用或禁用任務序列。
第二個選項卡介紹任務序列過程,即為準備和部署系統而將執行的所有步驟。在這里,我們將努力調整 我們的任務序列。
最后一個選項卡涉及將由任務序列部署的 OS。
現在,我們返回到"任務序列"選項卡,我們將在映像部署期間激活對更新的搜索。
單擊Windows 更新(應用程序安裝后)和Windows 更新(應用程序前安裝),然后取消選中禁用此步驟框。然后單擊"確定"。
返回到部署共享并右鍵單擊它,轉到"屬性",然后轉到"規則"選項卡。
在此選項卡中,可以直接為此部署共享添加/修改參數。有很多參數可用,我們在這里只設置幾個:
跳過計算機名稱 =否
跳過或不跳過指定計算機名稱的步驟。
跳過域成員身份 = 是
我們不希望系統提示我們將計算機加入域。
SkipUserData=YES
用于在映像之前備份用戶數據
跳過捕獲 =
是是否需要在部署后捕獲系統。
DoCapture=NO
通過將其設置為"否",可確保在部署結束時不會提示您
跳過本地選擇 = 是
不要提示我們選擇本地選擇(即:語言等)
跳過任務序列 =否
如果要跳過任務序列。在這里我們不想。
跳過時間區=是
跳過或不跳過"時區"窗口。我們在下面定義了時區,以便跳過它。
跳過應用程序 =
否我們不想跳過應用程序窗口,以防我們想要安裝其他應用程序
跳過摘要=否
我們不會為了仔細檢查設置而跳過摘要。
跳過BDD歡迎=是
跳過歡迎向導
時區=210
時區名稱=中國標準時間
我們定義時區。
WSUSServer=http://srv-wsus:8530
這是 WSUS 服務器,將用于獲取我們圖像的更新。
SkipComputerName=NO
Skip or not the step to specify the Computer Name.
SkipDomainMembership=YES
We don’t want to be prompted to join the computer to the domain.
SkipUserData=YES
for backup user data before imaging
SkipCapture=YES
If you need or not to capture the system after the deployment.
DoCapture=NO
By setting it to No it ensure you will not be prompted at the end of deployment
SkipLocaleSelection=YES
Don’t prompt us to select local selection (ie : languages, etc.)
SkipTaskSequence=NO
If you want to skip task sequences. Here we don’t want to.
SkipTimeZone=YES
Skip or not the TimeZone window. We define the time zone below so we can skip it.
SkipApplications=NO
We don’t want to skip Application window in case of we want to install additional applications
SkipSummary=NO
We don’t skip the summary in order to double check settings.
SkipBDDWelcome=YES
Skip the Welcome wizard
TimeZone=210
TimeZoneName=China Standard Time
We define the time zone.
WSUSServer=http://srv-wsus:8530
This is the WSUS Server which will be used to get update for our images.
(要查找您的時區,您可以使用以下網站
:http://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx )
我們還需要修改Bootstrap.ini文件。單擊"Edit Bootstrap.ini"。
我們將在此引導程序中指定.ini僅用于部署的特定服務帳戶及其憑據和鍵盤布局。
用戶 ID = WDSAdmin
UserDomain=WDS.lan
用戶密碼 = P@ssword
KeyboardLocale=fr-US
跳過BDD歡迎=是
UserID=WDSAdmin
UserDomain=WDS.lan
UserPassword=P@ssword
KeyboardLocale=fr-US
SkipBDDWelcome=YES
當您部署操作系統并將其鏈接到WSUS時,客戶端服務器/計算機的注冊表項將被修改。然后,如WSUS 教程中所述,它將始終查找 WSUS 服務器,而不是 Microsoft Windows Update 服務器。
如果要部署將保留在公司/基礎結構中并且仍將使用 WSUS 服務器的服務器或客戶端計算機,則這不是問題。但是,如果要部署計劃使用其他 WSUS 服務器或 Windows 更新的服務器或客戶端計算機,則必須刪除此注冊表項,以使它們再次聯系 Microsoft Windows Update 服務器。
當然,我們不會在每臺計算機/服務器上手動制作它,MDT會為我們制作它!
為此,我們需要在 TS 中添加兩個步驟。
1) 運行 PowerShell 腳本以刪除注冊表項
2) 重新啟動計算機
打開任務序列,然后單擊"添加"。如您所見,我們可以添加許多參數,甚至是一些Active Directory角色...
現在,我們將重點介紹"常規"菜單,尤其是"運行PowerShell腳本"和"重新啟動計算機"。
因此,首先,我們將創建 PowerShell 腳本。進入部署共享的腳本文件夾。對我來說,它是在E:\MDT 2013\DeploymentShare\Scripts中。
若要創建 powershell 腳本,請創建新的文本文檔,輸入以下命令:
Stop-Service wuauserv
Remove-Item -Path 'HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\*' -recurse
-force Start-Service wuauserv
保存時,將.txt擴展名更改為 .ps1。
之后,我們將添加一個"運行 PowerShell 腳本"步驟。請務必在整個步驟結束時添加它。
在PowerShell 腳本框中,輸入%SCRIPTROOT%\<YourScript>.ps1
修改注冊表后,我們需要重新啟動計算機,這就是為什么我們將添加一個步驟重新啟動計算機通過相同的過程添加> 重新啟動計算機。
您可以對 TS 進行的另一項修改(以及其他所有可用修改)是更改 MDT 為硬盤驅動器提供的默認名稱、更改大小、對其進行分區等。
Okay, We have our Task Sequence ready to use… now let’s try to deploy a VM!
在測試部署映像之前,請確保 VM 配置為在網絡上啟動。有時你還需要使用特定的vswitch。
正確配置 VM 后,可以啟動它,它應該在PXE上自動啟動,并從 DHCP 獲取 IP。
之后,它將要求您按ENTER鍵以繼續在網絡上啟動。
VM 正在連接到部署服務器...
最后在 WinPE 映像上啟動...
單擊"下一步",之后您可以看到摘要。單擊"開始"開始安裝。
然后,您可以輸入計算機/服務器的名稱。
單擊"下一步",之后您可以看到摘要。單擊"開始"開始安裝。
現在我們開始,我們只需要等待安裝完成...
部署操作系統后,你將看到我們為檢查WSUS的更新而修改的步驟。
在此之后,應會看到一個快速彈出窗口,運行我們創建的 PowerShell 腳本,并且 VM 將自動重新啟動。
如果一切順利,您的安裝就完成了。之后,可以在MDT中更改許多選項,因此請隨時查看并修改內容以滿足您的需求。
我希望本教程對您有所幫助,請隨時發表評論
以滿足您的需求。
我希望本教程對您有所幫助,請隨時發表評論
教程對您有所幫助,請隨時發表評論
搭建Windows驅動開發環境
開發主機安裝Microsoft Visual Studio 2013,
開發主機安裝Windows Driver Kit (WDK) 8.1,
測試虛擬機環境為Win7 64位
VS2013必須配合WDK8.1才可以進行驅動程序的開發,只有安裝了WDK8.1后,VS2013中才會出現驅動開發工程的模板,如下圖:
創建一個WDM驅動工程
新建一個空的WDM驅動工程,刪除附帶的Package工程,新建一個WDMDriver.c文件,我們的代碼都寫在該文件中。
編寫WDM驅動程序代碼
#include <wdm.h>/// @brief 初始化驅動程序/// @param[in] pDriverObject 驅動對象/// @param[in] pRegPath 驅動程序在注冊表中的路徑/// @return 初始化驅動狀態NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath){ UNREFERENCED_PARAMETER(pRegPath); KdPrint(("Enter DriverEntry\n")); pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice; pDriverObject->DriverUnload = HelloWDMUnload; for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { pDriverObject->MajorFunction[i] = HelloWDMDispatchRoutine; } pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp; KdPrint(("Leave DriverEntry\n")); return STATUS_SUCCESS;}
WDM驅動程序需要包含wdm.h頭文件。
代碼中我們給AddDevice設置了一個回調函數,NT驅動程序沒有此回調函數。該函數的作用是創建設備,該函數由PNP(即插即用)管理器調用,PNP管理器會在指定設備插入系統時調用該函數。
相比于NT驅動程序,WDM驅動程序還多了一個對IRP_MJ_PNP的處理函數,該函數的作用是處理PNP的請求包,例如啟動設備、停止設備等請求。
在WDM驅動程序中DriverEntry不再負責創建設備,而是交由AddDevice例程去創建設備。
/// @brief 設備擴展結構typedef struct _DEVICE_EXTENSION{ PDEVICE_OBJECT PDeviceObject; ///< 設備對象 PDEVICE_OBJECT PNextStackDevice; ///< 下層設備對象指針 UNICODE_STRING DeviceName; ///< 設備名稱 UNICODE_STRING SymLinkName; ///< 符號鏈接名} DEVICE_EXTENSION, *PDEVICE_EXTENSION; /// @brief 添加新設備 /// @param[in] pDriverObject 從I/O管理器傳進來的驅動對象 /// @param[in] pPhysicalDeviceObject 從I/O管理器傳進來的物理設備對象 /// @return 添加新設備狀態 NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT pDriverObject, IN PDEVICE_OBJECT pPhysicalDeviceObject){ KdPrint(("Enter HelloWDMAddDevice\n")); NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING devName = { 0 }; // 設備名稱 UNICODE_STRING symName = { 0 }; // 鏈接符號名 PDEVICE_OBJECT pDeviceObject = NULL; // 創建的設備對象 PDEVICE_EXTENSION pDeviceExt = NULL; // 設備擴展對象 // 初始化字符串 RtlInitUnicodeString(&devName, L"\\Device\\HelloWDMDevice"); RtlInitUnicodeString(&symName, L"\\??\\HelloWDM"); // 創建設備 status = IoCreateDevice( pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, SE, &pDeviceObject); if (!NT_SUCCESS(status)) { return status; } pDeviceExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension; pDeviceExt->PDeviceObject = pDeviceObject; pDeviceExt->DeviceName = devName; pDeviceExt->SymLinkName = symName; // 講設備對象掛接在設備堆棧上 pDeviceExt->PNextStackDevice = IoAttachDeviceToDeviceStack(pDeviceObject, pPhysicalDeviceObject); status = IoCreateSymbolicLink(&symName, &devName); if (!NT_SUCCESS(status)){ IoDeleteSymbolicLink(&pDeviceExt->SymLinkName); status = IoCreateSymbolicLink(&symName, &devName); if (!NT_SUCCESS(status)){ return status; } } pDeviceObject->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE; pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; KdPrint(("Leave HelloWDMAddDevice\n")); return status;}
和NT驅動一樣我們需要聲明一個設備擴展結構。
AddDevice例程的參數有兩個,第一個是驅動對象,第二個是物理設備對象,該對象由I/O管理器創建,物理設備對象的概以后講。
創建設備對象代碼和NT驅動程序類似,只是多了一個將設備對象附加在設備棧上的過程,設備棧的概念以后會講到。
當系統中插入多個指定硬件時該函數被被多次調用,也就是說會多次創建設備對象,創建多個設備時需要每個設備的設備名稱和鏈接符號都不同,示例代碼中使用相同的設備名和鏈接符號是有問題的,實際編寫程序時請注意。
3. PNP IRP處理例程
/// @brief 對即插即用IPR進行處理 /// @param[in] pDeviceObject 功能設備對象 /// @param[in] pIrp 請求包/ // @return 狀態NTSTATUS HelloWDMPnp( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) { KdPrint(("Enter HelloWDMPnp\n")); PDEVICE_EXTENSION pDeviceExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension; PIO_STACK_LOCATION pStackLoc = IoGetCurrentIrpStackLocation(pIrp); unsigned long func = pStackLoc->MinorFunction; KdPrint(("PNP Request (%u)\n", func)); NTSTATUS status = STATUS_SUCCESS; switch (func) { case IRP_MN_REMOVE_DEVICE: status = PnpRemoveDevice(pDeviceExt, pIrp); break; default: status = PnpDefaultHandler(pDeviceExt, pIrp); break; } dPrint(("Leave HelloWDMPnp\n")); return status;}
PNP派遣函數的第一個參數是設備對象,系統在回調該方法時會使用我們在AddDevice例程中創建出來的設備對象填寫該參數。
通過當前IRP棧位置我們可以知道請求的次功能代碼,在上述代碼中我們只對移除設備功能代碼做了特殊處理,其他PNP功能代碼進行默認處理,IRP棧以后會講到,目前不用在意。
PNP功能代碼的默認處理函數如下:
/// @brief 對PNP IRP進行默認處理 /// @param[in] pDeviceExt 設備對象擴展 /// @param[in] pIrp I/O請求包 /// @return 狀態NTSTATUS PnpDefaultHandler(PDEVICE_EXTENSION pDeviceExt, PIRP pIrp){ KdPrint(("Enter DefaultPnpHandler\n")); // 略過當前堆棧 IoSkipCurrentIrpStackLocation(pIrp); KdPrint(("Leave DefaultPnpHandler\n")); // 用下層堆棧的驅動設備處理此IRP return IoCallDriver(pDeviceExt->PNextStackDevice, pIrp);}
代碼中我們只是略過當前堆棧,并將IRP轉發給下層設備處理。
PNP移除設備代碼的處理函數如下:
/// @brief PNP移除設備處理函數 /// @param[in] pDeviceExt 設備擴展對象 /// @param[in] pIrp 請求包 /// @return 狀態NTSTATUS PnpRemoveDevice(PDEVICE_EXTENSION pDeviceExt, PIRP pIrp){ KdPrint(("Enter HandleRemoveDevice\n")); pIrp->IoStatus.Status = STATUS_SUCCESS; NTSTATUS status = PnpDefaultHandler(pDeviceExt, pIrp); // 刪除符號鏈接 IoDeleteSymbolicLink(&pDeviceExt->SymLinkName); //調用IoDetachDevice()把設備對象從設備棧中脫開: if (pDeviceExt->PNextStackDevice != NULL) IoDetachDevice(pDeviceExt->PNextStackDevice); //刪除設備對象: IoDeleteDevice(pDeviceExt->PDeviceObject); KdPrint(("Leave HandleRemoveDevice\n")); r eturn status; }
這個函數負責了驅動程序的卸載工作。
卸載前我們需要讓下層設備先完成這個請求,所以我們調用了DefaultPnpHandler處理方法。
除了刪除符號鏈接刪除設備對象外,我們還需要將設備對象從設備棧中脫開。
4. 默認IRP處理例程
/// @brief 對默認IPR進行處理NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp){ UNREFERENCED_PARAMETER(pDeviceObject); KdPrint(("Enter HelloWDMDispatchRoutine\n")); NTSTATUS status = STATUS_SUCCESS; pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); KdPrint(("Leave WDMDriverDispatchRoutine\n")); return STATUS_SUCCESS; }
代碼中我們簡單的將IRP設置為完成。
5. 卸載驅動例程
WDM驅動的卸載工作在PNP處理函數中的RemoveDevice函數中完成,所以卸載驅動例程不用做任何事。
/// @brief 驅動程序卸載操作void HelloWDMUnload(IN PDRIVER_OBJECT pDriverObject){ UNREFERENCED_PARAMETER(pDriverObject); KdPrint(("Enter HelloWDMUnload\n")); KdPrint(("Leave HelloWDMUnload\n"));}
3. 編寫INF文件
WDM驅動的安裝需要使用INF文件,VS2013已經給我們創建了一個默認的INF文件,我們只需修改它就可以。INF文件描述了WDM驅動程序的操作硬件設備的信息和驅動程序的一些信息,INF文件的配置項很多目前我們只需復制如下的內容即可:
;; chapter01HelloWDM.inf;[Version]Signature="$WINDOWS NT$"; 如果設備是一個標準類別,使用標準類的名稱和GUID 否則創建一個自定義的類別名稱,并自定義它的GUID ;自定義的類別在注冊表中 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\ 有顯示Class=%DeviceClassName%ClassGuid={24B968AA-9C43-41D4-BFD5-0DED43B29F16};INF文件的提供者Provider=TesterDriverVer=;如果不是標準類別設備,這里的配置必須的 [ClassInstall32] Addreg=ClassAddReg ;對應的注冊表是 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\ [ClassAddReg] HKR,,,,%DeviceClassName% HKR,,Icon,,"-5" ;驅動文件需要復制到的目的目錄,12表示%windir%/system32/drivers[DestinationDirs]DriverFilesName=12;驅動文件名段[DriverFilesName] chapter01-HelloWDM.sys[SourceDisksNames.x86]1 = "HelloWDM Installation Disk"... [SourceDisksNames.amd64]1 = "HelloWDM Installation Disk"...[SourceDisksFiles];源驅動文件在標記為1的磁盤下DriverFilesName=1;這里是設置制作商相關的選項,注意這里VS默認生成的標準設備的配置 如:%ManufacturerName%=Standard,NT$ARCH$ ;如果不是標準類別設備這里必須修改,要不然最后加載的時候會出現259錯誤;并且指定模型段 [Manufacturer]%ManufacturerName%=MyDeviceModel,ntx86,ntamd64 ;設備模型段[MyDeviceModel] ;指定設備描述和設備ID,以及安裝段%DeviceDesc%=DriverInstall.nt, PCI\VEN_8888&DEV_8888[MyDeviceModel.ntx86] ;指定設備描述和設備ID,以及安裝段%DeviceDesc%=DriverInstall.nt, PCI\VEN_8888&DEV_8888 [MyDeviceModel.ntamd64] ;指定設備描述和設備ID,以及安裝段%DeviceDesc%=DriverInstall.nt, PCI\VEN_8888&DEV_8888 ;這里需要注意WIN2000及其以上的系統這里有個.NT[DriverInstall.nt] ;指定需要拷貝的文件 CopyFiles=DriverFilesName ;指定寫注冊表的段 AddReg=InstallAddReg[InstallAddReg] HKLM, "System\CurrentControlSet\Services\TestWDMSvc\Parameters", "BreakOnEntry", 0x00010001, 0;注冊表中的服務名具體地址是 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services [DriverInstall.nt.Services];TestWDMSvc為安裝分服務名稱 Addservice = TestWDMSvc, 0x00000002, SysAddService ;服務的具體選項 [SysAddService] DisplayName = TestWDMSvc ServiceType = 1 ; SERVICE_KERNEL_DRIVER StartType = 3 ; SERVICE_DEMAND_START ErrorControl = 1 ; SERVICE_ERROR_NORMAL ServiceBinary = %12%\chapter01-HelloWDM.sys [Strings]ManufacturerName="MySoft"DeviceDesc="MyVEN_8888&DEV_8888Device"DeviceClassName="TestClass"
4. 配置工程屬性、編譯
配置選項選擇Win7Debug x64并且設置測試簽名:
編譯成功后可以在工程目錄x64\Win7Debug文件夾下找到如下文件:
chapter01-HelloWDM.sys,目標驅動文件
chapter01HelloWDM.inf,安裝配置文件
chapter01-HelloWDM.cer,證書文件
5. 虛擬機配置
開啟測試簽名模式
提前開啟DebugView
將chapter01-HelloWDM.sys和chapter01HelloWDM.inf拷貝到虛擬機上
6. 安裝WDM驅動
1. 打開虛擬機的設備管理器點擊添加過時硬件
2. 選擇手動安裝,點擊下一步
3. 選擇顯示所有設備,點擊下一步
4. 點擊從磁盤安裝
5. 點擊瀏覽,選擇chapter01HelloWDM.inf文件,點擊確定
6. 點擊下一步
7. 成功安裝驅動
8. DebugView中可以看到調試信息
9. 設備管理器中可以看到我們安裝的驅動程序
9. 點擊我們的驅動程序,選擇卸載驅動,DebugView中可以看到卸載驅動的調試信息
注冊表創建服務加載NT驅動程序
在Win7 64位虛擬機的注冊表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services下添加新項目,例如HelloNTDriver,這個名稱就是服務的名稱。
在這個項目下面添加如下子鍵:
ImagePath為驅動程序的路徑,必須以\??\開頭,在這里我們使用第一文介紹過的NT驅動程序。
Start為服務的啟動模式,3表示為按需要啟動服務。
Type為1,表明此服務在內核模式下加載,是一個驅動程序。
ErrorControl為1,表明加載出錯時彈出錯誤信息。
DisplayName,可以自定義
重啟虛擬機后,打開管理員權限的命令行工具,輸入net start HelloNTDriver即可加載驅動程序,輸入net stop HelloNTDriver即可卸載驅動程序。如果我們提前開啟DebugView工具,那么就可以在DebugView中看到調試信息。
編寫程序加載NT驅動程序
NT驅動程序的動態加載主要由服務控制管理程序(Service Control Manager, SCM)系統組件完成。加載和卸載NT驅動分為4個步驟:
為NT驅動程序創建服務。
開啟服務。
關閉服務。
刪除創建的服務。
加載NT驅動的代碼
char* pFullPathBuffer = NULL; DWORD bufferSize = 256; DWORD fullPathSize = 0; SC_HANDLE hServiceManager = NULL; // 服務控制管理器句柄 SC_HANDLE hDriverService = NULL; // 驅動服務句柄 // 獲取驅動程序文件全路徑 pFullPathBuffer = new char[bufferSize]; ZeroMemory(pFullPathBuffer, bufferSize); fullPathSize = GetFullPathNameA(pDriverPath, bufferSize, pFullPathBuffer, NULL); if (fullPathSize > bufferSize) { delete[] pFullPathBuffer; pFullPathBuffer = NULL; pFullPathBuffer = new char[fullPathSize]; ZeroMemory(pFullPathBuffer, fullPathSize); GetFullPathNameA(pDriverPath, bufferSize, pFullPathBuffer, NULL); } // 打開SCM管理器 // 需要管理員權限 hServiceManager = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (NULL == hServiceManager) { bRet = false; printf("OpenSCManager Fail: %d\n", GetLastError()); goto SAFE_EXIT; } // 創建服務 hDriverService = CreateServiceA( hServiceManager, pDriverName, pDriverName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, pFullPathBuffer, NULL, NULL, NULL, NULL, NULL); if (NULL == hDriverService) { dwRet = GetLastError(); if (dwRet != ERROR_IO_PENDING && dwRet != ERROR_SERVICE_EXISTS){ printf("CreateService Fail: %d\n", dwRet); bRet = false; goto SAFE_EXIT; } else { printf("Service Is Exist\n"); } // 如果服務已經存在則打開服務 hDriverService = OpenServiceA(hServiceManager, pDriverName, SERVICE_ALL_ACCESS); if (NULL == hDriverService) { printf("Open Service Fail: %d\n", GetLastError()); bRet = false; goto SAFE_EXIT; } } // 啟動服務 dwRet = StartServiceA(hDriverService, NULL, NULL); if (FALSE == dwRet) { dwRet = GetLastError(); if (dwRet != ERROR_SERVICE_ALREADY_RUNNING) { printf("Start Service Fail: %d\n", dwRet); bRet = false; goto SAFE_EXIT; } printf("Service Had Been Started\n"); } bRet = true;SAFE_EXIT: if (NULL != hDriverService) { CloseServiceHandle(hDriverService); hDriverService = NULL; } if (NULL != hServiceManager) { CloseServiceHandle(hServiceManager); hServiceManager = NULL; } if (pFullPathBuffer != NULL) { delete[] pFullPathBuffer; pFullPathBuffer = NULL; } return bRet; };
創建服務時需要填寫驅動程序文件的全路徑,所以我們在一開始先獲取驅動程序文件的全路徑。
創建服務時填寫的參數SERVICE_DEMAND_START,意思為需要時啟動我們的服務,也就是需要我們自己去啟動,也可以設置為開機啟動。
創建完成后我們就可以使用StartServiceA啟動服務。
卸載NT驅動的代碼
/// @brief 卸載NT驅動程序 /// 需要管理員權限, 否則會卸載失敗 /// @param[in] pDriverName 驅動程序名稱 /// @return 成功返回true, 失敗返回false bool UnLoadNTDriver(const char* pDriverName) { bool bRet = true; DWORD dwRet = FALSE; SC_HANDLE hServiceManager = NULL; // 服務控制管理器句柄 SC_HANDLE hDriverService = NULL; // 驅動服務句柄 SERVICE_STATUS serviceStatus; // 打開SCM管理器 // 需要管理員權限 hServiceManager = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (NULL == hServiceManager) { bRet = false; printf("OpenSCManager Fail: %d\n", GetLastError()); goto SAFE_EXIT; } // 打開服務 hDriverService = OpenServiceA(hServiceManager, pDriverName, SERVICE_ALL_ACCESS); if (NULL == hDriverService) { printf("Open Service Fail: %d\n", GetLastError()); bRet = false; goto SAFE_EXIT; } // 停止服務 dwRet = ControlService(hDriverService, SERVICE_CONTROL_STOP, &serviceStatus); if (dwRet == FALSE) { printf("Control Service Stop Fail: %d\n", GetLastError()); } // 刪除服務 dwRet = DeleteService(hDriverService); if (dwRet == FALSE) { printf("Delete Service Fail: %d\n", GetLastError()); } SAFE_EXIT: if (NULL != hDriverService) { CloseServiceHandle(hDriverService); hDriverService = NULL; } if (NULL != hServiceManager) { CloseServiceHandle(hServiceManager); hServiceManager = NULL; } return bRet; }
測試驅動程序代碼
/// @brief 測試驅動程序 void TestNTDriver(){ HANDLE hDevice = CreateFileA( "\\\\.\\HelloNTDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hDevice == INVALID_HANDLE_VALUE) printf("Open Device Fail\n"); else printf("Open Device Success\n"); CloseHandle(hDevice);}
將NT驅動程序代碼中創建的鏈接符號作為參數傳遞給CreateFile就可以打開設備對象,原來的鏈接符號格式為"\??\HelloNTDriver",這里我們需要修改成"\\.\HelloNTDriver"。
Win32程序和驅動程序的溝通都是通過這樣的方式,在前面的文章中我們已經對IRP_MJ_CREATE和IRP_MJ_CLOSE設置了派遣函數,CreateFile函數會觸發IRP_MJ_CREATE的排遣函數,CloseHandle函數會觸發IRP_MJ_CLOSE的排遣函數。
主程序代碼
int main(int argc, char** argv){ if (argc == 3) { bool bRet = false; bRet = LoadNTDriver(argv[1], argv[2]); if (bRet) printf("Load NT Driver Success\n"); else printf("Load NT Driver Fail\n"); system("pause"); TestNTDriver(); system("pause"); bRet = UnLoadNTDriver(argv[1]); if (bRet) printf("UnLoad NT Driver Success\n"); else printf("UnLoad NT Driver Fail\n"); } system("pause"); return 0; }
主程序接受兩個命令行參數,第一個為驅動程序的名稱(也用做服務名),第二個為驅動程序的路徑。
主程序先加載驅動,之后測試驅動,最后卸載驅動。
運行程序
編譯我們程序,將得到的可執行文件(LoadNTDriver.exe)和需要加載的NT驅動程序(chapter01-1.sys)放在虛擬機的相同目錄下,打開管理員權限的命令行工具,運行如下命令:
之后我們會在DebugView中看到如下的調試信息:
如果在卸載驅動前我們可以在注冊表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services目錄下看到系統自動創建的HelloNTDriver項: