This blog is used as a memory dump of random thoughts and interesting facts about different things in the world of IT. If anyone finds it useful, the author will be just happy! :-)

Showing posts with label Windows Installer. Show all posts
Showing posts with label Windows Installer. Show all posts

Wednesday, September 14, 2011

Revisited: Multiple Instance installations and patches

I initially blogged about multiple instance installations couple of years ago. The way I described it worked fine for me, but the time flies and the things has changed ever since – WiX grew up to even more solid toolset, and I also gained some knowledge. So, this post is to revisit the topic and look at it through the prism of WiX 3.6.
Imagine you have an application, and you’d like to be able to install several instances of it side-by-side on a single machine. The starting point is still to author the InstanceTransforms element:
<InstanceTransforms Property="INSTANCEID">   
   <Instance Id="I01" ProductCode="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" ProductName="My Product 01"/>   
   <Instance Id="I02" ProductCode="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" ProductName="My Product 02"/>   
   <Instance Id="I03" ProductCode="{GUIDGOES-HERE-5494-843B-BC07BBC022DB}" ProductName="My Product 03"/>
    ...
</InstanceTransforms>
Obviously, the number of Instance elements is the number of instances supported by this installation program (plus the default one). In order to install the default instance, you should run the following command (assuming the generated MSI package is called MultiInstance.msi):
msiexec /i MultiInstance.msi
In order to start the installation of another instance, change the command as follows:
msiexec /i MultiInstance.msi MSINEWINSTANCE=1 TRANSFORMS=":I01"
The MSINEWINSTANCE property set to 1 instructs msiexec to start the installation of another instance instead of default one. Note that in the above example we installing the instance I01. The Instance element results into an instance transform being embedded into the MSI package, and by setting TRANSFORMS property to “:I01” we instruct msiexec to apply the embedded instance transform which corresponds to the I01 instance. The TRANSFORMS property can contain other transforms (for instance, language transforms), but that’s another topic.

Uninstalling looks quite similar, for instance, default instance uninstallation:
msiexec /x MultiInstance.msi
In order to uninstall the extra instance, you should explicitly specify its ProductCode. So, for instance I01 the uninstall command line looks like this:
msiexec /x {GUIDGOES-HERE-4731-8DAA-9E843A03D482}
So far, so good – it is quite straight-forward. Now, let’s turn to the Windows Installer documentation about multiple instances one more time. Apart from the requirement for each instance to have a unique product code and instance identifier (this is what WiX does for free with InstanceTransforms technique), it strongly recommends to keep the data isolated. For the file data, this means installing the files of each instance to a different location – the path containing instance ID as its part fits best. For the non-file data, it’s a bit more complex: the appropriate components should have different GUIDs, and again install to a different location.

In my first attempt to approach the problem, I’ve applied a workaround: generate new GUIDs for each component of new instance, embed those “component transforms” into the resulting MSI and apply along with the instance transform. Well, sounds not very efficient, but assuming a great number of components harvested automatically, this was simple enough. Fortunately, wise developers of WiX team thought this through and came up with a far more elegant solution in version 3.6.

