Sajo Jacob

I am back blogging, almost 2 years after I decided to abandon my good ol C/C++ blog, so here goes my ramblings...
in
Disk Cost Issue with ReserveCost!

This was an interestingly annoying issue I came across with Windows Installer, when you use the ReserveCost Element to specify the disk space by populating the disk cost for your components in the ReserveCost Table.

 

Here is the scenario:

1) You have a ReserveCost Element for your component which specifies the amount of disk space needed for the component.

2) You have conditions around the components which decide if the component is installed on the target machine.

 

So something like this….

<Component Id="MyCustomComponent" Guid="A2DCFC09-0651-4E2C-04A4-B18F759F9F41">
  <Condition>
    <![CDATA[MYCONDITION = "1"]]>
  </Condition>
  <File Id="MYCAB.CAB" Name="MYCAB.CAB" Source="$(var.CustomPath)MYCAB.CAB"  Compressed="no" 
  KeyPath="yes" DiskId="1" />
  <ReserveCost Directory="CABDIR" RunLocal="300000" RunFromSource="0" Id="CABID" />
</Component>
 
3) And then you have checks as follows in the “Next” or “Install” button
to prevent the installation from continuing if the target machine does not have
sufficient space
 
<Publish Event="SpawnDialog" Value="OutOfDiskDlg"><![CDATA[(OutOfDiskSpace = 1 AND 
OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")]]></Publish>
 

You would expect to see the assigned space of 300000, in this case in the Space requirements information section by subscribing to the “SelectionSize” event

<Subscribe Event="SelectionSize" Attribute="Text" />
 

And also expect that the installer will not proceed with the installation on the target machine with insufficient space and will trigger the “OutOfDiskDlg” dialog which you just wired up.

 

But unfortunately even though your ReserveCost table was populated correctly, Windows Installer decides to go the safe route of not including the size (ReserveCost size) of any components which have a condition around them and the total space needed for the component(and Product) is not correct anymore (even if the conditions which they are dependant on have already being determined to be true and the component will be installed).

 

The fix for this would be to include a dummy component with the ReserveCost element with no conditions under the same feature (and ofcourse with no File attribute).

CustomActionData in WiX with Deferred Custom Actions
Here is a major gotcha that you will encounter when you try to configure your product with 
deferred actions using configurable properties during the installation.
 
Deferred actions do NOT have access to the MSI session objects which means that if you try 
to get the value of a property set by the installer like:

 

Value = Session.Property( "Property1" )

 

in a VBScript custom action which is deferred, It will return null. Yes, you just lost the

original value of Property1 during deferred custom actions.

Here is where CustomActionData comes to the rescue, let’s see how this works
 
Your deferred Custom Action:
 

<CustomAction Id=" MYCustomDeferredAction " Return="check" Execute="deferred"

BinaryKey="MYVBSCRIPT" VBScriptCall=" MyVBScriptMethod" />
 
Now let’s create a new custom action which will run as immediate: 
 
<!--Note I am passing 2 values delimted by a ‘,’ in this example-->
<CustomAction Id=" SetCustomActionDataValue " Return="check" Property="MYCustomDeferredAction" 
Value="&quot;[Value1]&quot;,&quot;[Value2]&quot;" />

And in the InstallExecuteSequence place the CustomActionData custom action before the
 actual custom action is executed:

<Custom Action="SetCustomActionDataValue" After="CAction1">
    <![CDATA[ ( Place any conditions here…)]]>
</Custom>

<Custom Action=" MYCustomDeferredAction " After=" SetCustomActionDataValue ">
    <![CDATA[ ( Place any conditions here…)]]>
</Custom>

 
And now in the VBScript just do a split to parse out the 2 values from the CustomActionData
and you are done!

' VBScript source code

Delim = ","

strArgs = Session.Property("CustomActionData")

Args = Split( strArgs, Delim )

Value1 = Args(0)

Value2= Args(1)

How to print EULA from WiX

Again there is absolutely no documentation out there on this, WiX provides out of the box support for doing this though. Just define a custom action into wixca.dll with entry point PrintEula

 

<CustomAction Id="PrintEula" BinaryKey="wixca" DllEntry="PrintEula" Return="ignore" Execute="immediate" />

 

And on the LicenseAgreement dialog, define the EULA in a ScrollableText control with ID “LicenseText", this is how WiX knows what text to print.

 

<Control Id="LicenseText" Type="ScrollableText" X="201" Y="62" Width="254" Height="66" Sunken="yes" TabSkip="no">

         <Text SourceFile="..\License.rtf" />

</Control>

 

Simple!

How to customize the install directory dynamically in WiX?

This is one of those elusive topics whose answer is hard to find with googling around, but you will discover that it’s pretty easy, again if only you knew the magic words.

 

Define this in your Main.wxs:

<Property Id="_BrowseProperty" Value="INSTALLDIR" />

 

<Directory Id="ProgramFilesFolder" SourceName="program files">

                        <Directory Id="INSTALLDIR" Name=".">

                                                <Directory Id="FOOBAR_DIR" Name="FOOBAR">

                                           <!-- All your components goes here…… -->

                                                < /Directory >

                          < /Directory >

< /Directory >

 

Define the Ok (pushbutton) and PathEdit controls in your Browse.wxs dialog for browsing directories to specify the install path as:

<Control Id="PathEdit" Type="PathEdit" X="84" Y="202" Width="261" Height="20" Property="_BrowseProperty" Indirect="yes" />

<Control Id="OK" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_OK]">

          <Publish Event="SetTargetPath" Value="[_BrowseProperty]"><![CDATA[1]]></Publish>

          <Publish Event="EndDialog" Value="Return"><![CDATA[1]]></Publish>

 </Control>

 

And finally wire up the events in the Next button in the Customize.wxs Dialog which spawns the Browse.wxs dialog: 

<Control Id="Next" Type="PushButton" X="330" Y="308" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">

    <Publish Event="SetTargetPath" Value="INSTALLDIR"><![CDATA[1]]></Publish>

    <Publish Event="NewDialog" Value="NextDlg"><![CDATA[1]]></Publish>

    <Subscribe Event="SelectionNoItems" Attribute="Enabled" />

</Control>

                             

So how did this really work?

So as we can see INSTALLDIR is currently setup as “C:\Program Files\” (note the parent of “INSTALLDIR” is “Program Files”) but defining the INSTALLDIR with a Name attribute = “.” simply ignores the root parent folder if it finds INSTALLDIR was assigned a different value which we do in Browse.wxs dialog and uses the newly assigned install path.

 

 

I have no clue!

 

 

You will know why I chose that caption by the time you reach the end of this post.

So this week turned out to be an "interesting" week, besides getting bashed badly at Foos by my colleagues, something blew my fuse off and I didn’t know what really hit me for a little bit….

 

Like most outlook users/consultants, I try to take extra care before sending out emails, do the usual proof reading, double check the attachments and recipients. Besides that I also have a rule setup on my Outlook to defer my outgoing mail by 2 minutes so that I have enough time to catch last minute errors and avoid any possible embarrassment...

Well sadly none of those practices helped in this case.

Before I go any further, for those of who don't know how to defer your emails, here is how to do it

 

Tools>Rules and Alerts > New Rule

 

1 

 

Click Next...

2

 

Click on "a number of" and select the number of minutes by which you want to defer your outgoing mail...

3

 

So this basically keeps your outgoing mail in the Outbox for 2 minutes before sending it. It does help!

 

I always liked the Outlook-SharePoint integration with shared documents since you can preview the shared documents on your SharePoint site right from your Outlook and you don't have to download different versions as someone makes changes to it.

I found this feature to be very useful during the first couple of weeks after a new project is kicked off and the project docs are frequently updated.

 

What went wrong with the Outlook-SharePoint Discussion board integration?

For those of whom haven't used this feature yet, here is couple of ways you can connect to your SharePoint discussion board with Outlook.

 

1) Directly from your sharepoint site: Go to the discussion board and click on Actions and select "Connect to Outlook"

5

2) If you have subscribed to e-mail notifications by using the "Alert Me" option above then you will find the following option to connect to the discussion board as a header on your notification email in Outlook

