Introduction
The article DevMSI: An Example C++ MSI/Wix Deferred Custom Action DLL describes creating a C/C++ DLL to provide support for deferred custom actions in Wix installs. This article builds on that foundation to provide a functional MSI install for the echo sample from the Windows WDK. It leverages DevMSI as needed for creation and removal of the non-PnP device 'root\echo', as well as removal of the service 'echo'.
The WiX files and build scripts are available for download here.
Background
In order to install the Echo driver completely, several steps have to be accomplished:
- Copy (and remove on uninstall) the driver and related application to an install directory. This task is accomplished through the use of WiX.
- Perform a driver file pre-install on the system. This does not actually set up the driver to be loaded: it merely does the necessary steps to inform Windows of the new driver and place it in appropriate system paths for discovery when the appropriate driver is detected. The MSDN article Driver Install Frameworks API (DIFxAPI) explains the API Microsoft provides for this task. In Wix V3 the API is available to the Wix framework.
- For installation, add the non-PnP device 'root\echo' to the system which will use the Echo driver. For removal, remove any non-PnP devices named 'root\echo' and also remove the echo service. For these tasks, the DevMSI dll will extend Wix with the needed functionality.
Wix V3 and DIFxAPI
Wix and DIFxAPI is a larger topic than this article intends to address. However, Koby Kahane's excellent blog post "Installing filter drivers with DIFxApp and a WiX v3 MSI" is an excellent starting point if the reader wishes to understand more of what is being done through the .wxs scripts presented here.
DevMSI
As mentioned above, the companion article DevMSI: An Example C++ MSI/Wix Deferred Custom Action DLL describes the C++ DLL extension used to perform needed setup tasks. In brief, DevMSI performs a limited subset of the WDK
devcon.exe sample: this subset will add or remove devices and related services on request.
Steps Required to build the MSI with WIX
The following steps will need to be accomplished to deploy the MSI, in sequence. Sample build scripts are available for download, but the essential commands without error checking are listed here for reference.
- Build the WDK echo driver and sample application
pushd %PROJECT_ROOT%\general\echo
build -ceZ
popd
Copy the needed build outputs to a working directory
set COPY_SOURCE="%PROJECT_ROOT%"\general\echo\kmdf\AutoSync\obj%_BuildType%_%DDK_TARGET_OS%_%_BUILDARCH%\%_BUILDARCH%
set COPY_TARGET="."
copy %COPY_SOURCE%\echo.inf %COPY_TARGET%\.
copy %COPY_SOURCE%\echo.sys %COPY_TARGET%\.
set COPY_SOURCE="%PROJECT_ROOT%"\general\echo\exe\obj%_BuildType%_%DDK_TARGET_OS%_%_BUILDARCH%\%_BUILDARCH%
copy %COPY_SOURCE%\echoapp.exe %COPY_TARGET%\.
set COPY_SOURCE="%PROJECT_ROOT%"\..\redist\wdf\%_BUILDARCH%
copy %COPY_SOURCE%\WdfCoInstaller01009.dll %COPY_TARGET%\.
Sign the driver and test executable - this example uses signtool with Verisign
Signtool sign /v /ac "c:\verisign\VeriSign Class 3 Public Primary Certification Authority -
G5.cer" /i Verisign /t http://timestamp.verisign.com/scripts/timestamp.dll [filename]
Create a .cat file from the INF file
inf2cat /driver:.\ /os:7_X64
- Sign the .cat file - see above for sample usage
- Run the WiX tool candle on the .wxs files
%WIX%\bin\candle -ext %WIX%\bin\WixUIExtension.dll -ext %WIX%\bin\WixUtilExtension.dll
-ext %WIX%\bin\WixDifxAppExtension.dll *.wxs -arch x64
Run the WiX tool light on the .wixobj files
%WIX%\bin\light -ext %WIX%\bin\WixUIExtension.dll -ext %WIX%\bin\WixUtilExtension.dll -ext
%WIX%\bin\WixDifxAppExtension.dll *.wixobj %WIX%\bin\difxapp_x64.wixlib -o echo.msi
All of the above are scripted into a set of .cmd files available for download, with "build.cmd echo" being an appropriate command line to start the sequence of steps.
Deferring a Discussion on Custom Actions
This installer uses deferred custom actions to leverage DevMSI for the non-PnP device installation and removal. Since many types of drivers (PnP device drivers, filter drivers)
do not require deferred custom actions to be installed (or removed), this article will first examine the
echo.wxs file with emphasis on the non-custom action portions.
Examining Echo.wxs
WiX files are written in XML, and many examples are available with minimal searching. This file starts with much of the same boilerplate as those examples:
='1.0'='Windows-1252'
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'
xmlns:difx='http://schemas.microsoft.com/wix/DifxAppExtension'>
Note the extra namespace provided for DIFxAPP, as many non-drivers will not include the extension. Next follows some slightly tailored boilerplate defining the product:
<Product Name='EchoInstaller'
Id='PUT-GUID-HERE'
Language="'1033'" Codepage='1252' Version='1.10' Manufacturer='CodeProject'
UpgradeCode='PUT-GUID-HERE'>
<Package Id='*' Keywords='echo' Description='WDK Echo Sample Driver Installer'
Comments='Installs WDK Echo Sample Drivers' Manufacturer='CodeProject' InstallerVersion='200'
Languages='1033' Compressed='yes' SummaryCodepage='1252' />
<Media Id='1' Cabinet='Installer.cab' EmbedCab='yes' DiskPrompt='WDK Echo Sample Driver Media' />
<Property Id='DiskPrompt' Value='WDK Echo Sample Driver Install Media' />
Many of the fields described here can and should be customized for the particular driver installation. Next, the file describes the installation directory target path:
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFiles64Folder'>
<Directory Id='INSTALLDIR' Name='WDK Samples'>
This example expects to have a base installation directory of "WDK Samples" under the Program Files folder (64-bit only, as this sample expects a 64-bit driver).
Within that base folder, the installer will put the echoapp.exe test application and create another folder for the driver files:
<Component Id='SampleApp' Guid='PUT-GUID-HERE'>
<File Id='echoappEXE' Name='echoapp.exe' DiskId='1' Source='Driver\echoapp.exe' KeyPath='yes' />
</Component>
<Directory Id='DriverDir' Name='Drivers'>
The .INF, .CAT, and .SYS files go in the driver folder, as does the KMDF co-installer. Note the extra XML that tells DIFxAPI these are the driver files to preinstall:
<Component Id='EchoDriver' Guid='PUT-GUID-HERE'>
<File Id='echoSYS' Name='echo.sys' DiskId='1' Source='Driver\echo.sys' KeyPath='yes' />
<File Id='echoINF' Name='echo.inf' DiskId='1' Source='Driver\echo.inf' />
<File Id='echoCAT' Name='KmdfSamples.cat' DiskId='1' Source='Driver\KmdfSamples.cat' />
<File Id='wdfcoinstaller01009DLL' Name='wdfcoinstaller01009.dll' DiskId='1' Source='Driver\wdfcoinstaller01009.dll' />
<difx:Driver AddRemovePrograms="no" Legacy="no" PlugAndPlayPrompt="no" Sequence='1' />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
Next, the file declares that the drivers and the sample app should be installed on a "complete" installation, and to use a stock simple UI for the installer:
<Feature Id='Complete' Level='1'>
<ComponentRef Id='SampleApp' />
<ComponentRef Id='EchoDriver' />
</Feature>
<UIRef Id="WixUI_Minimal" />
Skipping the Custom Action related items for now, there's nothing left in the .WXS files but closing some tags:
</Product>
</Wix>
That's all that is necessary!
Needed: Deferred Execution Custom Actions
Adding a non-PnP device to the system requires Administrator privileges on the system. The MSDN article Deferred Execution Custom Actions includes this important note:
Note that deferred custom actions, including rollback custom actions and commit custom actions,
are the only types of actions that can run outside the users security context.
Because DevMSI will need additional privileges, this means that a Deferred Execution Custom Action is necessary for all DevMSI tasks.
Examining DevMSI.wxs
The code necessary for wrapping DevMSI calls inside of Deferred Custom Actions is contained in
DevMSI.wxs. This file does not contain any of the project setup and definition, but rather defines only the extensions that are needed. In WiX terms, this is a fragment:
='1.0'='Windows-1252'
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Fragment>
A binary item needs to be defined, which denotes the WiX alias for the DLL ("DevMsi") and the path to the binary on the WiX computer. The binary will be extracted and used during setup as necessary by the MSI.
<Binary Id="DevMsi" SourceFile="..\DevMsi\x64\Release\DevMsi.dll" />
Next three Deferred Custom Actions are defined as available from the DevMsi binary
<CustomAction Id="AddDevice"
BinaryKey="DevMsi"
DllEntry="CreateDevnode"
Execute="deferred"
Impersonate="no"
/>
<CustomAction Id="DelDevice"
Return="ignore"
BinaryKey="DevMsi"
DllEntry="RemoveDevnode"
Execute="deferred"
Impersonate="no"
/>
<CustomAction Id="DelService"
Return="ignore"
BinaryKey="DevMsi"
DllEntry="RemoveService"
Execute="deferred"
Impersonate="no"
/>
For each of these, Id names the custom action for use elsewhere, BinaryKey is the alias name specified above, and DllEntry
is the C entry point to the DLL. The Execute="deferred" keyword is what makes this custom action a deferred custom action,
and Impersonate="no" allows the custom action to run with Admin privileges.
Needed: (Normal) Custom Actions
Defining a Deferred Execution Custom Action is relatively straightforward, as noted above. However, it is not immediately obvious how to pass parameters to the deferred custom action. According to the MSDN article Obtaining Context Information for Deferred Execution Custom Actions, there are very few properties (three) that can be retrieved during a deferred custom action. The only property that is really usable to pass parameters to the action is the CustomActionData
property.
The way to set the
CustomActionData
property for a deferred custom action is, surprisingly, through another custom action. This action does not need to be deferred since it does not need a privileged security context, and in fact probably should not be deferred so that it can in turn access other properties.
As a result, the way to call a deferred custom action with parameters goes through the following steps:
- Define a Custom Action that will set the
CustomActionData
property for a Deferred Execution Custom Action. - Define a Deferred Execution Custom Action.
- Schedule both the Custom Action and the Deferred Execution Custom Action to execute during an install or uninstall sequence as appropriate.
This wxs example uses a simple naming convention to help the reader understand which custom actions are aligned with their corresponding deferred custom actions. If Foo is the ID of a deferred custom action, then
Foo.SetParam
is the ID of the corresponding custom action that sets
CustomActionData
for Foo.
Revisiting Echo.wxs
Looking back into
echo.wxs at the latter part of the script reveals the custom actions:
<CustomAction Id="AddDevice.SetParam"
Return="check"
Property="AddDevice"
Value='"[DriverDir]echo.inf" root\Echo'
/>
<CustomAction Id="DelDevice.SetParam"
Return="check"
Property="DelDevice"
Value="root\Echo"
/>
<CustomAction Id="DelService.SetParam"
Return="check"
Property="DelService"
Value="Echo"
/>
As described above, AddDevice.SetParam
sets the Custom Action Data for
AddDevice
to be the value -- in this case, the expanded string
'"[DriverDir]echo.inf" root\Echo'. Looking back again at the directory paths earlier in the .wxs file finds the
[DriverDir]
property:
<Directory Id='DriverDir' Name='Drivers'>
So when the CustomAction is performed, DriverDir will be expanded to be the installation directory for the driver files, and the
CustomActionData
for AddDevice
will be set to the appropriate string. When the deferred custom action is executed, the DLL will be able to extract and use this string as an input parameter.
Similarly, the other two custom actions listed above (DelDevice.SetParam
and
DelService.SetParam
) set a string parameter for their associated deferred custom actions. Once the deferred custom actions and custom actions are all defined,
the next step is to declare when these actions are to be performed:
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallInitialize" />
<Custom Action ="AddDevice.SetParam" After="InstallFiles">NOT Installed</Custom>
<Custom Action='AddDevice' After="AddDevice.SetParam">NOT Installed</Custom>
<Custom Action ="DelDevice.SetParam" After="AddDevice">Installed</Custom>
<Custom Action='DelDevice' After="DelDevice.SetParam">Installed</Custom>
<Custom Action ="DelService.SetParam" After="DelDevice">Installed</Custom>
<Custom Action='DelService' After="DelService.SetParam">Installed</Custom>
</InstallExecuteSequence>
Note that the deferred custom action is set to run after the custom action that sets it's
CustomActionData
parameter. The NOT Installed text
declares that the (deferred or normal) custom action is run during installation, and the Installed text declares that the custom action is to be run only
if the product is already installed (presumably removal). There are many other possibilities and combinations that are not investigated in this article: WiX manuals and tutorials
can provide additional information.
Conclusion
Writing a WiX installer was actually easier than explaining how to do so. It is the author's hope that future users can leverage the information here to their advantage.
History
- 2013-04-09: Initial version.