Starting from WiX 3.6.1502.0, a Component element has an attribute MultiInstance of YesNo type. According to the WiX docs, “If this attribute is set to 'yes', a new Component/@Guid will be generated for each instance transform.” Fantastic! That’s what we need! Let’s see how it affects the multiple instance installations on a sample. Let’s say our installation program consists of the following components, and we’d like to be able to install this software at least 3 times:
<Directory Id="ProductNameFolder" Name="TestName">
   <Component Id="FileComponent" Guid="{GUIDGOES-HERE-4301-95D2-86A4C80EF5F0}">
      <File Id="dll" Source="$(var.Source)\Some.Test.dll" KeyPath="yes" />
   </Component>
   <Component Id="ConfigComponent" Guid="{GUIDGOES-HERE-4c2f-BE74-CF78D2350E48}">
      <File Id="web_config" Source="$(var.Source)\web.config" KeyPath="yes" />
   </Component>
   <Directory Id="EmptyFolderDir" Name="EmptyFolder">
      <Component Id="FolderComponent" Guid="{GUIDGOES-HERE-4543-A9F8-17491670D3A6}">
         <CreateFolder />
      </Component>
   </Directory>
   <Component Id="RegistryComponent" Guid="{GUIDGOES-HERE-45e5-ABFD-07E5CC4D7BC9}">
      <RegistryKey Id="MainRegKey" Action="createAndRemoveOnUninstall" Root="HKLM" Key="SOFTWARE\MultiInstanceTest\[ProductCode]">
         <RegistryValue Id="MainRegValue" Name="InstanceId" Value="[INSTANCEID]" Type="string" />
         <RegistryValue Id="InstallPathValue" Name="Location" Value="[ProductNameFolder]" Type="string" />
         <RegistryValue Id="ProductCodeValue" Name="ProductCode" Value="[ProductCode]" Type="string" />
         <RegistryValue Id="ProductNameValue" Name="ProductName" Value="[ProductName]" Type="string" />
         <RegistryValue Id="ProductVersionValue" Name="ProductVersion" Value="[ProductVersion]" Type="string" />
      </RegistryKey>
   </Component>
</Directory>
<InstanceTransforms Property="INSTANCEID">
   <Instance Id="I01" ProductCode="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" ProductName="My Product 01"/>
   <Instance Id="I02" ProductCode="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" ProductName="My Product 02"/>
</InstanceTransforms>
The MSDN recommendations about multiple instances are followed, except for “keeping non-file data isolated”. Let’s see how it affects the install/uninstall. Run the installation of the default and I01 instance as described above. Both instances are installed to the different locations correctly:
Instance00installedInstance00RegInstalled

Instance01installedInstance01RegInstalled

Now uninstall the default instance – you’ll see that non-file data was not removed properly:

Instance00brokenInstance00RegBroken

This is happening because the components which hold this data are considered shared by the Windows Installer, and during uninstallation of one instance it detects that there’s another one pointing to the same components and leaves those untouched. Now if you uninstall the other instance, it successfully removes both EmptyFolder and registry key, but as a result we’ll still have orphaned resources of the first instance.

That’s the initial problem, and let’s see how elegant new WiX feature deals with it. You should only add the MultiInstance=’yes’ attribute to the components holding non-file data, and forget about the problem of orphaned resources forever. Like this:
<Directory Id="ProductNameFolder" Name="TestName">
   <Component Id="FileComponent" Guid="{GUIDGOES-HERE-4301-95D2-86A4C80EF5F0}">
      <File Id="dll" Source="$(var.Source)\Some.Test.dll" KeyPath="yes" />
   </Component>
   <Component Id="ConfigComponent" Guid="{GUIDGOES-HERE-4c2f-BE74-CF78D2350E48}">
      <File Id="web_config" Source="$(var.Source)\web.config" KeyPath="yes" />
   </Component>
   <Directory Id="EmptyFolderDir" Name="EmptyFolder">
      <Component Id="FolderComponent" Guid="{GUIDGOES-HERE-4543-A9F8-17491670D3A6}" MultiInstance="yes">
         <CreateFolder />
      </Component>
   </Directory>
   <Component Id="RegistryComponent" Guid="{GUIDGOES-HERE-45e5-ABFD-07E5CC4D7BC9}" MultiInstance="yes">
      <RegistryKey Id="MainRegKey" Action="createAndRemoveOnUninstall" Root="HKLM" Key="SOFTWARE\MultiInstanceTest\[ProductCode]">
         <RegistryValue Id="MainRegValue" Name="InstanceId" Value="[INSTANCEID]" Type="string" />
         <RegistryValue Id="InstallPathValue" Name="Location" Value="[ProductNameFolder]" Type="string" />
         <RegistryValue Id="ProductCodeValue" Name="ProductCode" Value="[ProductCode]" Type="string" />
         <RegistryValue Id="ProductNameValue" Name="ProductName" Value="[ProductName]" Type="string" />
         <RegistryValue Id="ProductVersionValue" Name="ProductVersion" Value="[ProductVersion]" Type="string" />
      </RegistryKey>
   </Component>
</Directory>
Now check the above scenario once again: install 2 instances and uninstall them. You’ll see that both install correctly and uninstall clearly. Isn’t it GREAT?! Smile

