I’m not sure why I never thought of this before. All of the pieces were right there in front of me for a while, but it didn’t click until today.
Today I created my first Virtual Device Driver with App-V!!!!
OK. Time for the fine print:
The driver isn’t really virtualized, I just make it only exist when it needs to and then goes away so that other software doesn’t know that it is there.
Background
So how does this work?
First, you need an app that has a device driver associated with it that you can virtualize if you deploy the driver separately.
But what is a driver? A specialized piece of software that runs inside the kernel. Typically delivered as a “.sys” file, it sometimes is also delivered with an inf file that explains how to install it. We can’t virtualize stuff running inside the kernel with App-V, but we can virtualize the control, which is what I will demonstrate in this blog post.
You see, most of the drivers associated with a user mode app that you want to virtualize can actually be started and stopped at will. Drivers that can’t be started and stopped at will are things that are always processing stuff, but if your virtual app isn’t running then it isn’t processing stuff and no other app is going to trigger it to do work either, then maybe we can start/stop it at will.
In fact, it probably is due to the stupidity of MSI that we don’t consider this. Packers never actually see the thing that does a driver install. Additionally, MSI generally will install or uninstall a driver, but starting/stopping it is unusual.
And this confuses a lot of IT experts. For example, if you read up on IT Ninja on the app used in this blog (Wireshark and the WinPCap driver), you’ll see a bunch of experts explain that after you install the driver, you must reboot before you can run Wireshark.
Bullocks I say! You just have to start the driver first. Rebooting is the lazy man’s way of starting the driver. OK.
So a bit about both drivers and windows services. Ultimatly, both store registration information in the same place — the HKLM\System\CurrentControlSet\Services key. Both drivers and services use a key under this location with the name of the driver or service to store their parameters. In fact, drivers are just a special case of services as far as windows is concerned.
Both may be created, controlled, and removed using the sc.exe utility. It’s just that drivers run in the kernel (as a kernel thread of the System process (processed 4, or 8 on DataCenter), and services run either as a user mode thread in one of the svchost processes (when delivered as a dll), or as a separate process (when delivered as an exe).
[Side note: Why are there a bunch of svchost processes? Because each houses services that run under the same security context; one for Local System, one for Network System, etc.]
Part 1: Creating a Silent Driver Installer/Controller for our package!
We want to use App-V 5 to virtualize Wireshark and dynamically deliver the WinPCap driver.
In the past, we might have natively installed WinPCap, and then virtualized Wireshark. But maybe you consider a driver with an open interface so that another program can snoop in on all of the TCP/IP traffic on the PC a security issue so you only want it enabled when it really has to be. I think we got you covered here.
With some applications there is a nice separate device driver installer, but in others you must create your own. WinPCap is a separate install driver, but unfortunately one of the annoying things about the WinPCap installer is that is does not have a silent install option. Apparently, once upon a time it did but the developers got annoyed with third party products silently dropping it on peoples systems without telling them, so they removed it. It is an exe based installer without the typical “/s” option. So we’ll have to create our own. This turns out to be easier than expected!
To investigate WinPCap, I use the sequencer. For simplicity, we are only looking at the 32-bit case. I start the sequencer, enter monitoring mode, install WinPCap, and work my way to the Sequence Editor to review all of the file and registry changes made. When there, I discover that the installer pretty much does the following:
- Drops 3 dlls into the System32 folder.
- Drops the driver file into the System32/drivers folder.
- Drops two exe files into Program Files/WinPCap folder. One of those is an uninstaller that we do not need.
- It creates the driver, as can be evidenced under the HKLM\System\CurrentControlSet\services\NPF key.
- It creates a windows service, the evidence for which we can see in HKLM\System\CurrentControlSet\Services\rpcapd key. From the entries, I can see that this is a manual start service that is the exe file I noticed earlier.
I investigate the three dll files to see if they require COM registration, by attempting a manual regsvr32.exe on them. I find that they are not COM. So if I want to build my own script based installer, I just need to copy them in place. Nice.
I gather the three dlls, the sys file, and the important exe. I start to create a powershell script that can do the install. The first step of the script will be to copy the dlls, the sys driver, and the required exe into the locations that the real installer used. To make it easy for us later on, the PowerShell script is written to take an argument that supplies the location of the source files, which I’ll make available in a single folder as part of the package.
You might hope that just copying the registry entries for the driver and services would be sufficient, but it is not. To install these you need to use a special system executable called “sc”, which will generate those keys and do some other undocumented magic. But using the information from those registry keys, I can come up with the command lines to install both the driver and the service. The captured entry for the windows service was:
HKLM\System\CurrentControlSet\System\npf\ DisplayName REG_SZ NetGroup Packet Filter Driver ErrorControl REG_DWORD 0x00000001 ImagePath REG_EXPAND_SZ \\?\C:\Windows\System32\drivers\npf.sys Start REG_DWORD 0x00000002 TimestampMode REG_DWORD 0x00000000 Type REG_DWORD 0x00000001
The appropriate install command for the driver is thus:
sc.exe create npf binPath= "C:\Windows\System32\drivers\npf.sys" type= kernel start= auto error= normal tag= no DisplayName= "NetGroup Packet Filter Driver"
NOTES:
- sc.exe is part of the OS and is in the PATH variable so it is automatically found without path reference.
- The options are always the option name immediately followed by the equal sign, and then a space, and then the value for the option. No space before the =, must have one after. If the value has a space in it, you must enclose the value in quotation marks.
- type “sc.exe /?” for more details on the command.
The captured entry for the windows service was:
HKLM\System\CurrentControlSet\System\rpcapd\ DisplayName REG_SZ Remote Package Capture Protocol... ErrorControl REG_DWORD 0x00000001 ImagePath REG_EXPAND_SZ C:\Program Files\WinPcap\rpcapd.exe ObjectName REG_SZ LocalSystem Start REG_DWORD 0x00000003 Type REG_DWORD 0x00000010
The appropriate install command for the service is thus:
sc.exe create rpcapd type= own start= demand binPath= "C:\Program Files\WinPCap\rpcad.exe" DisplayName= "Remote Package Capture Protocol..."
Furthermore, in testing I find that the driver is stoppable The developer declares this in his code when he/she writes the driver and when the driver starts up this information is given to the services controller. Once the driver is loaded you can use sc.exe with the queryex option to see if it is pause-able and/or stoppable.
The command to start the driver is:
sc.exe start npf
And the command to stop it is
sc.exe stop npf
So I can now make both an install and an uninstall PowerShell scripts. Here is the installer:
[CmdletBinding()] param( [parameter(Mandatory=$true)][string]$SourceDir ) copy $SourceDir\wpcap.dll C:\Windows\System32\wpcap.dll copy $SourceDir\pthreadVC.dll C:\Windows\System32\pthreadVC.dll copy $SourceDir\Packet.dll C:\Windows\System32\Packet.dll copy $SourceDir\npf.sys C:\Windows\System32\drivers\npf.sys copy $SourceDIr\rpcapd.exe "C:\Program Files\WinPCap\rpcapd.exe" sc.exe create rpcapd type= own start= demand binPath= "C:\Program Files\WinPCap\rpcad.exe" DisplayName= "Remote Package Capture Protocol..." sc.exe create npf binPath= "C:\Windows\System32\drivers\npf.sys" type= kernel start= auto error= normal tag= no DisplayName= "NetGroup Packet Filter Driver" sc.exe start npf exit 0
And here is the uninstall:
[CmdletBinding()] param( [parameter(Mandatory=$true)][string]$SourceDir ) sc.exe stop npf sc.exe delete npf sc.exe delete rpcapd rm C:\Windows\System32\wpcap.dll rm C:\Windows\System32\pthreadVC.dll rm C:\Windows\System32\Packet.dll rm C:\Windows\System32\drivers\npf.sys rm "C:\Program Files\WinPCap\rpcapd.exe" exit 0
This kind-of takes me back to my SoftwareWow days!
I place these two PowerShell script files (.ps1 files) and the 5 executable files into a folder for safe keeping on a separate machine, and move on to the task of making our virtual package.
Part 2: Creating the Virtual WireShark+WinPcap Driver
So next I revert the Sequencer and make the package. A summary of those steps:
- Natively install the WinPCap driver. I may either use provided installer or my own.
- In Sequencer Monitor mode, install WireShark. When it asks about installing WinPCap, decline.
- Also in monitoring mode, create a folder called “C:\Script” and copy the two PowerShell scripts plus the 5 executable files in it.
- Complete the package and save it off.
Edit the DeploymentConfig file. Under the UserConfiguration section, add both a StartVirtualEnvironment script and an EndVirtualEnvironment script. Like this:
<UserScripts> <StartVirtualEnvironment RunInVirtualEnvironment="false"> <Path>PowerShell.exe</Path> <Arguments>-ExecutionPolicy ByPass -File [{AppvPackageDrive}]\Script\ScriptInstallWinPCap.ps1</Arguments> <Wait Timeout="300" RollbackonError="true"/> </StartVirtualEnvironment> <TerminateVirtualEnvironment RunInVirtualEnvironment="false"> <Path>PowerShell.exe</Path> <Arguments>-ExecutionPolicy ByPass -File [{AppvPackageDrive}]\Script\ScriptUnInstallWinPCap.ps1</Arguments> <Wait Timeout="300" RollbackonError="false"/> </TerminateVirtualEnvironment> <UserScripts>
Summary
So now the driver only exists and will be running when WireShark is running.
This wouldn’t be a good idea on an RDS server, where multiple users could conflict, but it works great on desktops and VDI instances.
If you are less concerned with security, you can make the scripts into Add/Remove package scripts in the MachineScripts portion of the file instead. I am hoping that someday we’ll be able to put those scripts directly into the AppXManifest file of the package rather than the external DeploymentConfig.xml file.
The virtual Office2013 and 365 packages created by the Office Deployment Tool put scripts in this file today, so obviously the client can handle it.
Maybe someday the sequencer will let us do that…