<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5206553102090748219</id><updated>2011-10-05T19:15:29.422+03:00</updated><category term='install'/><category term='attach'/><category term='XSLT'/><category term='heat'/><category term='torch'/><category term='CA'/><category term='SQL Server'/><category term='VB.NET'/><category term='MSI'/><category term='sitecore'/><category term='validation'/><category term='dotNetInstaller'/><category term='multiple instance'/><category term='detach'/><category term='custom action'/><category term='TreeList'/><category term='verbose'/><category term='IIS extension'/><category term='WiX'/><category term='Windows Installer'/><category term='design guidelines'/><category term='log'/><category term='patching'/><category term='bootstrapper'/><category term='.NET'/><title type='text'>.NET, Sitecore and setup development</title><subtitle type='html'>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! :-)</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>17</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-8251943883869429454</id><published>2011-09-14T23:00:00.001+03:00</published><updated>2011-09-14T23:04:39.461+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='patching'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows Installer'/><category scheme='http://www.blogger.com/atom/ns#' term='multiple instance'/><title type='text'>Revisited: Multiple Instance installations and patches</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;I initially &lt;a href="http://ysdevlog.blogspot.com/2008/12/multiple-instance-installations-and.html"&gt;blogged about multiple instance installations&lt;/a&gt; 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.&lt;br /&gt;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 &lt;a href="http://wix.sourceforge.net/manual-wix3/wix_xsd_instancetransforms.htm"&gt;InstanceTransforms&lt;/a&gt; element:&lt;pre class="brush:xml"&gt;&amp;lt;InstanceTransforms Property="INSTANCEID"&amp;gt;   &lt;br /&gt;   &amp;lt;Instance Id="I01" ProductCode="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" ProductName="My Product 01"/&amp;gt;   &lt;br /&gt;   &amp;lt;Instance Id="I02" ProductCode="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" ProductName="My Product 02"/&amp;gt;   &lt;br /&gt;   &amp;lt;Instance Id="I03" ProductCode="{GUIDGOES-HERE-5494-843B-BC07BBC022DB}" ProductName="My Product 03"/&amp;gt;&lt;br /&gt;    ...&lt;br /&gt;&amp;lt;/InstanceTransforms&amp;gt;&lt;/pre&gt;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):&lt;pre class="brush:csharp"&gt;msiexec /i MultiInstance.msi&lt;/pre&gt;In order to start the installation of another instance, change the command as follows:&lt;pre class="brush:csharp"&gt;msiexec /i MultiInstance.msi MSINEWINSTANCE=1 TRANSFORMS=":I01"&lt;/pre&gt;The &lt;a href="http://msdn.microsoft.com/en-us/library/aa370326.aspx"&gt;MSINEWINSTANCE&lt;/a&gt; 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 &lt;a href="http://msdn.microsoft.com/en-us/library/aa369528.aspx"&gt;instance transform&lt;/a&gt; being embedded into the MSI package, and by setting &lt;a href="http://msdn.microsoft.com/en-us/library/aa372085.aspx"&gt;TRANSFORMS&lt;/a&gt; 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.&lt;br/&gt;&lt;br/&gt;Uninstalling looks quite similar, for instance, default instance uninstallation:&lt;pre class="brush:csharp"&gt;msiexec /x MultiInstance.msi&lt;/pre&gt;In order to uninstall the extra instance, you should explicitly specify its ProductCode. So, for instance I01 the uninstall command line looks like this:&lt;pre class="brush:csharp"&gt;msiexec /x {GUIDGOES-HERE-4731-8DAA-9E843A03D482}&lt;/pre&gt;So far, so good – it is quite straight-forward. Now, let’s turn to the &lt;a href="http://msdn.microsoft.com/en-us/library/aa367797.aspx"&gt;Windows Installer documentation about multiple instances&lt;/a&gt; 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. &lt;br /&gt;&lt;br/&gt;In &lt;a href="http://ysdevlog.blogspot.com/2008/12/multiple-instance-installations-and.html"&gt;my first attempt to approach the problem&lt;/a&gt;, 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.&lt;br /&gt;&lt;br/&gt;Starting from &lt;a href="http://wix.sourceforge.net/releases/3.6.1502.0/"&gt;WiX 3.6.1502.0&lt;/a&gt;, a &lt;a href="http://wix.sourceforge.net/manual-wix3/wix_xsd_component.htm"&gt;Component&lt;/a&gt; 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:&lt;pre class="brush:xml"&gt;&amp;lt;Directory Id="ProductNameFolder" Name="TestName"&amp;gt;&lt;br /&gt;   &amp;lt;Component Id="FileComponent" Guid="{GUIDGOES-HERE-4301-95D2-86A4C80EF5F0}"&amp;gt;&lt;br /&gt;      &amp;lt;File Id="dll" Source="$(var.Source)\Some.Test.dll" KeyPath="yes" /&amp;gt;&lt;br /&gt;   &amp;lt;/Component&amp;gt;&lt;br /&gt;   &amp;lt;Component Id="ConfigComponent" Guid="{GUIDGOES-HERE-4c2f-BE74-CF78D2350E48}"&amp;gt;&lt;br /&gt;      &amp;lt;File Id="web_config" Source="$(var.Source)\web.config" KeyPath="yes" /&amp;gt;&lt;br /&gt;   &amp;lt;/Component&amp;gt;&lt;br /&gt;   &amp;lt;Directory Id="EmptyFolderDir" Name="EmptyFolder"&amp;gt;&lt;br /&gt;      &amp;lt;Component Id="FolderComponent" Guid="{GUIDGOES-HERE-4543-A9F8-17491670D3A6}"&amp;gt;&lt;br /&gt;         &amp;lt;CreateFolder /&amp;gt;&lt;br /&gt;      &amp;lt;/Component&amp;gt;&lt;br /&gt;   &amp;lt;/Directory&amp;gt;&lt;br /&gt;   &amp;lt;Component Id="RegistryComponent" Guid="{GUIDGOES-HERE-45e5-ABFD-07E5CC4D7BC9}"&amp;gt;&lt;br /&gt;      &amp;lt;RegistryKey Id="MainRegKey" Action="createAndRemoveOnUninstall" Root="HKLM" Key="SOFTWARE\MultiInstanceTest\[ProductCode]"&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="MainRegValue" Name="InstanceId" Value="[INSTANCEID]" Type="string" /&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="InstallPathValue" Name="Location" Value="[ProductNameFolder]" Type="string" /&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="ProductCodeValue" Name="ProductCode" Value="[ProductCode]" Type="string" /&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="ProductNameValue" Name="ProductName" Value="[ProductName]" Type="string" /&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="ProductVersionValue" Name="ProductVersion" Value="[ProductVersion]" Type="string" /&amp;gt;&lt;br /&gt;      &amp;lt;/RegistryKey&amp;gt;&lt;br /&gt;   &amp;lt;/Component&amp;gt;&lt;br /&gt;&amp;lt;/Directory&amp;gt;&lt;/pre&gt;&lt;pre class="brush:xml"&gt;&amp;lt;InstanceTransforms Property="INSTANCEID"&amp;gt;&lt;br /&gt;   &amp;lt;Instance Id="I01" ProductCode="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" ProductName="My Product 01"/&amp;gt;&lt;br /&gt;   &amp;lt;Instance Id="I02" ProductCode="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" ProductName="My Product 02"/&amp;gt;&lt;br /&gt;&amp;lt;/InstanceTransforms&amp;gt;&lt;/pre&gt;The &lt;a href="http://msdn.microsoft.com/en-us/library/aa367797.aspx"&gt;MSDN recommendations about multiple instances&lt;/a&gt; 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:&lt;br /&gt;&lt;a href="http://lh4.ggpht.com/-kNoOE7Xgkpw/TnEDOVFxiDI/AAAAAAAAAc0/xDKdDlmf2-A/s1600-h/Instance00installed%25255B3%25255D.png"&gt;&lt;img alt="Instance00installed" border="0" height="184" src="http://lh5.ggpht.com/-f-y_ymDDpsg/TnEDPFQTf5I/AAAAAAAAAc4/csVuv73w6Sc/Instance00installed_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="Instance00installed" width="683" /&gt;&lt;/a&gt;&lt;a href="http://lh6.ggpht.com/-zBD-8Nbj_JI/TnEDPmI-ZxI/AAAAAAAAAc8/0z3a4i7fM50/s1600-h/Instance00RegInstalled%25255B3%25255D.png"&gt;&lt;img alt="Instance00RegInstalled" border="0" height="248" src="http://lh3.ggpht.com/-02O4l8thb74/TnEDQGZV0vI/AAAAAAAAAdA/K2wTQXmZC6E/Instance00RegInstalled_thumb%25255B1%25255D.png?imgmax=800" style="border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="Instance00RegInstalled" width="1076" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://lh3.ggpht.com/-x9bhH2JcruA/TnEDQignM9I/AAAAAAAAAdE/pKGT89S6oZ0/s1600-h/Instance01installed%25255B3%25255D.png"&gt;&lt;img alt="Instance01installed" border="0" height="186" src="http://lh6.ggpht.com/-cvLMQCx_o6o/TnEDRvly36I/AAAAAAAAAdI/wUbopNkDXM0/Instance01installed_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="Instance01installed" width="680" /&gt;&lt;/a&gt;&lt;a href="http://lh3.ggpht.com/-siDWeoSZNXk/TnEDSF2Fx4I/AAAAAAAAAdM/TlKGNa3QMkk/s1600-h/Instance01RegInstalled%25255B3%25255D.png"&gt;&lt;img alt="Instance01RegInstalled" border="0" height="248" src="http://lh6.ggpht.com/-biKeu4p_i_E/TnEDS_RZh0I/AAAAAAAAAdQ/wx4_Pnv-wkk/Instance01RegInstalled_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="Instance01RegInstalled" width="1076" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now uninstall the default instance – you’ll see that non-file data was not removed properly:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://lh6.ggpht.com/-0AhXVNeVdgg/TnEDTVJjUXI/AAAAAAAAAdU/cdHC2RG-Ihs/s1600-h/Instance00broken%25255B3%25255D.png"&gt;&lt;img alt="Instance00broken" border="0" height="144" src="http://lh3.ggpht.com/-U9e8M0FckmA/TnEDT5ySS2I/AAAAAAAAAdY/YQbr_a5y3uI/Instance00broken_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="Instance00broken" width="679" /&gt;&lt;/a&gt;&lt;a href="http://lh6.ggpht.com/-NzZrS2kAE3Q/TnEDURgH01I/AAAAAAAAAdc/AMxJ_8cBoBI/s1600-h/Instance00RegBroken%25255B3%25255D.png"&gt;&lt;img alt="Instance00RegBroken" border="0" height="248" src="http://lh4.ggpht.com/-H55eKBQLubA/TnEDVKHy9TI/AAAAAAAAAdg/UVGYp9G2WAI/Instance00RegBroken_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="Instance00RegBroken" width="1076" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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:&lt;pre class="brush:xml"&gt;&amp;lt;Directory Id="ProductNameFolder" Name="TestName"&amp;gt;&lt;br /&gt;   &amp;lt;Component Id="FileComponent" Guid="{GUIDGOES-HERE-4301-95D2-86A4C80EF5F0}"&amp;gt;&lt;br /&gt;      &amp;lt;File Id="dll" Source="$(var.Source)\Some.Test.dll" KeyPath="yes" /&amp;gt;&lt;br /&gt;   &amp;lt;/Component&amp;gt;&lt;br /&gt;   &amp;lt;Component Id="ConfigComponent" Guid="{GUIDGOES-HERE-4c2f-BE74-CF78D2350E48}"&amp;gt;&lt;br /&gt;      &amp;lt;File Id="web_config" Source="$(var.Source)\web.config" KeyPath="yes" /&amp;gt;&lt;br /&gt;   &amp;lt;/Component&amp;gt;&lt;br /&gt;   &amp;lt;Directory Id="EmptyFolderDir" Name="EmptyFolder"&amp;gt;&lt;br /&gt;      &amp;lt;Component Id="FolderComponent" Guid="{GUIDGOES-HERE-4543-A9F8-17491670D3A6}" MultiInstance="yes"&amp;gt;&lt;br /&gt;         &amp;lt;CreateFolder /&amp;gt;&lt;br /&gt;      &amp;lt;/Component&amp;gt;&lt;br /&gt;   &amp;lt;/Directory&amp;gt;&lt;br /&gt;   &amp;lt;Component Id="RegistryComponent" Guid="{GUIDGOES-HERE-45e5-ABFD-07E5CC4D7BC9}" MultiInstance="yes"&amp;gt;&lt;br /&gt;      &amp;lt;RegistryKey Id="MainRegKey" Action="createAndRemoveOnUninstall" Root="HKLM" Key="SOFTWARE\MultiInstanceTest\[ProductCode]"&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="MainRegValue" Name="InstanceId" Value="[INSTANCEID]" Type="string" /&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="InstallPathValue" Name="Location" Value="[ProductNameFolder]" Type="string" /&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="ProductCodeValue" Name="ProductCode" Value="[ProductCode]" Type="string" /&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="ProductNameValue" Name="ProductName" Value="[ProductName]" Type="string" /&amp;gt;&lt;br /&gt;         &amp;lt;RegistryValue Id="ProductVersionValue" Name="ProductVersion" Value="[ProductVersion]" Type="string" /&amp;gt;&lt;br /&gt;      &amp;lt;/RegistryKey&amp;gt;&lt;br /&gt;   &amp;lt;/Component&amp;gt;&lt;br /&gt;&amp;lt;/Directory&amp;gt;&lt;/pre&gt;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?! &lt;img alt="Smile" class="wlEmoticon wlEmoticon-smile" src="http://lh4.ggpht.com/-Q_cR2rTJMrk/TnED307vexI/AAAAAAAAAdk/KCBp9wAo2vQ/wlEmoticon-smile%25255B2%25255D.png?imgmax=800" style="border-bottom-style: none; border-left-style: none; border-right-style: none; border-top-style: none;" /&gt;&lt;br /&gt;&lt;br /&gt;Now, let’s turn to patching. Again, if we look back to &lt;a href="http://ysdevlog.blogspot.com/2008/12/multiple-instance-installations-and.html"&gt;my initial post on this topic&lt;/a&gt;, 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 &lt;a href="http://blogs.msdn.com/b/heaths/"&gt;Heath Stewart&lt;/a&gt; for the hint – here’s the &lt;a href="http://www.mail-archive.com/wix-users@lists.sourceforge.net/msg27696.html"&gt;full thread on wix-users mailing list&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;So, the default behavior is the following: if you author the &lt;a href="http://wix.sourceforge.net/manual-wix3/wix_xsd_patchbaseline.htm"&gt;PatchBaseline&lt;/a&gt; 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 &lt;a href="http://wix.sourceforge.net/manual-wix3/wix_xsd_validate.htm"&gt;Validate&lt;/a&gt; child to the PatchBaseline, and instruct it not to check the ProductCode:&lt;pre class="brush:xml"&gt;&amp;lt;Media Id="5000" Cabinet="RTM.cab"&amp;gt;&lt;br /&gt;   &amp;lt;PatchBaseline Id="RTM"&amp;gt;&lt;br /&gt;      &amp;lt;Validate ProductId="no" /&amp;gt;&lt;br /&gt;   &amp;lt;/PatchBaseline&amp;gt;&lt;br /&gt;&amp;lt;/Media&amp;gt;&lt;/pre&gt;So, after you build this patch, you’ll be able to apply it to a particular instance:&lt;pre class="brush:csharp"&gt;msiexec /i {GUIDGOES-HERE-4412-9BC2-17DAFFB00D20} PATCH=patch.msp /l*v patch.log&lt;/pre&gt;Or to all the installed instances at once (so-called “double-click scenario”):&lt;pre class="brush:csharp"&gt;msiexec.exe /p patch.msp /l*vx patch.log&lt;/pre&gt;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:&lt;pre class="brush:xml"&gt;&amp;lt;TargetProductCodes Replace="no"&amp;gt;&lt;br /&gt;    &amp;lt;TargetProductCode Id="{GUIDGOES-HERE-4412-9BC2-17DAFFB00D20}" /&amp;gt;&lt;br /&gt;    &amp;lt;TargetProductCode Id="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" /&amp;gt;&lt;br /&gt;    &amp;lt;TargetProductCode Id="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" /&amp;gt;&lt;br /&gt;&amp;lt;/TargetProductCodes&amp;gt;&lt;/pre&gt;As usual, WiX Toolset developers have done and keep doing fantastic job making our lives as setup developers easier! &lt;br /&gt;&lt;br /&gt;Feel free to leave a comment in case you have a note or a question. Feedback is welcome, as usual!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-8251943883869429454?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/8251943883869429454/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2011/08/revisited-multiple-instance.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/8251943883869429454'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/8251943883869429454'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2011/08/revisited-multiple-instance.html' title='Revisited: Multiple Instance installations and patches'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/-f-y_ymDDpsg/TnEDPFQTf5I/AAAAAAAAAc4/csVuv73w6Sc/s72-c/Instance00installed_thumb%25255B1%25255D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-3936790367085090519</id><published>2011-02-24T18:36:00.005+02:00</published><updated>2011-02-24T19:21:40.473+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='install'/><category scheme='http://www.blogger.com/atom/ns#' term='dotNetInstaller'/><category scheme='http://www.blogger.com/atom/ns#' term='bootstrapper'/><title type='text'>Moving to dotNetInstaller: the odd Basic UI</title><content type='html'>&lt;p&gt;In the &lt;a href="http://ysdevlog.blogspot.com/2011/02/moving-to-dotnetinstaller-launch.html"&gt;previous post&lt;/a&gt;, I’ve outlined how to emulate the launch conditions behavior in dotNetInstaller. In that article I have also emphasized the importance of turning the UI into the Basic mode. It is necessary in order to avoid extra dialogs which require user interaction. If you followed the scenario I described, you might notice a strange behavior of the BasicUI mode: &lt;strong&gt;&lt;span &gt;the message boxes disappear without any user participation&lt;/span&gt;&lt;/strong&gt;. I thought it’s be a kind of a bug, but it was done on purpose. Take a look at this code (taken from dotNetInstaller sources):&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;int &lt;/span&gt;DniMessageBox::Show(&lt;span style="color: blue"&gt;const &lt;/span&gt;std::wstring&amp;amp; p_lpszText, UINT p_nType &lt;span style="color: green"&gt;/*=MB_OK*/&lt;/span&gt;, UINT p_nDefaultResult &lt;span style="color: green"&gt;/*=MB_OK*/&lt;/span&gt;, UINT p_nIDHelp &lt;span style="color: green"&gt;/*=0*/&lt;/span&gt;)&lt;br /&gt;{&lt;br /&gt;   &lt;span style="color: blue"&gt;int &lt;/span&gt;result = p_nDefaultResult;&lt;br /&gt;   &lt;span style="color: blue"&gt;switch&lt;/span&gt;(InstallUILevelSetting::Instance-&amp;gt;GetUILevel())&lt;br /&gt;   {&lt;br /&gt;   &lt;span style="color: green"&gt;// basic UI, dialogs appear and disappea&lt;br /&gt;   &lt;/span&gt;&lt;span style="color: blue"&gt;case &lt;/span&gt;InstallUILevelBasic:&lt;br /&gt;       {&lt;br /&gt;           g_hHook = SetWindowsHookEx(WH_CBT, CBTProc, NULL, GetCurrentThreadId());&lt;br /&gt;           CHECK_WIN32_BOOL(NULL != g_hHook, L&lt;span style="color: #a31515"&gt;"Error setting CBT hook"&lt;/span&gt;);&lt;br /&gt;           result = AfxMessageBox(p_lpszText.c_str(), p_nType, p_nIDHelp);&lt;br /&gt;           CHECK_BOOL(0 != result, L&lt;span style="color: #a31515"&gt;"Not enough memory to display the message box."&lt;/span&gt;);&lt;br /&gt;           &lt;span style="color: blue"&gt;if &lt;/span&gt;(result == 0xFFFFFF) result = p_nDefaultResult;&lt;br /&gt;       }&lt;br /&gt;       &lt;span style="color: blue"&gt;break&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;   &lt;span style="color: green"&gt;// silent, no UI&lt;br /&gt;   &lt;/span&gt;&lt;span style="color: blue"&gt;case &lt;/span&gt;InstallUILevelSilent:&lt;br /&gt;       result = p_nDefaultResult;&lt;br /&gt;       &lt;span style="color: blue"&gt;break&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;   &lt;span style="color: green"&gt;// full UI&lt;br /&gt;   &lt;/span&gt;&lt;span style="color: blue"&gt;case &lt;/span&gt;InstallUILevelFull:&lt;br /&gt;   &lt;span style="color: blue"&gt;default&lt;/span&gt;:&lt;br /&gt;       result = AfxMessageBox(p_lpszText.c_str(), p_nType, p_nIDHelp);&lt;br /&gt;       &lt;span style="color: blue"&gt;break&lt;/span&gt;;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   &lt;span style="color: blue"&gt;return &lt;/span&gt;result;&lt;br /&gt;}&lt;/pre&gt;&lt;p&gt;So, as you can see, in Basic mode is shows the message box, and after some time (if you didn’t catch the moment to press any button), it automatically emulates the pressing of default choice button. I was quite surprised when I understood it was designed to work like this – that’s because I’ve never seen such a UI behavior…&lt;/p&gt;&lt;p&gt;But, anyway, I suspect that a user would like to know why the installation terminated  - a certain prerequisite is not installed. As long as the mentioned behavior is hard-coded, the only option is to create a custom build of dotNetInstaller. It’s obvious that the fix is trivial here – make the case for InstallUILevelBasic go the same branch as InstallUILevelFull, that is, just show the message box. Next step is to build the solution – see “Contributing to Source Code” chapter of dotNetInstaller.chm for instructions how to build.&lt;/p&gt;&lt;p&gt;Finally, install the custom build instead of the official one and make sure your setup project picks the changes up. That’s it!&lt;/p&gt;&lt;p&gt;As usual, I would appreciate any comments and notes!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-3936790367085090519?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/3936790367085090519/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2011/02/moving-to-dotnetinstaller-odd-basic-ui.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/3936790367085090519'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/3936790367085090519'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2011/02/moving-to-dotnetinstaller-odd-basic-ui.html' title='Moving to dotNetInstaller: the odd Basic UI'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-6255290807993006211</id><published>2011-02-18T15:59:00.002+02:00</published><updated>2011-02-24T19:22:56.703+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='install'/><category scheme='http://www.blogger.com/atom/ns#' term='dotNetInstaller'/><category scheme='http://www.blogger.com/atom/ns#' term='bootstrapper'/><title type='text'>Moving to dotNetInstaller: launch conditions</title><content type='html'>&lt;p&gt;In the &lt;a href="http://ysdevlog.blogspot.com/2011/01/moving-to-dotnetinstaller-simplest-case.html"&gt;previous post&lt;/a&gt; I’ve described how to implement the simplest use case of a bootstrapper: create a single EXE file and run the actual installation after extraction. Today I’d like to go further and illustrate more production-like situation. &lt;/p&gt;  &lt;p&gt;Ok, imagine that you’d like to add some checks to your installation package, and run the actual installation only if all those checks pass. This scenario has its own term: adding launch conditions. Launch condition is basically a statement which evaluates to either true, or false. In case it’s false, and the check is critical for the further installation, you terminate the installation process, as a rule. Otherwise, you let it do the job.&lt;/p&gt;  &lt;p&gt;The &lt;a href="http://dotnetinstaller.codeplex.com/"&gt;dotNetInstaller&lt;/a&gt; has a conception called Installed Checks. It can check various areas, like system registry, files or directories. It is only allowed to place installed checks under components. In the &lt;a href="http://ysdevlog.blogspot.com/2011/01/moving-to-dotnetinstaller-simplest-case.html"&gt;simplest scenario&lt;/a&gt; we avoided using components, relying just on the install complete command. Components refer to separate independent parts of your installation package. There are various types of components – dotNetInstaller help file explains them all pretty good. So, my first guess was to add a single component of type “exe”, move my embedded files there and add a number of installed checks to it for various prerequisites I require. Something like this:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_dqWPbhO6dtA/TV57QKK5n-I/AAAAAAAAAaY/64MkgfdtQ4s/s1600-h/DNI_prerequisite_wrong3.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="DNI_prerequisite_wrong" border="0" alt="DNI_prerequisite_wrong" src="http://lh6.ggpht.com/_dqWPbhO6dtA/TV57QlM1xZI/AAAAAAAAAac/YiTgeNRz_NU/DNI_prerequisite_wrong_thumb1.png?imgmax=800" width="929" height="604" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;But my assumption was not correct. The trick is that installed check (or a combination of those) placed under a component defines &lt;strong&gt;&lt;span &gt;if this very component is installed&lt;/span&gt;&lt;/strong&gt;. In other words, the most “supported” use case of dotNetInstaller is when you add all the components you need into your final package, and each of them verifies its own presence on the target machine. As a result of such verification, a component decides whether to install or not. &lt;/p&gt;  &lt;p&gt;A quick search on &lt;a href="http://codeplex.com/"&gt;codeplex.com&lt;/a&gt; discussions gave me a link to the &lt;a href="http://dotnetinstaller.codeplex.com/workitem/6387"&gt;appropriate feature request&lt;/a&gt;, which proved my assumption it’s not supported out of the box today. However, there is a workaround.&lt;/p&gt;  &lt;p&gt;For each of the launch conditions a separate component should be declared. The trick is such components won’t actually install anything, so we’ll call them “fake” components. A component has a property called “failed_exec_command_continue”. It contains a message to be shown to the user in case a component failed to install, so put the appropriate message there, for instance, “.NET 3.5 SP1 is not installed. The installation program will terminate”. Make sure that both “allow_continue_on_error” and “default_continue_on_error” are set to False – otherwise a user will be presented with a prompt box, instead of a simple message box. Finally, put non-existing executable to the “executable” property, e.g. “fake.exe”. Now it’s time to add a required number and combination of installed checks to this fake component, which will actually do the job. Here’s what we get at the end of this shaman dancing:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_dqWPbhO6dtA/TV57ROn0EgI/AAAAAAAAAag/N6dFGabezTY/s1600-h/DNI_prerequisite_right3.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="DNI_prerequisite_right" border="0" alt="DNI_prerequisite_right" src="http://lh4.ggpht.com/_dqWPbhO6dtA/TV57RswTQ-I/AAAAAAAAAak/aiwI_JvgIFw/DNI_prerequisite_right_thumb1.png?imgmax=800" width="929" height="604" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;So, how does this work? The dotNetInstaller starts the installation from the .NET (3.5 SP1) component and the first thing it evaluates the installed checks. If the evaluation succeeds, in our sample this means that the .NET 3.5 SP1 is present on the target machine. In terms of dotNetInstaller, this means that a component we called “.NET (3.5 SP1)” is installed and we do not trigger its installation. Otherwise, if the evaluation fails, this means that the component is not present and dotNetInstaller starts its installation. It will try to call “fake.exe”, which &lt;strong&gt;does not exist&lt;/strong&gt;, and will show a message. As long as we forbad the rest of the installation to continue, it will terminate. Exactly what we need!&lt;/p&gt;  &lt;p&gt;Note however, that the described behavior looks that good &lt;strong&gt;&lt;span &gt;in Basic UI mode&lt;/span&gt;&lt;/strong&gt;. The error of failed component is just logged to the log file, and no more annoying dialogs are displayed.&lt;/p&gt;  &lt;p&gt;If you try this out, you’ll notice one strange little thing with message boxes. In the next blog post I’ll tell you what it is, and how to handle it. And this will be the end of the trilogy. :-)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-6255290807993006211?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/6255290807993006211/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2011/02/moving-to-dotnetinstaller-launch.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/6255290807993006211'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/6255290807993006211'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2011/02/moving-to-dotnetinstaller-launch.html' title='Moving to dotNetInstaller: launch conditions'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_dqWPbhO6dtA/TV57QlM1xZI/AAAAAAAAAac/YiTgeNRz_NU/s72-c/DNI_prerequisite_wrong_thumb1.png?imgmax=800' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-3116593929131595403</id><published>2011-01-27T17:22:00.001+02:00</published><updated>2011-01-27T17:22:45.939+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='install'/><category scheme='http://www.blogger.com/atom/ns#' term='dotNetInstaller'/><category scheme='http://www.blogger.com/atom/ns#' term='bootstrapper'/><title type='text'>Moving to dotNetInstaller: the simplest case</title><content type='html'>&lt;p&gt;I’ve been playing with one of the most popular &lt;a href="http://wix.mindcapers.com/wiki/Bootstrapper"&gt;bootstrapper&lt;/a&gt; applications available as free and open source tool – &lt;a href="http://http://dotnetinstaller.codeplex.com/"&gt;dotNetInstaller&lt;/a&gt;. On one hand, it turns out to be quite a powerful and feature-rich tool. But on the other, some things seem not intuitive to me, and there are still limitations. This post opens the series of (at least, two) posts about dotNetInstaller and my own experience with it.&lt;/p&gt;  &lt;p&gt;Ok, imagine you need to do a very simple thing: wrap your installation program resources into a single EXE file, let it extract necessary files to somewhere under %TEMP%, run the installation UI wizard and finally drop extracted files when the installation is done. &lt;/p&gt;  &lt;p&gt;You should start by installing dotNetInstaller (I used the &lt;a href="http://dotnetinstaller.codeplex.com/releases/view/50143"&gt;most recent 2.0 version&lt;/a&gt;). One of the executables being installed is InstallerEditor.exe. It is a kind of IDE (smart editor) for dotNetInstaller project files, which are called configurations. The information about your project is stored as XML, that is easily DIFF-able and MERGE-able. &lt;/p&gt;  &lt;p&gt;So, run InstallerEditor, and select File &amp;gt; New – the new empty config file will be created. The first thing I suggest to do is to enable logging – it is a property of config file you’ve just created. Next, right click the root (and so far the only) node in the left pane, and select Add &amp;gt; Configurations &amp;gt; Setup Configuration. Actually, this is the only type of entities you can add under config file node. Besides, at this level you can set the UI level for your bootstrapper. According to our task definition, ‘basic’ is just enough. By now, you should end up with something like this:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_dqWPbhO6dtA/TUGNtemJjtI/AAAAAAAAAZQ/KQ9dQ6j8ZI4/s1600-h/DNI_initial_config%5B13%5D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="DNI_initial_config" border="0" alt="DNI_initial_config" src="http://lh4.ggpht.com/_dqWPbhO6dtA/TUGNuNVi_-I/AAAAAAAAAZU/4O4jTeYdzKM/DNI_initial_config_thumb%5B7%5D.png?imgmax=800" width="929" height="600" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Setup configuration serves as a root for various entities: embedded files, installation components, UI controls, etc. However, our requirements for the simplest scenario doesn’t require most of it. Usually configuration consists of a number of components, but again, we won’t add them for now.&lt;/p&gt;  &lt;p&gt;In order to include installation files into our bootstrapper, right-click “install:” node and select Add &amp;gt; Embed &amp;gt; Embed Folder. Now fill the properties for this embedded folder. Fortunately, those are just two – &lt;em&gt;sourcefolderpath&lt;/em&gt; and &lt;em&gt;targetfolderpath&lt;/em&gt;. Place the value ‘#APPPATH’ to the first one and any value to the second. ‘#APPPATH’ is one of the several variable substitutions offered by dotNetInstaller out-of-the-box and basically means that installation files will be picked either from the current folder, or from the one you specify in the /a switch of the linker. The ‘targetfolderpath’ can logically be left empty, because it sets the name of the subfolder under system temp location to extracts the files to. But it is designed to be required, so feel free to paste anything here, for instance, ‘exe’. Ok, so now we are at this point:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_dqWPbhO6dtA/TUGNuqEQ56I/AAAAAAAAAZY/RTdrH6MrNJU/s1600-h/DNI_embed_folder%5B11%5D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="DNI_embed_folder" border="0" alt="DNI_embed_folder" src="http://lh4.ggpht.com/_dqWPbhO6dtA/TUGNvK9SmNI/AAAAAAAAAZc/S5Oq1rRhRnM/DNI_embed_folder_thumb%5B5%5D.png?imgmax=800" width="929" height="600" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The installation wizard to run is also among those files we embedded, of course. So, in order to run it after the extraction is done we should fill in the ‘complete_command’ property of the configuration. For this, select “install:” node and find the set of properties prefixed with “complete_command”. As you can see, the configuration entity has lots of properties to configure and is quite flexible. The “complete_command” should store the command line to run on successful installation complete. You can specify different values for each of 3 UI modes: full, basic and silent. Actually, if basic or silent are not specified, it will fall back to just “complete_command”. &lt;/p&gt;  &lt;p&gt;Besides, we’d like to show CAB extraction dialog. This is especially useful when the files are large and it takes some time to extract. Set “show_cab_dialog” to ‘true’. Optionally, customize other properties of the CAB extraction dialog, like Caption and Message. So, summarizing these two paragraphs, we now have the following configuration:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh6.ggpht.com/_dqWPbhO6dtA/TUGNvkFml6I/AAAAAAAAAZg/8WfnCxbuypQ/s1600-h/DNI_complete_command%5B3%5D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="DNI_complete_command" border="0" alt="DNI_complete_command" src="http://lh5.ggpht.com/_dqWPbhO6dtA/TUGNwOzPvQI/AAAAAAAAAZk/xBanjpviccA/DNI_complete_command_thumb%5B1%5D.png?imgmax=800" width="929" height="604" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Pay attention to “cab_path” property. In this form it basically means: take system %TEMP% location, and create a subfolder in it named as random GUID. This guaranties the uniqueness of the extract target location and you would not probably ever want to change it. Now, this magic location can be referenced as #CABPATH by other properties. For isntance, this is what we have done for “complete_command”. The values says: go to the folder the files were just extracted to, go down to its “exe” subfolder (remember ‘targetfolderpath’?) and run InstallWizard.exe.&lt;/p&gt;  &lt;p&gt;And finally, some more details. Make sure “auto_start”, “wait_for_complete_command” and “cab_path_autodelete” are all set to ‘true’. Obviously, this will instruct our bootstrapper to start automatically, and auto delete the extracted files after the complete command completes. &lt;/p&gt;  &lt;h4&gt;Linking and running&lt;/h4&gt;  &lt;p&gt;Before building the project, you can run it with dotNetInstaller.exe to see the UI. Just run dotNetInstaller.exe /ConfigFile configuration.xml. But &lt;strong&gt;&lt;font color="#ff0000"&gt;this won’t embed any files&lt;/font&gt;&lt;/strong&gt;. As a result, &lt;strong&gt;&lt;font color="#ff0000"&gt;you’ll be able to check only UI &lt;/font&gt;&lt;/strong&gt;(which is obviously not the point for our case). &lt;strong&gt;&lt;font color="#ff0000"&gt;All settings which rely on embedded files will fail&lt;/font&gt;&lt;/strong&gt;.&lt;/p&gt;  &lt;p&gt;Instead, we’ll link the sources into final setup.exe. The following command does the job:&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;font face="Consolas"&gt;InstallerLinker.exe /o:setup.exe /t:dotNetInstaller.exe /c:install_config.xml /i:my.ico /a:source /v+&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Here, /o: stands for output file name, /t: is a template of EXE file to make like – be sure to always set it to dotNetInstaller.exe, /c: is a path to the configuration file we have been editing all this time, /i: is obviously a path to the icon to use as an application icon for setup.exe, /a: is a path to the installation files to embed, and finally, /v+ turns the verbose logging on. In case there are no errors, you’ll see the following output:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_dqWPbhO6dtA/TUGNwsZQRGI/AAAAAAAAAZo/q1rMWEGoyxg/s1600-h/DNI_linker_output%5B3%5D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="DNI_linker_output" border="0" alt="DNI_linker_output" src="http://lh4.ggpht.com/_dqWPbhO6dtA/TUGNxN02_zI/AAAAAAAAAZs/Cm6G3JH9ruM/DNI_linker_output_thumb%5B1%5D.png?imgmax=800" width="681" height="514" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Now you have setup.exe, which extracts your installation files (showing the progress), and starts your main InstallWizard.exe in case of successful extraction.&lt;/p&gt;  &lt;p&gt;That’s it! As usual, your comments and notes are welcome.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-3116593929131595403?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/3116593929131595403/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2011/01/moving-to-dotnetinstaller-simplest-case.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/3116593929131595403'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/3116593929131595403'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2011/01/moving-to-dotnetinstaller-simplest-case.html' title='Moving to dotNetInstaller: the simplest case'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_dqWPbhO6dtA/TUGNuNVi_-I/AAAAAAAAAZU/4O4jTeYdzKM/s72-c/DNI_initial_config_thumb%5B7%5D.png?imgmax=800' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-5062738025735063374</id><published>2010-09-10T02:23:00.001+03:00</published><updated>2010-09-10T02:29:40.648+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sitecore'/><title type='text'>Back to basics: Versioned, Unversioned and Shared fields</title><content type='html'>&lt;p&gt;It is well-known that each field of a template can be versioned (default option), unversioned or shared. The Template Builder UI exposes the Unversioned and Shared properties as two independent checkboxes. And thus, despite it’s a very basic Sitecore concept, it is sometimes asked &lt;a href="http://sdn.sitecore.net/forum//ShowPost.aspx?PostID=29034"&gt;what’s the point of marking a field both shared and unversioned&lt;/a&gt;. The answer is “a field marked both shared and unversioned is still a shared field”. Think about “shared” as a superset of “unversioned” – the field can’t be shared (between all versions of all languages) without being unversioned (between all versions of one language).&lt;/p&gt;  &lt;p&gt;Let’s see how it works under the hood when the field “sharing” level is changed. Let’s create a simple template with just a single field. We’ll keep the defaults so far (versioned). Now create a content item based on this template and fill in the field. &lt;/p&gt;  &lt;p&gt;Sitecore fields are stored in three different tables inside the database: VersionedFields, UnversionedFields and SharedFields. The names are quite self-explanatory. Let’s run the following SQL query:&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;font color="#0000ff"&gt;SELECT * FROM VersionedFields WHERE FieldId = '{GUID-GOES-HERE-…}'&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;As a result, one record is returned – the field information of the item we’ve just created is stored in the VersionedFields table. The similar queries for UnversionedFields and SharedFields give 0 records. &lt;/p&gt;  &lt;p&gt;Now change the field to be Unversioned and run all 3 queries again – it will return 1 record for UnversionedFields table and 0 for others. Change the field to be both Shared and Unversioned and repeat the experiment – the field info now resides in SharedFields table. Now if you uncheck Unversioned and leave it just Shared, it will still show 1 record for SharedFields table and 0 for others. So, here’s the evidence!&lt;/p&gt;  &lt;p&gt;NOTE: changing the field “sharing” level might result in a data loss (similar to type cast operation in C#), and Sitecore warns you about it.&lt;/p&gt;  &lt;p&gt;You might think that two checkboxes are to be blamed for this confusion. Check out the hot VS extension called &lt;a href="http://visualstudiogallery.msdn.microsoft.com/en-us/44a26c88-83a7-46f6-903c-5c59bcd3d35b/view"&gt;Sitecore Rocks&lt;/a&gt; – a brand new tool (CTP for now) for developers working with Sitecore projects in VS 2010. It seems to look more natural in this way, isn’t it?&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_dqWPbhO6dtA/TIlsZANnP8I/AAAAAAAAAXA/3E97dawvRnU/s1600-h/RocksDesignTemplate%5B4%5D.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="RocksDesignTemplate" border="0" alt="RocksDesignTemplate" src="http://lh6.ggpht.com/_dqWPbhO6dtA/TIlsZ1BB5aI/AAAAAAAAAXE/OHGbPYeLP2s/RocksDesignTemplate_thumb%5B2%5D.png?imgmax=800" width="644" height="178" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-5062738025735063374?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/5062738025735063374/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2010/09/back-to-basics-versioned-unversioned.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/5062738025735063374'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/5062738025735063374'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2010/09/back-to-basics-versioned-unversioned.html' title='Back to basics: Versioned, Unversioned and Shared fields'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_dqWPbhO6dtA/TIlsZ1BB5aI/AAAAAAAAAXE/OHGbPYeLP2s/s72-c/RocksDesignTemplate_thumb%5B2%5D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-3976165228468061997</id><published>2010-05-07T19:34:00.001+03:00</published><updated>2010-05-07T19:35:04.833+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MSI'/><category scheme='http://www.blogger.com/atom/ns#' term='torch'/><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows Installer'/><title type='text'>Torch.exe confuses the language validation and ProductCode validation</title><content type='html'>&lt;p&gt;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…&lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;The workaround is quite simple: use literally “–serr” and “–val” to achieve the same result. For instance, for language transform it should be:&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; torch.exe … –serr a –serr b –serr c –serr d –serr e –serr f –val l …&lt;/p&gt;  &lt;p&gt;[By the way, does it look too long just for me? I would prefer –serr abcdef :-)]&lt;/p&gt;  &lt;p&gt;I’ve also filed an &lt;a href="https://sourceforge.net/tracker/?func=detail&amp;amp;aid=2998229&amp;amp;group_id=105970&amp;amp;atid=642714"&gt;issue&lt;/a&gt; to the WiX toolset. Hope this can help somebody.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-3976165228468061997?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/3976165228468061997/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2010/05/torchexe-confuses-langauge-validation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/3976165228468061997'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/3976165228468061997'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2010/05/torchexe-confuses-langauge-validation.html' title='Torch.exe confuses the language validation and ProductCode validation'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-8908520579790450492</id><published>2010-04-14T15:35:00.001+03:00</published><updated>2010-04-14T15:35:40.525+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MSI'/><category scheme='http://www.blogger.com/atom/ns#' term='torch'/><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows Installer'/><title type='text'>Torch.exe throws scary error message unrelated to the real problem</title><content type='html'>&lt;p&gt;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&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; torch -t language setup.msi setup_ru-ru.msi -out mst\ru-ru.mst&lt;/p&gt;  &lt;p&gt;returned the scary error message:&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; error TRCH0001 : The Windows Installer service failed to start. Contact your support personnel&lt;/p&gt;  &lt;p&gt;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 &lt;a href="http://blogs.msdn.com/pmarcu/archive/2008/05/30/Patching-something-you-didnt-build-with-WiX-using-WiX-.aspx#8920333"&gt;a single, but exact hint&lt;/a&gt;. It is just a single line, and one can miss the point since that’s another problem which is discussed there. &lt;/p&gt;  &lt;p&gt;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 &lt;a href="https://sourceforge.net/tracker/?func=detail&amp;amp;aid=2987095&amp;amp;group_id=105970&amp;amp;atid=642714"&gt;an issue&lt;/a&gt; to the WiX inbox at sourceforge.net.&lt;/p&gt;  &lt;p&gt;Hope this info is helpful until the message text is fixed.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-8908520579790450492?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/8908520579790450492/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2010/04/torchexe-throws-scary-error-message.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/8908520579790450492'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/8908520579790450492'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2010/04/torchexe-throws-scary-error-message.html' title='Torch.exe throws scary error message unrelated to the real problem'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-1552950824577840286</id><published>2009-09-17T21:36:00.001+03:00</published><updated>2009-09-17T21:36:27.064+03:00</updated><title type='text'>Going to Agileee 2009</title><content type='html'>&lt;p&gt;Today I’m heading to the &lt;a href="http://agileee.org/"&gt;Agileee 2009 conference&lt;/a&gt; being held in Kiev on September, 18 – 19th. This is rather new field to me – I’ve never been practicing Agile or Scrum before. We’ll see how it goes. At least, I’m expecting to learn many new and interesting things and see how to apply this in the Modules team.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-1552950824577840286?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/1552950824577840286/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/09/going-to-agileee-2009.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/1552950824577840286'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/1552950824577840286'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/09/going-to-agileee-2009.html' title='Going to Agileee 2009'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-4054982650567494677</id><published>2009-08-18T23:46:00.001+03:00</published><updated>2009-08-18T23:46:42.181+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='IIS extension'/><title type='text'>WebDirProperties: AnonymousUser attribute is not obligatory</title><content type='html'>&lt;p&gt;When you specify a &lt;em&gt;WebDirProperties&lt;/em&gt; element to be used by the sites you install (configure) with WiX, you might also want to allow anonymous access to this site. Fortunately, there’s an attribute &lt;em&gt;AnonymousAccess&lt;/em&gt;, which being set to ‘yes’ allows anonymous access to IIS web site.&lt;/p&gt;  &lt;p&gt;NOTE: If you don’t address any property of “authorization” group (&lt;em&gt;AnonymousAccess, BasicAuthentication, DigestAuthentication, PassportAuthentication&lt;/em&gt; or &lt;em&gt;WindowsAuthentication&lt;/em&gt;) in your &lt;em&gt;WebDirProperties&lt;/em&gt;, the site inherits those from w3svc root. If you set at least one explicitly, you need to set others the way you wish, because WiX defaults might not work for you. &lt;/p&gt;  &lt;p&gt;The wix.chm states that “&lt;a href="http://wix.sourceforge.net/manual-wix3/iis_xsd_webdirproperties.htm"&gt;When setting this (AnonymousAccess) to 'yes' you should also provide the user account using the AnonymousUser attribute, and determine what setting to use for the IIsControlledPassword attribute.&lt;/a&gt;” But it turns out you are not forced to provide the AnonymousUser attribute and I suppose you never wanted to – you should provide a password as well, but who knows the password of IUSR on a target machine?&lt;/p&gt;  &lt;p&gt;Instead, just omit the &lt;em&gt;AnonymousUser&lt;/em&gt; attribute and this part of IIS metabase will stay untouched. The username/password will inherit from higher node (again, w3svc). And yes, don’t forget &lt;em&gt;IIsControlledPassword&lt;/em&gt;=”yes”.&lt;/p&gt;  &lt;p&gt;Hope this helps you tuning the website during the installation.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-4054982650567494677?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/4054982650567494677/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/08/webdirproperties-anonymoususer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/4054982650567494677'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/4054982650567494677'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/08/webdirproperties-anonymoususer.html' title='WebDirProperties: AnonymousUser attribute is not obligatory'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-6488469426904214692</id><published>2009-08-16T23:43:00.001+03:00</published><updated>2009-08-16T23:43:14.031+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='design guidelines'/><category scheme='http://www.blogger.com/atom/ns#' term='VB.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><title type='text'>A warning to VB.NET developers</title><content type='html'>&lt;p&gt;Avoid defining methods with default arguments!&lt;/p&gt;  &lt;p&gt;Today I have been exploring the “Member design” chapter of a great book of &lt;a href="http://blogs.msdn.com/kcwalina/"&gt;Cwalina&lt;/a&gt;/&lt;a href="http://blogs.msdn.com/brada/"&gt;Abrams&lt;/a&gt; “&lt;a href="http://www.amazon.com/Framework-Design-Guidelines-Conventions-Development/dp/0321545613/ref=dp_ob_title_bk"&gt;Framework Design Guidelines&lt;/a&gt;”, and found a guideline which shocked me a bit. No, the guideline itself is correct and valuable. I just was never thinking it works like this.&lt;/p&gt;  &lt;p&gt;VB.NET has a language feature called default arguments. When you define a method in your class, you can specify default values to the optional parameters to be taken when this parameter is omitted. As far as I understand, this is a kind of alternative to the method overloading. &lt;/p&gt;  &lt;p&gt;Consider the following code:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff"&gt;Public Class DefaultValues      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Public Function Sum(ByVal a As Integer, Optional ByVal b As Integer =&lt;/font&gt; &lt;strong&gt;&lt;font color="#ff0000"&gt;100&lt;/font&gt;&lt;/strong&gt;&lt;font color="#0000ff"&gt;)      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Sum = a + b       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; End Function       &lt;br /&gt;&amp;#160; End Class&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;(I speak C# myself, so excuse me my poor VB ;-))&lt;/p&gt;  &lt;p&gt;Let’s say we compile this code into a DLL and we have a client console application to utilize that library:&lt;/p&gt;  &lt;p&gt;&lt;font color="#0000ff"&gt;Module TestDefaultValues      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Sub Main()       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Dim df As DefaultValues.DefaultValues = New DefaultValues.DefaultValues()       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(df.Sum(&lt;strong&gt;&lt;font color="#ff0000"&gt;55&lt;/font&gt;&lt;/strong&gt;))       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; End Sub       &lt;br /&gt;&amp;#160; End Module&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Compile everything and run TestDefaultValues.exe. The result is predictable: &lt;strong&gt;&lt;font color="#ff0000"&gt;155&lt;/font&gt;&lt;/strong&gt;.&lt;/p&gt;  &lt;p&gt;Now change the default value from 100 to 200 and compile only the library. DO NOT recompile the client application. Run it again, and it is still &lt;strong&gt;&lt;font color="#ff0000"&gt;155&lt;/font&gt;&lt;/strong&gt;!&lt;/p&gt;  &lt;p&gt;This is why it is strongly not recommended to use default arguments instead of normal method overloading. And this issue is why C# doesn’t expose this technique. &lt;/p&gt;  &lt;p&gt;Be careful, VB.NET developers! And long live C#! :-)&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-6488469426904214692?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/6488469426904214692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/08/warning-to-vbnet-developers.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/6488469426904214692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/6488469426904214692'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/08/warning-to-vbnet-developers.html' title='A warning to VB.NET developers'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-568877047238417879</id><published>2009-07-19T23:24:00.001+03:00</published><updated>2009-07-19T23:24:59.588+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='heat'/><category scheme='http://www.blogger.com/atom/ns#' term='sitecore'/><category scheme='http://www.blogger.com/atom/ns#' term='XSLT'/><title type='text'>XSLT: inline blocks of managed code</title><content type='html'>&lt;p&gt;It’s not a secret that XSLT supports &lt;a href="http://www.w3.org/TR/xslt11/#define-extension-functions"&gt;blocks of code&lt;/a&gt;, written in another language, to be used inside the stylesheet. It seems to have been there from the very beginning – at least, &lt;a href="http://www.w3.org/TR/xslt11/"&gt;XSLT 1.1&lt;/a&gt; understands it. &lt;/p&gt;  &lt;p&gt;However, Microsoft enriched this option with their own element, &lt;a href="http://msdn.microsoft.com/en-us/library/ms256042.aspx"&gt;msxsl:script&lt;/a&gt;. It offers pretty much the same functionality, but you can also write the code in C# or any other language of .NET platform. XSLT gurus might argue that it is superfluous stuff and it is unnecessary in 99% of cases. Well, as for me, XSLT lacks a number of useful functions in the standard library, such as ToLower/ToUpper, EndWith, etc. You never think about such low level things when programming C#, but you often have to invent a wheel trying to do the same with XSLT.&lt;/p&gt;  &lt;p&gt;More details can be found in the &lt;a href="http://msdn.microsoft.com/en-us/library/wxaw5z5e.aspx"&gt;official documentation&lt;/a&gt;, but here is a brief extract:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;guess an extra prefix and let XSLT processor know about it:      &lt;br /&gt;      &lt;br /&gt;&amp;lt;xsl:stylesheet version=&amp;quot;1.0&amp;quot;       &lt;br /&gt;&amp;#160; xmlns:xsl=&amp;quot;&lt;a href="http://www.w3.org/1999/XSL/Transform&amp;quot;"&gt;http://www.w3.org/1999/XSL/Transform&amp;quot;&lt;/a&gt;       &lt;br /&gt;&amp;#160; xmlns:msxsl=&amp;quot;urn:schemas-microsoft-com:xslt&amp;quot;       &lt;br /&gt;&amp;#160; &lt;strong&gt;xmlns:ext=&amp;quot;&lt;/strong&gt;&lt;a href="http://my.domain.com/ext&amp;quot;"&gt;&lt;strong&gt;http://my.domain.com/ext&amp;quot;&lt;/strong&gt;&lt;/a&gt;&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160; ...       &lt;br /&gt;&amp;lt;/xsl:stylesheet&amp;gt;       &lt;br /&gt;      &lt;br /&gt;Also, pay attention how msxsl prefix is defined – it is required to use msxsl:script syntax.       &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;code your extension function:      &lt;br /&gt;      &lt;br /&gt;&amp;lt;msxsl:script language=&amp;quot;&lt;strong&gt;C#&amp;quot;&lt;/strong&gt; implements-prefix=&amp;quot;&lt;strong&gt;ext&lt;/strong&gt;&amp;quot;&amp;gt;       &lt;br /&gt;&amp;#160;&amp;#160; public string ToUpper(string inString)       &lt;br /&gt;&amp;#160;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; return inString.ToUpper();       &lt;br /&gt;&amp;#160;&amp;#160; }       &lt;br /&gt;&amp;lt;/msxsl:script&amp;gt;       &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;and finally use it:      &lt;br /&gt;      &lt;br /&gt;&amp;lt;xsl:value-of select=&amp;quot;ext:ToUpper(@Name))&amp;quot;/&amp;gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Obviously, it is not a good idea to write lots of code this way. It makes the XSLT stylesheet larger and a bit harder to maintain. And, according to Microsoft, you should “&lt;a href="http://msdn.microsoft.com/en-us/library/ms256042.aspx"&gt;avoid script blocks from XSLT files, because they require loading the script engine multiple times&lt;/a&gt;”. Actually, if you created an XSLT stylesheet to fill it with tones of .NET code, you’re definitely doing something wrong. But it seems to be good addition to small, but useful “one-line” operations.&lt;/p&gt;  &lt;h4&gt;Sitecore and msxsl:script&lt;/h4&gt;  &lt;p&gt;If you plan to take advantage of inline blocks of C# code in Sitecore XSL rendering, you’ll have to do one more step. By default, .NET API to handle the XSL transforms disables the possibility to use msxsl:script. It is probably done for security reason. But the web.config of your Sitecore solution contains the setting &lt;strong&gt;EnableXslScripts&lt;/strong&gt;, which you can easily set to true and be happy:&lt;/p&gt;  &lt;p&gt;&amp;lt;!--&amp;#160; ENABLE XSLT SCRIPTS    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Determine whether XSLT script support should be enabled.     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; If script support is not enabled, it will be an error if the XSLT file contains script blocks.     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;strong&gt;Default value: false&lt;/strong&gt;.     &lt;br /&gt;--&amp;gt;     &lt;br /&gt;&amp;lt;setting name=&amp;quot;EnableXslScripts&amp;quot; value=&amp;quot;&lt;strong&gt;true&lt;/strong&gt;&amp;quot; /&amp;gt; &lt;/p&gt;  &lt;p&gt;The performance seems to be the same for this simple code either written in msxsl:script block, or wrapped into &lt;a href="http://sdn.sitecore.net/upload/sitecore6/61/presentation_component_cookbook_sc61_a4.pdf"&gt;XSL extension&lt;/a&gt;. So, the choice is yours.&lt;/p&gt;  &lt;h4&gt;WiX and msxsl:script&lt;/h4&gt;  &lt;p&gt;The &lt;a href="http://wix.sourceforge.net/manual-wix3/heat.htm"&gt;heat.exe&lt;/a&gt; utility of the WiX toolset has an option to run the harvested authoring against XSLT transform. This is a checkpoint when you can mutate the output before it is done. INHO, it is the most powerful extension option of Heat, because you can do anything with the XML fragment in XSLT.&lt;/p&gt;  &lt;p&gt;However, it was a bit disappointing to find out the scripts are disabled by default, and it is not customizable, and the easiest way to fix this is to patch Heat itself and prepare custom WiX build. It would be great if this option is available one day in the base, either as a command line argument, or a configuration setting.&lt;/p&gt;  &lt;p&gt;That’s it. If you have some experience with this trick, knowing its pros and cons deeper, share it here. And as usual, any comments are welcome.&lt;/p&gt;  &lt;p&gt;P.S. this post was written with the help of Windows Live writer :-)&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-568877047238417879?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/568877047238417879/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/07/xslt-inline-blocks-of-managed-code.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/568877047238417879'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/568877047238417879'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/07/xslt-inline-blocks-of-managed-code.html' title='XSLT: inline blocks of managed code'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-7688569397871059307</id><published>2009-03-22T21:18:00.005+02:00</published><updated>2009-03-23T12:53:56.381+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='TreeList'/><category scheme='http://www.blogger.com/atom/ns#' term='validation'/><category scheme='http://www.blogger.com/atom/ns#' term='sitecore'/><title type='text'>Validating the source of TreeList</title><content type='html'>Sitecore 6 validation was designed to validate the field values. Recently, I also found it useful to control the source of the complex field types, like TreeList. In this post, I'll explain this option taking the TreeList field type as an example.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'm skipping the validation basics here, since this topic is covered by &lt;a href="http://alexeyrusakov.com/sitecoreblog/"&gt;Alexey Rusakov&lt;/a&gt; in his &lt;a href="http://alexeyrusakov.com/sitecoreblog/2008/07/02/Sitecore+6+Validation+Part+1.aspx"&gt;validation series&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can define a number of parameters in the source of TreeList field type. The complete list is described in the paragraph 2.4.2 "How to Control the List of Items in a Selection Field" of the &lt;a href="http://sdn5.sitecore.net/upload/sitecore6/datadefinitioncookbook-a4.pdf"&gt;Data Definition cookbook&lt;/a&gt;. These parameters can filter the available and visible items in the content tree (IncludeTemplatesForSelection, ExcludeItemsForDisplay, etc.), define the tree root (DataSource), control multiple selection (AllowMultipleSelection), etc.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But modifying this long list of parameters in a one-line edit field can lead to a simple typos, both in the parameters' names and values. Let's examine how this can be "solved" by introducing a source validator.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The BaseValidator class, the very root of the validator hierarchy in Sitecore API, has a protected method GetField(), which returns an instance of a Field - the one we validate. Hence, the Source property is also available. We want to validate only complex source here, thus skipping if it is an ID or an item path:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;        protected override ValidatorResult Evaluate()&lt;/div&gt;&lt;div&gt;        {&lt;/div&gt;&lt;div&gt;            ValidatorResult result = ValidatorResult.Valid;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;            Field field = GetField();&lt;/div&gt;&lt;div&gt;            if (field != null)&lt;/div&gt;&lt;div&gt;            {&lt;/div&gt;&lt;div&gt;                string fieldSource = field.Source;&lt;/div&gt;&lt;div&gt;                if (!string.IsNullOrEmpty(fieldSource) &amp;amp;&amp;amp; !ID.IsID(fieldSource) &lt;/div&gt;&lt;div&gt;                    &amp;amp;&amp;amp; !fieldSource.StartsWith("/", StringComparison.InvariantCulture))&lt;/div&gt;&lt;div&gt;                {&lt;/div&gt;&lt;div&gt;                    result = EvaluateSourceParameters(fieldSource);&lt;/div&gt;&lt;div&gt;                }&lt;/div&gt;&lt;div&gt;            }&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;            return result;&lt;/div&gt;&lt;div&gt;        }&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Ok, let's start the validation from just the verification if the source is "well-formed". It might happen that a certain parameter was left without a value, or a typo was introduced to the well-known name. Sitecore will never throw an error in such a case, but instead you may receive an orphaned field with nothing to choose from. Thus, the simplest validation includes these two checks, otherwise it keeps the name/value pairs for further analysis:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;        ValidatorResult EvaluateSourceParameters(string fieldSource)&lt;/div&gt;&lt;div&gt;        {&lt;/div&gt;&lt;div&gt;            SafeDictionary&lt;string&gt; parameters = new SafeDictionary&lt;string&gt;();&lt;/string&gt;&lt;/string&gt;&lt;/div&gt;&lt;div&gt;            string[] sourceParts = fieldSource.Split('&amp;amp;');&lt;/div&gt;&lt;div&gt;            foreach (string part in sourceParts)&lt;/div&gt;&lt;div&gt;            {&lt;/div&gt;&lt;div&gt;                if (string.IsNullOrEmpty(part))&lt;/div&gt;&lt;div&gt;                {&lt;/div&gt;&lt;div&gt;                    continue;&lt;/div&gt;&lt;div&gt;                }&lt;/div&gt;&lt;div&gt;                if (!part.Contains("=") || part.EndsWith("="))&lt;/div&gt;&lt;div&gt;                {&lt;/div&gt;&lt;div&gt;                    Text = string.Format("The value is not set for source parameter '{0}'", part.TrimEnd('='));&lt;/div&gt;&lt;div&gt;                    return GetFailedResult(ValidatorResult.Error);&lt;/div&gt;&lt;div&gt;                }&lt;/div&gt;&lt;div&gt;                else&lt;/div&gt;&lt;div&gt;                {&lt;/div&gt;&lt;div&gt;                    string parameterName = part.Substring(0, part.IndexOf('=')).ToLower();&lt;/div&gt;&lt;div&gt;                    if (!sourceParameters.Contains(parameterName))&lt;/div&gt;&lt;div&gt;                    {&lt;/div&gt;&lt;div&gt;                        Text = string.Format("Unknown source parameter '{0}'", parameterName);&lt;/div&gt;&lt;div&gt;                        return GetFailedResult(ValidatorResult.Error);&lt;/div&gt;&lt;div&gt;                    }&lt;/div&gt;&lt;div&gt;                    else&lt;/div&gt;&lt;div&gt;                    {&lt;/div&gt;&lt;div&gt;                        string parameterValue = part.Substring(part.IndexOf('=') + 1);&lt;/div&gt;&lt;div&gt;                        parameters.Add(parameterName, parameterValue);&lt;/div&gt;&lt;div&gt;                    }&lt;/div&gt;&lt;div&gt;                }&lt;/div&gt;&lt;div&gt;            }&lt;/div&gt;&lt;div&gt;            return EvaluateWellFormedParameters(parameters);&lt;/div&gt;&lt;div&gt;        }&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The further validation can go deeper and verify the presence of the specified template or item. The method EvaluateWellFormedParameters in this example just iterates the name/value pairs of parameters and applies a certain validation strategy, for instance:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;        ValidatorResult EvaluateTemplates(string value, Database database)&lt;/div&gt;&lt;div&gt;        {&lt;/div&gt;&lt;div&gt;            string[] templates = value.Split(new char[] { ',' });&lt;/div&gt;&lt;div&gt;            foreach (string template in templates)&lt;/div&gt;&lt;div&gt;            {&lt;/div&gt;&lt;div&gt;                if (!string.IsNullOrEmpty(template) &amp;amp;&amp;amp; Query.SelectSingleItem(string.Format("/sitecore/templates//*[@@key='{0}']", template.ToLower()), database) == null)&lt;/div&gt;&lt;div&gt;                {&lt;/div&gt;&lt;div&gt;                    Text = string.Format("The template '{0}' doesn't exist in the '{1}' database", template, database.Name);&lt;/div&gt;&lt;div&gt;                    return ValidatorResult.Warning;&lt;/div&gt;&lt;div&gt;                }&lt;/div&gt;&lt;div&gt;            }&lt;/div&gt;&lt;div&gt;            return ValidatorResult.Valid;&lt;/div&gt;&lt;div&gt;        }&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'm attaching the &lt;a href="http://sites.google.com/site/yshost/Home/files/TreeListSourceValidator.cs"&gt;full code of this example&lt;/a&gt;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There are several notes to consider:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;The DatabaseName parameter is not validated, because Sitecore takes over this. Try specifying DatabaseName=nosuchdb, and press Save&lt;/li&gt;&lt;li&gt;The parameter names are case-insensitive. This is because the parameters are extracted with the StringUtil.ExtractParameter() method, which ignores the case&lt;/li&gt;&lt;li&gt;The TreeList field type doesn't "tolower" the values of IncludeItemsForDisplay and ExcludeItemsForDisplay parameters. Hence, be sure to specify an item key instead of an item name here&lt;/li&gt;&lt;li&gt;The content tree filter is built out of the "ForDisplay" parameters using 'and' operation. Thus, if IncludeItemsForDisplay contain items of other templates than those specified in IncludeTemplatesForDisplay, this results in an empty tree. This can also be a point of extension of this validator's functionality&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Hope anyone finds this article useful. As usual, I would appreciate any comments.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-7688569397871059307?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/7688569397871059307/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/03/validating-source-of-treelist.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/7688569397871059307'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/7688569397871059307'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/03/validating-source-of-treelist.html' title='Validating the source of TreeList'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-909406032116290735</id><published>2009-02-02T22:46:00.008+02:00</published><updated>2009-02-02T23:31:06.880+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MSI'/><category scheme='http://www.blogger.com/atom/ns#' term='CA'/><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='log'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows Installer'/><category scheme='http://www.blogger.com/atom/ns#' term='custom action'/><category scheme='http://www.blogger.com/atom/ns#' term='verbose'/><title type='text'>Extended logging options in WiX custom actions</title><content type='html'>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 &lt;a href="http://msdn.microsoft.com/en-us/library/aa370536(VS.85).aspx"&gt;here&lt;/a&gt;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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 &lt;a href="http://www.amazon.com/Definitive-Guide-Windows-Installer-Experts/dp/1590592972"&gt;"The Definitive Guide to Windows Installer" by Phil Wilson&lt;/a&gt;, which guides you through the basics of log file reading and understanding.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you browse the WiX source code, you can find the lines like this in its custom actions:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;WcaLog(LOGMSG_STANDARD, "Error: Cannot locate User.User='%S'", wzUser);&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The first argument is a logging level. It can be &lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;LOGMSG_STANDARD, which is "write to log whenever informational logging is enabled", which in most cases means "always"&lt;/li&gt;&lt;li&gt;LOGMSG_TRACEONLY, which is "write to log if this WiX build is a DEBUG build" (is often used internally to dump CustomActionData contents)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;LOGMSG_VERBOSE, which is "write to log when LOGVERBOSE"&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Check if the LOGVERBOSE property is set (can be set in the command-line since it is public)&lt;/li&gt;&lt;li&gt;Otherwise, check if the MsiLogging property is set (MSI 4.0+)&lt;/li&gt;&lt;li&gt;Otherwise, check the &lt;a href="http://msdn.microsoft.com/en-us/library/aa369776(VS.85).aspx"&gt;logging policy in the registry&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, the following is the easiest way in my opinion to make your MSI (WiX-based) log file even more verbose:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   msiexec /i package.msi ... LOGVERBOSE=1 /L*v install.log&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hope this helps.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;P.S. This is just a brief extract of what's there in the source code. As usual, code is the best documentation ;-)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-909406032116290735?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/909406032116290735/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/02/extended-logging-options-in-wix-custom.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/909406032116290735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/909406032116290735'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/02/extended-logging-options-in-wix-custom.html' title='Extended logging options in WiX custom actions'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-8711511835940748926</id><published>2009-01-20T22:06:00.003+02:00</published><updated>2009-01-21T21:32:47.759+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows Installer'/><category scheme='http://www.blogger.com/atom/ns#' term='IIS extension'/><title type='text'>IIS extension: WebAppPool</title><content type='html'>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. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      &amp;lt;Component DiskId="1" Id="CreateIISAppPool" Guid="{YOURGUID-6C5B-4980-AD0B-E32FA2DBC1F4}" Directory="WebsiteFolder"&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         &amp;lt;Condition&amp;gt;IISMAJORVERSION &amp;lt;&amp;gt; "#5"&amp;lt;/Condition&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         &amp;lt;iis:WebAppPool Id="IISSiteAppPool6" Name="[IISAPPPOOL_NAME]" MaxWorkerProcesses="1" Identity="networkService" /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         &amp;lt;RegistryKey Root="HKLM" Key="$(var.ParentKey)"&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;            &amp;lt;RegistryValue Name="IISAppPoolName" Type="string" Value="[IISAPPPOOL_NAME]"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         &amp;lt;/RegistryKey&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      &amp;lt;/Component&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here is the algorithm:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;search the registry for the app pool name as usual&lt;br /&gt;&lt;/li&gt;&lt;li&gt;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&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;The first point is quite straight-forward:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      &amp;lt;Property Id="IISAPPPOOL_NAME"&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         &amp;lt;RegistrySearch Id="IISAppPoolName" Root="HKLM" Key="$(var.ParentKey)" Name="IISAppPoolName" Type="raw" /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      &amp;lt;/Property&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;The second one is not natural, like any hack:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      [CustomAction]&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      public static ActionResult ChangeWebAppPoolNameToDeceiveUninstall(Session session)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         int numberOfInstalled = 1;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         foreach (ProductInstallation product in ProductInstallation.GetRelatedProducts(session["UpgradeCode"]))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;            if ((session["ProductCode"] != product.ProductCode) &amp;amp;&amp;amp; product.IsInstalled)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;            {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="color: rgb(51, 51, 255);  font-size:13px;"&gt;               numberOfInstalled++;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;               break;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;            }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="color: rgb(51, 51, 255);  font-size:13px;"&gt;         if (numberOfInstalled &gt; 1)&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;            session["IISAPPPOOL_NAME"] += string.Format("|{0}", DateTime.Now.ToLongTimeString());&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         return ActionResult.Success;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Note that the mentioned action should be &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;immediate&lt;/span&gt;, should occur &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;after&lt;/span&gt; AppSearch on &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;uninstall&lt;/span&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That's it! I would appreciate any comments as usual.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-8711511835940748926?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/8711511835940748926/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/01/iis-extension-webapppool.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/8711511835940748926'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/8711511835940748926'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/01/iis-extension-webapppool.html' title='IIS extension: WebAppPool'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-357187188716316506</id><published>2009-01-19T22:04:00.010+02:00</published><updated>2009-01-20T22:08:09.843+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows Installer'/><category scheme='http://www.blogger.com/atom/ns#' term='IIS extension'/><title type='text'>IIS extension: WebSite</title><content type='html'>Ok, it's time for another portion of the installation fun, now about the IIS web sites.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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? &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      &amp;lt;Component DiskId="1" Id="ModifyIISSite5" Guid="{YOURGUID-2023-4D19-90D2-EE9101C71E44}" Directory="WebsiteFolder" &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;Permanent="yes"&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         &amp;lt;Condition&amp;gt;IISMAJORVERSION = "#5"&amp;lt;/Condition&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         &amp;lt;iis:WebSite Id="IISSite5" Description="[IISSITE_NAME]" Directory="WebsiteFolder" &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;ConfigureIfExists="yes"&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;            &amp;lt;iis:WebAddress Id="IISSiteAddress5" Port="[IISSITE_PORT]"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;         &amp;lt;/iis:WebSite&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      &amp;lt;/Component&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The previous snippet highlights another attribute as bold - Permanent="yes". It makes the hosting component &lt;a href="http://msdn.microsoft.com/en-us/library/aa369530(VS.85).aspx"&gt;permanent&lt;/a&gt;, 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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;    &amp;lt;PropertyRef Id="IISMAJORVERSION"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;    &amp;lt;PropertyRef Id="IISMINORVERSION"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;That's it! As usual, any comments are highly appreciated.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-357187188716316506?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/357187188716316506/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/01/iis-extension-website.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/357187188716316506'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/357187188716316506'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/01/iis-extension-website.html' title='IIS extension: WebSite'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-3034270517850361409</id><published>2009-01-10T18:19:00.013+02:00</published><updated>2009-01-13T12:24:33.662+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MSI'/><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='detach'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows Installer'/><category scheme='http://www.blogger.com/atom/ns#' term='attach'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><title type='text'>Attach / Detach database during installation</title><content type='html'>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. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:arial;"&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;fragment&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:arial;"&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:arial;"&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;sql:sqldatabase id="SqlMasterDBWinAuth" server="[SQL_SERVER]" database="master"/&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:arial;"&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:arial;"&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;/fragment&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;Component DiskId="1" Id="MSSQLCore" Guid="YOURGUID-4E94-4B28-B995-DCBFD50B9F07"&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;Condition&amp;gt;YOUR CONDITION GOES HERE&amp;lt;/Condition&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;File Id="MSSQLCoreFile" Name="$(var.CoreFileName)" KeyPath="yes" /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;File Id="MSSQLCoreLogFile" Name="$(var.CoreFileLogName)" /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;lt;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'"/&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;lt;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"/&amp;gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;/Component&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thanks to the hint of Rob Mensching in &lt;a href="http://www.mail-archive.com/wix-users@lists.sourceforge.net/msg18212.html"&gt;one of his replies to the WiX mailinglist&lt;/a&gt;, 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 &lt;a href="http://msdn.microsoft.com/en-us/library/ms189128.aspx"&gt;rules of permissions change during attach/detach&lt;/a&gt;. As a result, the windows installer can't access the file afterwards, and the uninstallation is rolled back.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To fix this, perform "SET OFFLINE" query before detaching the database and you'll never face with this behavior again.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thus, the final version will look similar to this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;Component DiskId="1" Id="MSSQLCore" Guid="YOURGUID-4E94-4B28-B995-DCBFD50B9F07"&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;Condition&amp;gt;YOUR CONDITION GOES HERE&amp;lt;/Condition&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;File Id="MSSQLCoreFile" Name="$(var.CoreFileName)" KeyPath="yes" /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;File Id="MSSQLCoreLogFile" Name="$(var.CoreFileLogName)" /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;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"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;sql:SqlString Id="OfflineCoreDatabase" Sequence="2" ContinueOnError="yes" ExecuteOnUninstall="yes" SqlDb="SqlMasterDBWinAuth" SQL="ALTER DATABASE [\[][INSTANCENAME]Core[\]] SET OFFLINE WITH ROLLBACK IMMEDIATE" /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;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'"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;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'"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;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"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;/Component&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;util:User Id="SQLUser" Name="[SC_SQL_SERVER_USER]" Password="[SC_SQL_SERVER_PASSWORD]"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;sql:SqlDatabase Id="SqlMasterDBWinAuth" Server="[SC_SQL_SERVER]" Database="master" /&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;sql:SqlDatabase Id="SqlMasterDBSqlAuth" Server="[SC_SQL_SERVER]" Database="master" User="SQLUser" /&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Tired? The last thing. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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. &lt;/div&gt;&lt;div&gt;No problem, let's assign the necessary rights. Put the following snippet into your component which contains the SqlAuth scripts:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;CreateFolder&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;util:PermissionEx GenericAll="yes" User="NetworkService" /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;/CreateFolder&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Note: Don't forget to reference WIX_ACCOUNT_NETWORKSERVICE property.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But, wait, the ShedSecureObjects is scheduled after the InstallSqlData, this doesn't help!&lt;/div&gt;&lt;div&gt;Right, the sequence should also be changed like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;InstallExecureSequence&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;Custom Action="InstallSqlData" After="SchedSecureObjects"&amp;gt;NOT SKIPINSTALLSQLDATA AND VersionNT &amp;gt; 400&amp;lt;/Custom&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:small;"&gt;&lt;span class="Apple-style-span" style="COLOR: rgb(51,51,255)"&gt;&amp;lt;/InstallExecuteSequence&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Good luck! I would appreciate any comments and notes to this.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-3034270517850361409?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/3034270517850361409/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2009/01/attach-detach-database-during.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/3034270517850361409'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/3034270517850361409'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2009/01/attach-detach-database-during.html' title='Attach / Detach database during installation'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5206553102090748219.post-4195983428705263682</id><published>2008-12-30T11:00:00.007+02:00</published><updated>2008-12-30T13:41:22.109+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MSI'/><category scheme='http://www.blogger.com/atom/ns#' term='WiX'/><category scheme='http://www.blogger.com/atom/ns#' term='patching'/><category scheme='http://www.blogger.com/atom/ns#' term='Windows Installer'/><category scheme='http://www.blogger.com/atom/ns#' term='multiple instance'/><title type='text'>Multiple instance installations and patching</title><content type='html'>Well, this is the first message to my first weblog, and it should be outstanding by default. :-)&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;&lt;span class="Apple-style-span" style="font-family: arial;"&gt;   &amp;lt;instancetransforms property="ANY_PROPERTY"&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;&lt;span class="Apple-style-span" style="font-family: arial;"&gt;      &amp;lt;instance id="InstanceId1" productcode="{42A33A91-36B0-4700-A6F5-1289D22F358C}"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;&lt;span class="Apple-style-span" style="font-family: arial;"&gt;      &amp;lt;instance id="InstanceId2" productcode="{68C62C01-D064-4CF0-9239-F5D2FF36BD9A}"/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255); font-size: 13px;"&gt;&lt;span class="Apple-style-span" style="font-family: arial;"&gt;      ...&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;&lt;span class="Apple-style-span" style="font-family: arial;"&gt;   &amp;lt;/instancetransforms&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As always with Windows Installer, this is not the end. According to the &lt;a href="http://msdn.microsoft.com/en-us/library/aa367797(VS.85).aspx"&gt;MSI documentation about authoring multiple instances&lt;/a&gt;, "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. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;copy the MSI package&lt;/li&gt;&lt;li&gt;use API to query and update the database with new GUIDs for each component&lt;/li&gt;&lt;li&gt;generate a transform between the copy and original MSI&lt;/li&gt;&lt;li&gt;drop the copy MSI&lt;/li&gt;&lt;li&gt;repeat the steps about the number of times as many instances you plan to support&lt;/li&gt;&lt;li&gt;embed all these transforms into the original MSI&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;msiexec /i YourPackage.msi MSINEWINSTANCE=1 TRANSFORMS=:InstanceId1;:ComponentGUIDTransform1.mst ...&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This works like a charm in conjunction with an algorithm to detect next available instance and a bootstrapper. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now let's turn to the patching. When I browsed the internet for the info about multiple instance installs and patches, I found &lt;a href="http://blog.deploymentengineering.com/2006/10/multiple-instance-msis-and.html"&gt;a great post of Christopher Painter&lt;/a&gt;. 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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let's take a look at the &lt;a href="http://msdn.microsoft.com/en-us/library/aa370578(VS.85).aspx"&gt;patch definition and its contents&lt;/a&gt;: "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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   &lt;/span&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 153, 0);"&gt;// dump transform and change its properties&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   string transformFileName = GetNextValidName(transformName, nameSuffix);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   patch.ExtractTransform(transformName, transformFileName);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   SummaryInfo info = new SummaryInfo(transformFileName, true);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   info.RevisionNumber = info.RevisionNumber.Replace(originalProductCode, productCode);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   info.Persist();&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   info.Close();&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;So, as you can see, we do the following (for each instance and for each of 2 transforms in default patch):&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;extract transform from the patch package&lt;/li&gt;&lt;li&gt;change the original product code to this instance product code in summary info&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Afterwards, we must insert these newly created transforms into the _Storages table of the patch package:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;using (View insertView = patchForWrite.OpenView("INSERT INTO `_Storages` (`Name`,`Data`) VALUES ('{0}', ?)", transformFileName))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   using (Record record = new Record(1))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      record.SetStream(1, new FileStream(transformFileName, FileMode.Open));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      insertView.Execute(record);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      patchForWrite.Commit();&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 153, 0);"&gt;// update patch properties&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;if (!patchForWrite.SummaryInfo.Template.Contains(productCode))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;      patchForWrite.SummaryInfo.Template += ";" + productCode;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;   }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;patchForWrite.SummaryInfo.LastSavedBy += ";:" + transformFileName;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;patchForWrite.SummaryInfo.Persist();&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;That's it! Afterwards, the following magic line should work correctly and patch the installed instance of your application:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: small;"&gt;&lt;span class="Apple-style-span" style="color: rgb(51, 51, 255);"&gt;msiexec /p YourPatch.msp /n {YOURGUID-0002-0000-0000-624474736554} /qb&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Good luck deploying! I would appreciate any comments on this.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5206553102090748219-4195983428705263682?l=ysdevlog.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://ysdevlog.blogspot.com/feeds/4195983428705263682/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://ysdevlog.blogspot.com/2008/12/multiple-instance-installations-and.html#comment-form' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/4195983428705263682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5206553102090748219/posts/default/4195983428705263682'/><link rel='alternate' type='text/html' href='http://ysdevlog.blogspot.com/2008/12/multiple-instance-installations-and.html' title='Multiple instance installations and patching'/><author><name>Yan Sklyarenko</name><uri>http://www.blogger.com/profile/02915083843106544026</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_dqWPbhO6dtA/TRxCVi2UqoI/AAAAAAAAAYg/g4LBpfWrBjI/S220/YS_custom_1.jpg'/></author><thr:total>9</thr:total></entry></feed>