Now, let’s turn to patching. Again, if we look back to my initial post on this topic, I was using an ugly method to make the patch applicable for all instances of the installed product. That method assumed opening the binary patch for read/write and rude injection into its structure. Though it worked, there’s much more elegant way of doing this. I’d like to thank Heath Stewart for the hint – here’s the full thread on wix-users mailing list.

So, the default behavior is the following: if you author the PatchBaseline element with its default validation settings, the patch will be applicable to the default instance only. That’s because it tracks the ProductCode is the product baseline it was built against, and checks it during install time. The trick is to add a Validate child to the PatchBaseline, and instruct it not to check the ProductCode:
<Media Id="5000" Cabinet="RTM.cab">
   <PatchBaseline Id="RTM">
      <Validate ProductId="no" />
   </PatchBaseline>
</Media>
So, after you build this patch, you’ll be able to apply it to a particular instance:
msiexec /i {GUIDGOES-HERE-4412-9BC2-17DAFFB00D20} PATCH=patch.msp /l*v patch.log
Or to all the installed instances at once (so-called “double-click scenario”):
msiexec.exe /p patch.msp /l*vx patch.log
There’s still one more obvious inconvenience in the patch authoring, as for me. You have to specify the ProductCode entries twice: in the main installation sources (InstanceTransform/@ProductCode) and in the patch sources (TargetProductCode/@Id). It would be just fantastic if during patch building the WiX tools could look into the instance transforms collection of the baseline package and take the list of product codes out of there. That would omit the necessity to always specify the following section in the patch:
<TargetProductCodes Replace="no">
    <TargetProductCode Id="{GUIDGOES-HERE-4412-9BC2-17DAFFB00D20}" />
    <TargetProductCode Id="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" />
    <TargetProductCode Id="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" />
</TargetProductCodes>
As usual, WiX Toolset developers have done and keep doing fantastic job making our lives as setup developers easier!

Feel free to leave a comment in case you have a note or a question. Feedback is welcome, as usual!

Friday, May 7, 2010

Torch.exe confuses the language validation and ProductCode validation

This week I faced with another issue with torch.exe. As you might know, there’s a “type” option (-t) to apply a predefined set of validation flags to the generated transform. If you’d like to generate a language transform, you should use “-t language”. It should suppress all the errors plus validate that language in both MSI packages corresponds. But it doesn’t…

The reason is just a simple bug in the tool. When you set “-t language” in the command line, this option is mapped to the TransformFlags.LanguageTransformDefault value. It is a combination of atomic values (those you can set via –serr and -val), and it mistakenly takes “validate product code” bit instead of “validate language bit”. I’ve never noticed this unless my installation uses both instance transforms and language transforms.

The workaround is quite simple: use literally “–serr” and “–val” to achieve the same result. For instance, for language transform it should be:

       torch.exe … –serr a –serr b –serr c –serr d –serr e –serr f –val l …

[By the way, does it look too long just for me? I would prefer –serr abcdef :-)]

I’ve also filed an issue to the WiX toolset. Hope this can help somebody.

Wednesday, April 14, 2010

Torch.exe throws scary error message unrelated to the real problem

Today I’ve been working on the localization of my installation project, and I had to create a number of language transforms. The following simple call of torch.exe

            torch -t language setup.msi setup_ru-ru.msi -out mst\ru-ru.mst

returned the scary error message:

            error TRCH0001 : The Windows Installer service failed to start. Contact your support personnel

I’ve seen this kind of errors a couple of times, and it was a serious problem with Windows Installer engine on the target machine in all cases. Once, it indicated that Windows Installer service is completely broken, and only OS reinstall helped (fortunately, it was virtual PC)… But mighty Google gave a single, but exact hint. It is just a single line, and one can miss the point since that’s another problem which is discussed there.

So, the actual problem: if –out switch points to a folder which doesn’t exist (‘mst’ in this case), torch.exe can’t create it and returns the error. That’s okay behavior to live with, but the error message should be changed to something more appropriate: “The folder ‘mst’ can’t be found. Make sure it exists before referencing in –out switch”. I’ve also created an issue to the WiX inbox at sourceforge.net.

