As part of the 2.3 build of SQLPSX I built an MSI based installer to package all 10 SQLPSX modules. The installer was created with Windows XML Installer (Wix). To build an MSI using Wix you manually edit XML files and run several command-line tool to generate the necessary files. Note if you’re a Visual Studio user there’s also a plug-ins to make some of the tasks easier. Let’s take a look at the solution built without Visual Studio…

Getting Started

Wix has a learning curve, so work through the tutorial to understand the concepts

Requirements

Do no block scripts for execution

Invoke UAC

Remove Previous versions of the module

Install to the default user module directory (C:\Users\<myUserID>\Documents\WindowsPowerShell\Modules\), but allow the user to select an alternative directory including system module location (C:\Windows\system32\WindowsPowerShell\v1.0\Modules\)

Solution

Unlike a zip file in which all of the contained files inherit the blocked settings when extracted, the files contained in MSI based installer will not be blocked. So, simply by using an MSI based installer over a zip file for module distribution I’ve eliminated a support issue where new PowerShell users forget to unblock the zip file downloaded from CodePlex.

The remaining requirements are solved by defining a Wix file with the proper attributes. The Wix XML file below which we’ll call modules.wxs provides a template:

In line 2 we see the use of a include file (wxi), which we’ll call Config.wxi. The include file allows you to externalize things like variable names. Config.wxi is located in the same directory as the wxs file and looks like this:

In lines 4-7, I like to include comments for the product ID. This ID is a GUID which you’ll need to generate. Using PowerShell you run the following command to create a GUID:

([System.Guid]::NewGuid().toString()).ToUpper()

Most of the options are self-explanatory see the Wix manual for details. Of note on line 11, InstallPrivileges=”elevated” will invoke UAC. To handle upgrades I define the product version and specify to remove previous versions on initialization (lines 12-19).

A custom license agreement can be used by creating an RTF file (preferably in Wordpad as the Wordpad RTFs are smaller in size then Word), placing the file in the same directory as our wxs file, and finally setting WixUILicenseRtf to the RTF file as in line 21.

Next I define the define the directory structure in lines 22 through 33. The important thing to keep in mind is the location where the files will be installed is configurable by the user. In line 22 the Wix convention is to define the outer most directory using the syntax rectory Id=”TARGETDIR” Name=”SourceDir”. Here again, there are built-in proprties in Wix. For a complete listing of the available properties see the Wix Property Reference. In line 23 the Document or My Documents folder is specified using the builtin Wix variable PersonalFolder:

Directory Id=”PersonalFolder” Name=”PersonalFolder”

In lines 24 and 25 I create the WindowsPowerShell and Modules directory if they don’t already exist. Finally I’ll define the root directories for each module in lines 25-29. In the example wxs file I only have two modules MyModule1 and MyModule2.

Lines 34 and 35 add help and about links to the installer.

Because PowerShell modules can be made up of multiple files and even subdirectories you’ll want to make use of Wix fragments. Lines 37and 38 define each module as a component.

<ComponentGroupRefId="MyModule1"?><ComponentGroupRefId="MyModule2"?>

Wix includes a command-line utility called heat, which among other things harvest a directories, and subdirectories and all files into a wxs XML structure. We’ll use the utility to create our fragments:

Because PowerShell ISE allows you to highlight and run each line individual, I’ll use ISE to run the following commands:

The above command will generate two wxs files for each module, one for MyModule1.wxs and one for MyModule2.wxs (see the Wix reference documentation for an explanation of each parameter). I’ll make use of Wix variables stored in a separate include file, Config.wxi. Each wxs file will need to be modified to a add a reference to the include file. I prefer to use sed for inserting lines into text-based files because its easy and fast. Here’ a command to add “<?include $(sys.CURRENTDIR\\Config.wxi?> as the second line to each wxs file generated from heat.exe:

Chad Miller is a Senior Manager of Database Administration at Raymond James Financial. Chad has worked with Microsoft SQL Server since 1999 and has been automating administration tasks using Windows Powershell since 2007. Chad is the Project Coordinator/Developer of the Powershell-based Codeplex project SQL Server PowerShell Extensions (SQLPSX). Chad leads the Tampa Powershell User Group and is a frequent speaker at users groups, SQL Saturdays and Code Camps.