About Windows Installer, the .NET Framework, and Visual Studio.
A while back I explained how Windows Installer sets the TARGETDIR property to the root of the fixed drive with the most free space available. The problem is that even an attached USB external drive can appear as a fixed drive, and these may be later detached. If components are installed to that drive and the drive is detached, repairing, patching, or even uninstalling the product may fail because the components are not available and cannot be updated or removed. This is also one potential reason Windows Installer may trigger a repair after a failed resiliency check.
The problem is that this can be difficult to test in an automated testing environment. Attaching and detaching USB disks or even adding additional hard disk to a bank of test machines may be prohibitive. Adding disks must also survive a reboot, or you may run into a race condition where the drive isn’t ready before it is expected to be available. Fortunately, Windows 7 and Windows Server 2008 R2 lets us attach virtual disks that are indistinguishable from physical hard disks to Windows Installer.
With Windows 7 or Windows Server 2008 R2 installed you can use diskpart.exe to create, format, and attach a virtual disk. Diskpart.exe can also be run in scripting mode so it can easily be automated. The idea is that you will create a dynamic virtual disk, create a volume in it, format it, then attach it. It’s important that you also assign a drive letter because Windows Installer will only enumerate drives letters.
A script for diskpart.exe may look like the following.
create vdisk file=c:\test.vhd maximum=1048576 type=expandable select vdisk file=c:\test.vhd attach vdisk create partition primary format fs=ntfs label=test quick assign letter=v
If you were to put this into a file named, for example, “attach.txt”, you could run it using the following command in an elevated process. On a fairly average machine, this only took about 13 seconds to execute.
diskpart.exe /s attach.txt
You should adjust the maximum property setting (in MiB) to be larger than any other disk. The actual VHD before being formatted will be very small – less than 100 KB. Even when formatted, it’s still only a few MBs large. As long as you do not consume more space than remaining on the host disk or attempt to use differencing disks that parent this dynamic disk, you will not exhaust disk space.
After this step is complete, simply install your product packages and check the attached virtual disk partition (V: in this example) for unexpected directories or files. This is a good indication that files are installing under TARGETDIR without being redirected using either standard Windows Installer directories like ProgramFilesFolder or custom actions to set directories, though it’s best to avoid type 35 custom actions.
This is also evident in a verbose installation log, as you can see below.
MSI (c) (7C:FC) [03:01:02:363]: PROPERTY CHANGE: Adding ROOTDRIVE property. Its value is 'V:\'. ... Action start 3:01:02: CostFinalize. ... MSI (c) (7C:FC) [03:01:02:394]: PROPERTY CHANGE: Adding TARGETDIR property. Its value is 'V:\'. MSI (c) (7C:FC) [03:01:02:394]: PROPERTY CHANGE: Adding PFiles property. Its value is 'V:\Program Files\'. MSI (c) (7C:FC) [03:01:02:394]: PROPERTY CHANGE: Adding ManufacturerDir property. Its value is 'V:\Program Files\Heath Stewart\'. MSI (c) (7C:FC) [03:01:02:394]: PROPERTY CHANGE: Adding InstallDir property. Its value is 'V:\Program Files\Heath Stewart\Bad Product\'. MSI (c) (7C:FC) [03:01:02:394]: PROPERTY CHANGE: Adding ManufacturerFolder property. Its value is 'C:\Program Files (x86)\Heath Stewart\'. MSI (c) (7C:FC) [03:01:02:394]: PROPERTY CHANGE: Adding INSTALLDIR property. Its value is 'C:\Program Files (x86)\Heath Stewart\Bad Product\'. ... Action ended 3:01:02: CostFinalize. Return value 1.
If you find that errant files exist on the larger virtual disk, check the installation source for common problems like creating a directory named “Program Files” with a non-standard identifier like “PFiles”. If no custom actions otherwise set this directory, also check that it is a secure public property and set by default in the package. Even if you expect to pass in the directory, you should always author a default directory just in case. Users may attempt to install your package directly, or advertised installs may not be passed this directory property if triggered automatically for features and components being installed.
Attached is sample authoring and a package used to generate the log above, along with the full installation log. The package contains an example of a component that installs under ProgramFilesFolder, as well as a directory mistakenly authored as “Program Files” without actually targeting the real Program Files folder (which may not even be named “Program Files”).
Before recycling the machine, be sure to detach the virtual disk which can also be scripted through diskpart.exe containing the following example commands.
select vdisk file=c:\test.vhd detach vdisk
Is it possible to analyze the msi file itself to find this issues?
Peter, to some degree. We have a custom ICE that looks to see if all directories are either standard or paired with a type 51 CA (we warn on type 35 CAs), but it's just a warning. You would also need to analyze the AppSearch table, but what about binary CAs? They might also set directories if scheduled before CostFinalize (after CostFinalize, they have to force a recosting which can mean some components don't get updated). Ultimately, testing the Windows Installer engine (rather than trying to rewrite it, which is especially hard when you don't have the source) through installation is the best approach.