Hope this info is helpful until the message text is fixed.

Monday, February 2, 2009

Extended logging options in WiX custom actions

The best and maybe the only way to find out what's going wrong with the installation is investigating the MSI log file. Fortunately, the Windows Installer respects log writing very much. You can find the ways to enable logging and different logging options here.

The verbose log contains all the information MSI can generate. Though it is really useful when it comes to troubleshooting the failed installation, it is quite hard to read, especially for newbies. Again, fortunately, there's a super chapter "Using the Windows Installer log" in a super book called "The Definitive Guide to Windows Installer" by Phil Wilson, which guides you through the basics of log file reading and understanding.

I used to generate the log file with /L*v options, which means verbose. But, if you use WiX custom actions, it turns out that you can make them logging even more verbose.

If you browse the WiX source code, you can find the lines like this in its custom actions:

WcaLog(LOGMSG_STANDARD, "Error: Cannot locate User.User='%S'", wzUser);

The first argument is a logging level. It can be 
  • LOGMSG_STANDARD, which is "write to log whenever informational logging is enabled", which in most cases means "always"
  • LOGMSG_TRACEONLY, which is "write to log if this WiX build is a DEBUG build" (is often used internally to dump CustomActionData contents)
  • LOGMSG_VERBOSE, which is "write to log when LOGVERBOSE"
Wait a minute, what does the last option means? I've already set the verbose logging by /L*v, but I don't see more entries there. Here is the algorithm WiX CA use to know whether to write a log entry marked as LOGMSG_VERBOSE level:
  • Check if the LOGVERBOSE property is set (can be set in the command-line since it is public)
  • Otherwise, check if the MsiLogging property is set (MSI 4.0+)
  • Otherwise, check the logging policy in the registry

So, the following is the easiest way in my opinion to make your MSI (WiX-based) log file even more verbose:

   msiexec /i package.msi ... LOGVERBOSE=1 /L*v install.log

Hope this helps.

P.S. This is just a brief extract of what's there in the source code. As usual, code is the best documentation ;-)

Tuesday, January 20, 2009

IIS extension: WebAppPool

Another challenge - another piece of fun with WiX. Imagine the following requirement: the installation program must install an application pool on IIS6+ environments; the multiple installed instances should use the same application pool. In other words, the application pool must be created with the first instance installation, and must be removed with the last instance uninstallation. 

A special element for maintaining IIS AppPools in IIS extension is called WebAppPool. As usual, we'll wrap it into a separate component, so that it is created on install. Later, we'll create a special custom action to deceive the standard removing mechanism on uninstall:

      <Component DiskId="1" Id="CreateIISAppPool" Guid="{YOURGUID-6C5B-4980-AD0B-E32FA2DBC1F4}" Directory="WebsiteFolder">
         <Condition>IISMAJORVERSION <> "#5"</Condition>
         <iis:WebAppPool Id="IISSiteAppPool6" Name="[IISAPPPOOL_NAME]" MaxWorkerProcesses="1" Identity="networkService" />
         <RegistryKey Root="HKLM" Key="$(var.ParentKey)">
            <RegistryValue Name="IISAppPoolName" Type="string" Value="[IISAPPPOOL_NAME]"/>
         </RegistryKey>
      </Component>

As you can see, the component is installed once the target system has IIS 6+. It creates a WebAppPool with the name provided in IISAPPPOOL_NAME public property. It also writes this name into a registry value, which resides under the instance-specific registry key. 
With this component included into the MSI package, the app pool is created when the first instance is installed, and nothing happens for second and subsequent instances. 

Let's examine the uninstall behavior. The MSI behaves natural - when it meets the component to uninstall, it removes the WebAppPool specified in it. But the IIS extension which performs the actual deletion of app pool, needs the name to be passed in it. So, the only thing we should do is to supply this action with a fake app pool name each time, except for the last instance uninstall.

Here is the algorithm:
  1. search the registry for the app pool name as usual
  2. schedule a special action on unistall after AppSearch, which detects if this is the last instance being uninstalled, and if not, "breaks" the app pool name into something non-existent