4

 

Now if I were to say that an email correspondence (infact a personal email) became a discussion post on our SharePoint site sending out notifications with the email content to everyone in the office who subscribed to the alerts without my knowledge, Would that make it any interesting? Now I seem to have an audience here…

You always find comfort in the statement "It could have been worse"...I totally agree!

 

How did this happen?

Not quite sure, but here is one possibility:

 

Once connected to your Outlook, the sharepoint discussion folders appears under the root Sharepoint lists. Drag and drop of messages from your inbox to the sharepoint discussion folder will automatically create SharePoint Discussion posts without any prompts.

Scary part is: I am 95% confident I didn't do a drag and drop!

 

WiX tools, Logging and Silent installation

Some useful WiX tools and Windows installer notes:

 

WiXEdit

1

WiXEdit is a decent WiX editor (open source). I used it extensively during the initial UI design phases, it works for most purposes but don’t expect it to be very intuitive like for example when it comes to Attribute on controls its shows all possible attributes on right clicking the control instead of showing only the applicable ones.

Votive (the visual studio plugin)

You can use Votive to use Visual Studio to build your WiX code.

Remember you won’t be able to see the dialogs in design mode if you are using Votive. Only advantage here would be you could use visual studio’s intellisense for the WiX code after you copy the WiX XSD’s from WiX\doc  to the following directory: C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas and ofcourse you could integrate it with your project or as a separate build process

 