The first point is quite straight-forward:

      <Property Id="IISAPPPOOL_NAME">
         <RegistrySearch Id="IISAppPoolName" Root="HKLM" Key="$(var.ParentKey)" Name="IISAppPoolName" Type="raw" />
      </Property>

The second one is not natural, like any hack:

      [CustomAction]
      public static ActionResult ChangeWebAppPoolNameToDeceiveUninstall(Session session)
      {
         int numberOfInstalled = 1;
         foreach (ProductInstallation product in ProductInstallation.GetRelatedProducts(session["UpgradeCode"]))
         {
            if ((session["ProductCode"] != product.ProductCode) && product.IsInstalled)
            {
               numberOfInstalled++;
               break;
            }
         }

         if (numberOfInstalled > 1)
         {
            session["IISAPPPOOL_NAME"] += string.Format("|{0}", DateTime.Now.ToLongTimeString());
         }

         return ActionResult.Success;
      }

It iterates the related products (those sharing the UpgradeCode), and if it finds others installed, except for itself, it changes the app pool name we retrieved from registry into something unique, for instance, appends a unique string.

Thus, the IIS custom action which is going to delete the app pool fails to find the one with the provided name, and does nothing. When, otherwise, it is the last instance being uninstalled, the retrieved app pool name remains unchanged, and the app pool is successfully removed.

Note that the mentioned action should be immediate, should occur after AppSearch on uninstall.

That's it! I would appreciate any comments as usual.

Monday, January 19, 2009

IIS extension: WebSite

Ok, it's time for another portion of the installation fun, now about the IIS web sites.

The IIS extension in WiX is probably the most tricky and unobvious. That's my personal impression, of course. But, anyway, it gives you an option to tweak any property of a website, virtual directory or web directory. 

When installing a web application on Windows XP and thus IIS 5.1, it is natural to create an "ad hoc" virtual directory during install and remove it on uninstall. That's basically quite common case, but what if the application requires to reside under the site root directly, not virtual directory? 

In this case the root of the Default Web Site should just be switched to the installation directory - nothing is created on install and nothing is removed on uninstall. Let's see how this can be done with WiX IIS extension.

The iis:WebSite element has two "modes": if it resides under Component element, it is created during install, otherwise it is there just for reference from other elements. Fortunately, it has a special attribute ConfigureIfExists. Setting it to 'yes' avoids an attempt to create a new site, configuring the existent one instead:

      <Component DiskId="1" Id="ModifyIISSite5" Guid="{YOURGUID-2023-4D19-90D2-EE9101C71E44}" Directory="WebsiteFolder" Permanent="yes">
         <Condition>IISMAJORVERSION = "#5"</Condition>
         <iis:WebSite Id="IISSite5" Description="[IISSITE_NAME]" Directory="WebsiteFolder" ConfigureIfExists="yes">
            <iis:WebAddress Id="IISSiteAddress5" Port="[IISSITE_PORT]"/>
         </iis:WebSite>
      </Component>

Note, that in this case you should make sure you specified the existent website data. The website is uniquely identified by the description, port and header. The first is an attribute of a WebSite element itself, others belong to the child mandatory element WebAddress. 

The previous snippet highlights another attribute as bold - Permanent="yes". It makes the hosting component permanent, thus preventing it from being deleted on uninstall. Internally, the Windows Installer engine just keeps an extra reference to this component forever, thus it reference count is never equal to 0.

One last thing I'd like to point your attention to is a component condition. It uses the property called IISMAJORVERSION. This property, as well as another one called IISMINORVERSION, is brought by the IIS extension. They are populated from the target system registry during the AppSearch action. Before using them in your authoring make sure you add a couple of references:

    <PropertyRef Id="IISMAJORVERSION"/>
    <PropertyRef Id="IISMINORVERSION"/>

That's it! As usual, any comments are highly appreciated.

Saturday, January 10, 2009

Attach / Detach database during installation

It seems I have finally managed to implement full database support in my installation program. And it also seems that I stepped on every rake one could imagine in this area. But, the harder the battle, the sweeter the victory.