I personally use a batch file to build my WiX project mainly because it’s easier to point to different releases of WiX binaries without having to install Votive each time and also with some custom actions you might want to perform when building your project.

 

Orca Editor

2

I use this tool for peaking into the msi files, it also lets you edit the .msi or.msm file directly.

To install and run the Orca editor, follow these steps:

a.

Download the Windows Installer SDK samples, tools, and documentation.

b.

Install the Orca editor by double-clicking the Orca.msi file in the \Microsoft SDK\bin folder.

Tallow

This tool is used to generate WiX fragments by harvesting a directory with WiX2.0

3

 Tallow - d c:/My_Custom_Dir/tools >> Output.wxs

Heat

Similar functionality as in Tallow but Heat is optimized for working with WiX 3.0.

4

Heat dir c:/My_Custom_Dir/tools -out output.wxs

 

Keep in mind that both Heat and Tallow are designed to allow a setup author to generate their setup authoring very quickly for the first time after which authoring should be done manually to make sure that guids, identifier names remain the same and component rules aren’t broken

Dark

Dark is used to reverse engineer an MSI into a WiX source file

5

Dark Package.msi DarkedOutput.wxs

 

 

Install your installation package with logging:

msiexec /i "Package.msi" /l*v "d:\place for logs\Install.log"

 

 

Silent Install with selective feature installation and logging:

msiexec /i “Package.msi" /q ADDLOCAL=Feature1,Feature3 /l*v Install.log

Use the msiexec switch /q for silent installation

You can specify properties on the command line with PROPERTYNAME=Value

For a selection of features use ADDLOCAL= Feature ID (assigned in WiX code)

You can also control the install directory by passing it as a parameter. INSTALLDIR=C:\My_Custom_Dir

 

WiX and Databases

Since WiX is still fairly new and is an Open Source project, don’t be surprised if you hear a lot of noise about the little or no documentation available .

In this and the coming few posts I am going to start posting some of the challenges/ roadblocks I’ve hit and the workarounds or solutions I could find.

 

WiX and Databases

Let’s look at how to perform some simple tasks with databases first and then try the more complex ones.

First thing to get started would be to add a WiX SQLExtension and UtilExtension. To use any extension with WiX you will have to include a reference to the schema within the WiX element.

 xmlns:sql="http://schemas.microsoft.com/wix/SqlExtension" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"

 

If you are using Votive the Visual Studio plugin to build your project then make sure you add references to WixSqlExtension.dll and WixUtilExtension.dll from your WiX binaries directory. If you are using a batch file (like I do) for building your project then pass these extensions to both Candle and Light i.e. -ext WixSqlExtension -ext WixUtilExtension

 