I had the following requirements: the application is distributed with the MDF/LDF files, which must be attached during installation and detached during uninstallation. Both Windows and SQL authentication must be supported.

Fortunately, the kind WiX developers implemented a wonderful SQL extension. So, let's take advantage of the sql:SqlDatabase element. The documentation says, it can be placed either under Component, or under Fragment/Module/Product. In the first case the database will always be created when the component is being installed. This doesn't suite our needs with attach, so let's stick with another option:

<fragment>
...
<sql:sqldatabase id="SqlMasterDBWinAuth" server="[SQL_SERVER]" database="master"/>
...
</fragment>

As you can see, we specify the standard Master database in this element. That's because the database must exist on the target computer by the moment Windows Installer tries to connect. This syntax will instruct the custom action to open the connection using currently logged-on Windows account.

The next step is to provide the appropriate sql:String elements for attach/detach. It is better to put these elements inside the component which installs MDF/LDF files, but this is not the rule. And if you have different conditions for installing the files and running attach, you'll have to move the scripts into a separate component.

<Component DiskId="1" Id="MSSQLCore" Guid="YOURGUID-4E94-4B28-B995-DCBFD50B9F07">
<Condition>YOUR CONDITION GOES HERE</Condition>
<File Id="MSSQLCoreFile" Name="$(var.CoreFileName)" KeyPath="yes" />
<File Id="MSSQLCoreLogFile" Name="$(var.CoreFileLogName)" />

<sql:SqlString Id="DetachCore" Sequence="1" ContinueOnError="yes" ExecuteOnUninstall="yes" SqlDb="SqlMasterDBWinAuth" SQL="EXEC master.dbo.sp_detach_db @dbname = N'[INSTANCENAME]Core', @skipchecks=N'true'"/>

<sql:SqlString Id="AttachCore" Sequence="2" ContinueOnError="no" ExecuteOnInstall="yes" SqlDb="SqlMasterDBWinAuth" SQL="CREATE DATABASE [\[][INSTANCENAME]Core[\]] ON ( FILENAME = N'[DB_FOLDER]$(var.CoreFileName)' ), ( FILENAME = N'[DB_FOLDER]$(var.CoreFileLogName)' ) FOR ATTACH"/>

</Component>

At this point I should mention one reef. An SqlString string element also has an attribute SQLUser. If you provide both SqlDb attribute, pointing to the "WinAuth" definition of the database, and SqlUser attribute, pointing to the "sa user", it will lead to unpredictable and very strange behavior. I would avoid this.

Ok, now we should take care about the rollback actions: during install and uninstall correspondently. It is obvious that RollbackOnInstall should detach databases, if they got installed before failure, and RollbackOnUnistall should attach the databases back, if the failure occurred during uninstall.

Thanks to the hint of Rob Mensching in one of his replies to the WiX mailinglist, I managed to overcome another trick. Right after the database is attached, there is sometimes a connection left to this database. I can see this by opening the SQL Management studio and looking at the database status (Normal). If you detach the database in this moment, it flushes the permissions on a physical file to a logon account only. I didn't dig very deep into this, it probably corresponds to the rules of permissions change during attach/detach. As a result, the windows installer can't access the file afterwards, and the uninstallation is rolled back.

To fix this, perform "SET OFFLINE" query before detaching the database and you'll never face with this behavior again.

Thus, the final version will look similar to this:

<Component DiskId="1" Id="MSSQLCore" Guid="YOURGUID-4E94-4B28-B995-DCBFD50B9F07">
<Condition>YOUR CONDITION GOES HERE</Condition>
<File Id="MSSQLCoreFile" Name="$(var.CoreFileName)" KeyPath="yes" />
<File Id="MSSQLCoreLogFile" Name="$(var.CoreFileLogName)" />

<sql:SqlString Id="RollbackDetachCore" Sequence="1" ContinueOnError="yes" RollbackOnUninstall="yes" SqlDb="SqlMasterDBWinAuth" SQL="CREATE DATABASE [\[][INSTANCENAME]Core[\]] ON ( FILENAME = N'[DB_FOLDER]$(var.CoreFileName)' ), ( FILENAME = N'[DB_FOLDER]$(var.CoreFileLogName)' ) FOR ATTACH"/>
<sql:SqlString Id="OfflineCoreDatabase" Sequence="2" ContinueOnError="yes" ExecuteOnUninstall="yes" SqlDb="SqlMasterDBWinAuth" SQL="ALTER DATABASE [\[][INSTANCENAME]Core[\]] SET OFFLINE WITH ROLLBACK IMMEDIATE" />
<sql:SqlString Id="DetachCore" Sequence="3" ContinueOnError="yes" ExecuteOnUninstall="yes" SqlDb="SqlMasterDBWinAuth" SQL="EXEC master.dbo.sp_detach_db @dbname = N'[INSTANCENAME]Core', @skipchecks=N'true'"/>
<sql:SqlString Id="RollbackAttachCore" Sequence="4" ContinueOnError="yes" RollbackOnInstall="yes" SqlDb="SqlMasterDBWinAuth" SQL="EXEC master.dbo.sp_detach_db @dbname = N'[INSTANCENAME]Core', @skipchecks=N'true'"/>
<sql:SqlString Id="AttachCore" Sequence="5" ContinueOnError="no" ExecuteOnInstall="yes" SqlDb="SqlMasterDBWinAuth" SQL="CREATE DATABASE [\[][INSTANCENAME]Core[\]] ON ( FILENAME = N'[DB_FOLDER]$(var.CoreFileName)' ), ( FILENAME = N'[DB_FOLDER]$(var.CoreFileLogName)' ) FOR ATTACH"/>

</Component>

Ok, but what about Sql Authentication? Well, this requires some kind of duplicating the code. The SqlDb attribute of the SqlString element can't accept MSI properties, thus can't be dinamically changed during runtime. We must author another element SqlDatabase for referencing it from another set of scripts.

<util:User Id="SQLUser" Name="[SC_SQL_SERVER_USER]" Password="[SC_SQL_SERVER_PASSWORD]"/>
<sql:SqlDatabase Id="SqlMasterDBWinAuth" Server="[SC_SQL_SERVER]" Database="master" />
<sql:SqlDatabase Id="SqlMasterDBSqlAuth" Server="[SC_SQL_SERVER]" Database="master" User="SQLUser" />

The first element defines a user to connect to the database. In this example, the username and password are read from the public properties. The user is not created, it is just referenced. The second element should be familiar - it was described above. And the last one differs only in one attribute - SQLUser.
This does the trick: if you want Windows Authentication way to use, reference SqlMasterDBWinAuth in your scripts, otherwise - use SqlMasterDBWinAuth. Obviously, you need another set of the similar SqlString elements in a different component.

Tired? The last thing.

If you implemented something similar to what I've described, you should have mentioned that in case of Sql Auth the database is attached as read-only. This happens because the SQL service account (NETWORK SERVICE in my case) doesn't have enough permissions to the [DB_FOLDER] and files by the moment attach starts.
No problem, let's assign the necessary rights. Put the following snippet into your component which contains the SqlAuth scripts:

<CreateFolder>
<util:PermissionEx GenericAll="yes" User="NetworkService" />
</CreateFolder>

Note: Don't forget to reference WIX_ACCOUNT_NETWORKSERVICE property.

But, wait, the ShedSecureObjects is scheduled after the InstallSqlData, this doesn't help!
Right, the sequence should also be changed like this:

<InstallExecureSequence>
...
<Custom Action="InstallSqlData" After="SchedSecureObjects">NOT SKIPINSTALLSQLDATA AND VersionNT > 400</Custom>
...
</InstallExecuteSequence>

That's it! I know, this can't seem easy at first glance, but, as for me, it is much more controlled and customizable, than with InstallShield. I might be wrong, though.

Good luck! I would appreciate any comments and notes to this.

Tuesday, December 30, 2008

Multiple instance installations and patching

Well, this is the first message to my first weblog, and it should be outstanding by default. :-)

Sometimes, when creating an installation program, it is necessary to support multiple instance installations of the product to one particular computer. This requirement immediately brings the complexity of the installation to the advanced level. A couple of most tricky things to look after are component rules and patching.