Create a SQL User:

 <util:User Id="MySQLUser" Name ="[SQLUser]" Password="[SQLPassword]"/>

 

Create a SQL Database:

<sql:SqlDatabase Id="MySqlDatabase" Database="MyDatabase" Server="[SQLSERVER]" Instance="SQLEXPRESS"   CreateOnInstall="yes" DropOnUninstall="yes" User="MySQLUser" ContinueOnError="yes"/>

 

Create a SQL Database and run some custom sqlscripts:

<sql:SqlDatabase Id="MySqlDatabase" Database="MyDatabase" Server="[SQLSERVER]" Instance="SQLEXPRESS"   CreateOnInstall="yes" DropOnUninstall="yes" User="MySQLUser" ContinueOnError="yes"/>

        <sql:SqlScript Id="MySQLScript1" ContinueOnError="no" ExecuteOnReinstall="yes" ExecuteOnInstall="yes" BinaryKey="ScriptBin1"/>        <sql:SqlScript Id="MySQLScript2" ContinueOnError="no" ExecuteOnUninstall="yes" ExecuteOnReinstall="no" ExecuteOnInstall="no" BinaryKey=" ScriptBin2"/> 

</sql:SqlDatabase>

 Create a Database and specify where to place the database files on the target machine:<sql:SqlDatabase Id="MySqlDatabase" Database="MyDatabase" Server="[SQLSERVER]" Instance="SQLEXPRESS"   CreateOnInstall="yes" DropOnUninstall="yes" User="MySQLUser" ContinueOnError="yes">        <sql:SqlFileSpec Id="Mdf" Name=" MyDatabase " Filename=" ="[TARGETDIR] MyDatabase.mdf" Size="2MB" GrowthSize="2MB"/>        <sql:SqlLogFileSpec Id="Ldf"  Name=" MyDatabase _log"  Filename="[TARGETDIR] MyDatabase _log.ldf" /></sql:SqlDatabase>

 

Restore a database from an mdf/ldf from WiX

Recently I had to restore a database from an mdf/ldf from WiX, sounds pretty straightforward right? WiX actually does pretty well to make it straightforward (only if you knew the magic words).

Since I spent a few man hours on a workaround with what I had and did manage to avoid using VBScripts, I am going to post that here too; here is what I did:

So all we know until now is how to create a new database and run custom sql scripts, what we need to do is to create a database at a user specified location and restore the database from a mdf/ldf which is on the CD(final product).

You can create the database at the user specified location as shown above. You can also move the mdf/ldf to a location that the user desires.  The problem comes when you want to do an attach command from a sql script. How does your sql script know where the mdf/ldf were moved to?

 

1) To begin with,  I manually created a .bak file from the mdf/ldf and copied that to the project folder.

 

2)  Then I went ahead and created a new database from WiX at the location where the user desired and also specified where to place the mdf/ldf on the target machine (and the names) as shown in the last example above.

 

3) And then finally ran this sql script from WiX to search for the physical location (filename) of the mdf/ldf that was created from WiX and then replace them using a Restore command.

 

/*

Written by: Sajo Jacob. Clarity Consulting. Lasted Modified: November 08, 2007

NOTE: This script depends on the .BAK file generated from the MDF

*/

CREATE PROCEDURE RestoreDB

as

DECLARE @restoredb varchar(1024)

DECLARE @path varchar(1024) 

BEGIN

SET @path= MyDatabaseBak'

SET @restoredb = 'RESTORE DATABASE MySqlDatabase' + ' FROM DISK = ' + '''' + @path + ''''

+ ' WITH REPLACE,RECOVERY,MOVE ' + ''' MyDatabase ''' +

'  TO ' + '''' + ( select top 1 filename from master.dbo.sysaltfiles where name = ' MyDatabase') + '''' +

', MOVE ' + ''' MyDatabase_Log''' +

' TO ' + '''' + ( select top 1 filename from master.dbo.sysaltfiles where name = ' MyDatabase_log') + '''' 