In order to make your installation "multi-instance-aware", you should author a number of instance transforms in your source. The number of transforms is the number of instances you plan to support (except for the default one, of course). Fortunately, WiX provides very convenient way to do this:

   <instancetransforms property="ANY_PROPERTY">
      <instance id="InstanceId1" productcode="{42A33A91-36B0-4700-A6F5-1289D22F358C}"/>
      <instance id="InstanceId2" productcode="{68C62C01-D064-4CF0-9239-F5D2FF36BD9A}"/>
      ...
   </instancetransforms>

As always with Windows Installer, this is not the end. According to the MSI documentation about authoring multiple instances, "To keep the nonfile data of each instance isolated, the base package should collect nonfile data into sets of components for each instance". This can be done by authoring a duplicate of each component for each instance, and install conditionally. But it becomes really complex to manage when you have much more than 2 instances, let's say, 100. 

I chose another way. Assuming the fact that each instance should contain the same set of components, but with different GUIDs, we can generate a number of transforms, one per each instance, which change just the GUIDs of all the components. So, the algorithm is similar to this:
  • copy the MSI package
  • use API to query and update the database with new GUIDs for each component
  • generate a transform between the copy and original MSI
  • drop the copy MSI
  • repeat the steps about the number of times as many instances you plan to support
  • embed all these transforms into the original MSI
Obviously, the number of such customization transforms must be equal to the number of instance transforms and the names should be convenient to use. For instance, if you did everything correctly, you should be able to run the installation of new instance as follows:

msiexec /i YourPackage.msi MSINEWINSTANCE=1 TRANSFORMS=:InstanceId1;:ComponentGUIDTransform1.mst ...

This works like a charm in conjunction with an algorithm to detect next available instance and a bootstrapper. 

Now let's turn to the patching. When I browsed the internet for the info about multiple instance installs and patches, I found a great post of Christopher Painter. As he says there, one should populate the Targets property of the patch summary info stream with product codes of all the instances, otherwise the patch detects just the default instance. That's correct, but, yes, this is not the end of the story.

Let's take a look at the patch definition and its contents: "Patches contain at a minimum, two database transforms and can contain patch files that are stored in the cabinet file stream of the patch package". These two transforms contain the target product GUID and updated product GUID each. In the case of simple patching, it is just one GUID of the target product.

Hence, in order to be applied to each instance, the patch must contain a pair of transforms for each instance. Unfortunately, it is not supported by WiX torch+pyro approach and we should fall back to the powerful API:

   // dump transform and change its properties
   string transformFileName = GetNextValidName(transformName, nameSuffix);
   patch.ExtractTransform(transformName, transformFileName);
   SummaryInfo info = new SummaryInfo(transformFileName, true);
   info.RevisionNumber = info.RevisionNumber.Replace(originalProductCode, productCode);
   info.Persist();
   info.Close();

So, as you can see, we do the following (for each instance and for each of 2 transforms in default patch):
  • extract transform from the patch package
  • change the original product code to this instance product code in summary info
Afterwards, we must insert these newly created transforms into the _Storages table of the patch package:

using (View insertView = patchForWrite.OpenView("INSERT INTO `_Storages` (`Name`,`Data`) VALUES ('{0}', ?)", transformFileName))
{
   using (Record record = new Record(1))
   {
      record.SetStream(1, new FileStream(transformFileName, FileMode.Open));
      insertView.Execute(record);
      patchForWrite.Commit();
   }
}

And finally, we should append the product GUID of each instance to the Template property of Summary info (it is shown as Targets with Orca) and the name of each transform to the LastSavedBy property of the Summary info (it is not shown with Orca). Something like this:

// update patch properties
if (!patchForWrite.SummaryInfo.Template.Contains(productCode))
   {
      patchForWrite.SummaryInfo.Template += ";" + productCode;
   }
patchForWrite.SummaryInfo.LastSavedBy += ";:" + transformFileName;
patchForWrite.SummaryInfo.Persist();

That's it! Afterwards, the following magic line should work correctly and patch the installed instance of your application:

msiexec /p YourPatch.msp /n {YOURGUID-0002-0000-0000-624474736554} /qb

Good luck deploying! I would appreciate any comments on this.