EXEC (@restoredb)

END

 

WiX support for doing something like this is by letting you write Inline sql statements by using the SQL attribute on SqlString element

 <sql:SqlString Id=" Attach " SqlDb="MySqlDatabase" ExecuteOnInstall ="yes" ContinueOnError="no" Sequence="1" SQL="CREATE DATABASE $(var.LongDBName) ON (FILENAME=N'[TARGETDIR]MyDatabase.mdf'), (FILENAME=N'[TARGETDIR]MyDatabase_log.ldf') FOR ATTACH" />

 

If you have a readonly mdf/ldf then a restore can result in a readonly database, so assuming that is the case here, let’s perform a set operation after the restore. Sequence attribute here helps with the order of execution.

<sql:SqlString Id="ReadWriteDatabase" SqlDb=" MySqlDatabase " ExecuteOnInstall="yes" ContinueOnError="yes" Sequence="2" SQL="ALTER DATABASE $(var.LongDBName) SET READ_WRITE WITH NO_WAIT" />

 

WiX and Windows Authentication

Just take off the User attribute in the SqlDatabase element we had earlier in the database creation section and it will use Windows Authentication.

 

Windows Installer XML (WiX)
WiX is an XML based wrapper around the Windows Installer which lets you create Installer packages without you having to get your hands too dirty with the obscurities of dealing with the Windows Installer directly or for that matter if you want to cut down on the costs of licensing third party installer products, its a great way to create your custom Windows Installer at no money down. WiX is an open source project from the folks at MS which is currently maintained by Rob Mensching, the toolset itself was written in C#. Getting to speed with WiX reminded me of my C days, no it has nothing to do with the programming language construct itself (obviously), I learnt C after I learnt C++, yeah that's not fun...Learning WiX without knowing that you will need to learn the abc's of Windows Installer sooner or later is very common pitfall encountered by new "WiX'xers"(I made that up).   

 

Who really needs WiX? how good is it really? 

Simple case:

Did you just write a game in your spare time which you plan on distributing from your website? And did you want to upgrade the game when you have some extra time and wanted your game users to get your new upgrade patch. You definitely wont want to spend the same amount of time you took to create the game or more on creating the installation package(msi/msm or a msp) by directly using the Windows Installer, neither would you like to spend that extra $$ you saved up for that Hi-Def TV on a 3rd party installation tool.

Complex Cases:

WiX is used in most of the major software installations these days, Microsoft Office 2007, SQL Server 2005, Visual Studio 2005/2008 have already been "WiXed".

 

So how easy is it? Can it adopted by an application developer or is it still heavily in the Installer space?

Depends on how you look at it. Application developers from Clarity Consulting are currently working on an installation package for a major software company and we find it to be bridging the gap between application and Installation developers. The gap will seem to get narrower when we start talking about Votive the new Visual Studio plugin.

 

Let’s scrape the tip of the iceberg, how bad can Windows Installer installations really be? 

Some might say, as easy as copying the files to the target system, well that's how simple it can get.

Here is overview of how involved an installation process can be:the COM nightmare(when multiple products share the same COM server), supporting files for an installation can be as simple as dlls or as complex as pushing MSDE, installations alter the state of a target machine so patching a product or upgrading the product calls for some grey cells. Installations can often need admin privileges or should sometimes be installed for all users on the target machine, installing/starting/shutting down windows services, dealing with installation repair scenarios, running your custom code during the installation or how about having two versions of your product installed side by side without any conflicting behavior? 

That wasn’t meant to scare you off WiX, but to give you a vague idea of the scope/considerations when creating an installer package. WiX goes a long way in helping you with them, which I’ll cover in the next few posts. In the meanwhile take a look at Gábor Deák Jahn's WiX Tutorial, which is a great place to start.

 

Newer posts:

Wix and Databases

WiX tools, Logging and Silent installation

How to customize the install directory dynamically in WiX?

How to print EULA from WiX?

CustomActionData in WiX with Deferred Custom Actions

Disk Cost Issue with ReserveCost!