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

Wednesday, October 15, 2014

WIX failed to see source files when building using TfvcTemplate.12.xaml

Recently, I started using TfvcTemplate.12.xaml, after reading this msdn article: i.e. executing PowerShell script as Pre-Build event while implementing versioning.

Initially, without using any script, when I simply tried to build my solution, Wix was unable to see its sources files. I verified multiple times that files were there and it builds without any problem at command line using MsBuild and Wix Toolset commands. In addition, I changed the build definition to older DefaultTemplate.11.xaml and it worked fine.

After some experiments, I doubted about MsBuild’s parallel processing ability i.e. it might be building installer project (*.wixproj) before building my other projects. So, again I used TfvcTemplate.12.xaml and passed “/m:1” and here it is: The liner processing solved the problem and I got to know what was going wrong:

TfvcTemplate.12.xaml seems to be executing MsBuild with parallel processing enabled (/m), while in my VS Solution, Wix project was added independently i.e. in Build Order it was the last project to build, but I never set other projects as its dependency. So MSBuild found that it has no dependency and ignored its Build Order and simply building it either on random order or as first project.

I verified this phenomena by removing /m:1 argument to MSBuild and declaring all other projects as Dependencies of Wix Project: i.e. in VS, PROJECT –> Project Dependencies. This caused MsBuild to build all other projects before Wix Project and now Wix is able to find its Source files properly…

Here is the screen shot for illustration:

TfvcTemplate_MSBuildConfig

Thursday, September 25, 2014

HeatDirectory task failure on TFS with MSBUILD error MSB4166: Child node “3” exited prematurely

Due to the project’s requirement, I had to setup Wix to generate the installer xml markup at Build Time. For this, I started using HeatDirectory task in my *.wixproj. This worked very fine while building locally (not on Team Foundation Server (TFS) ). However, it failed while building on TFS with following error log:
C:\Program Files (x86)\WiX Toolset v3.8\bin\Heat.exe dir D:\Builds\32\48\bin\Debug\InstallSrc\WebApp\ -cg CompGrp_WebApp -dr WebApp -ke -scom -sreg -srd -var var.WebAppSrc -v -ag -sfrag -suid -out WebApp_.wxs
Could not load file or assembly 'file:///C:\Program Files (x86)\WiX Toolset v3.8\bin\Heat.exe' or one of its dependencies. An attempt was made to load a program with an incorrect format.            at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
            at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
            at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
            at System.Reflection.RuntimeAssembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, Boolean suppressSecurityChecks, StackCrawlMark& stackMark)
            at System.Reflection.Assembly.LoadFrom(String assemblyFile)
            at Microsoft.Tools.WindowsInstallerXml.Build.Tasks.WixToolTask.ExecuteToolThread(Object parameters)
0>MSBUILD : error MSB4166: Child node "3" exited prematurely. Shutting down. Diagnostic information may be found in files in the temporary files directory named MSBuild_*.failure.txt.

I searched for MSBUILD : error MSB4166: Child node "3" exited prematurely which mostly pointing towards MSBuild’s parallel project building capability. However, turning this feature off on TFS never solved the problem, instead reporting differently.
On same build machine (where TFS is used to build), I tried the Heat.exe command line (See blue in above log), and it worked fine. Even building the same solution on same build machine using MSBuild command line never gave any problem. It was only failing when building through TFS.
Making story short, I posted the problem on different forums, and got response on Wix-User’s Group.  Using these tips, I was able to correct the problem.

Issue is actually related to MSBuild Platform setting on TFS Build Definition.  i.e:
TFS --> Edit Build Definition --> Process --> Advanced --> MSBuild Platform = Auto.
With Auto, MSBuild was actually trying to use Heat.exe as x64 bit process, while it is actually x86 bit process, and so it was failing with this message:
 
Could not load file or assembly 'file:///C:\Program Files (x86)\WiX Toolset v3.8\bin\Heat.exe' or one of its dependencies. An attempt was made to load a program with an incorrect format.
 
Now, changed this to x86 i.e. 
 
TFS --> Edit Build Definition --> Process --> Advanced --> MSBuild Platform = X86
 
Here is a screen shot for this correction:
TFS_MSBuildPlatform
 
and MSBuild is now able to execute Heat.exe as x86 bit process and building the project as expected.








Thursday, September 18, 2014

Displaying <![CDATA[]]> literally in XML while escaping end token.

While adding XML Code Snippets for WIX (Windows Installer XML), I had to write XML containing <![CDATA[Installed OR NETFRAMEWORK40FULL]]>.
Problem arises, where Visual Studio Code Snippets uses <![CDATA[your snippet code here]]> to store the code and it can’t have another <![CDATA[]]> inside it. So it was not accepting this:
<Code Language="xml">
<![CDATA[
<PropertyRef Id="$NETFRAMEWORK40FULL$"/>
  <Condition Message="This application requires Microsoft .NET Framework 4.0 Runtime in order to run. Please install the .NET Framework and then run this installer again." >
        <![CDATA[Installed OR $NETFRAMEWORK40FULL$]]>
</Condition>
]]>
</Code>

That is, the inner <![CDATA[Installed OR $NETFRAMEWORK40FULL$]]> became a bottleneck. So making story short, after Googling, I found very good discussion on this discussion on Stack Overflow Thread and then I was able to build proper XML by escaping ]]>. Here is my solved snippet code:
    <Code Language="xml">    
      <![CDATA[<PropertyRef Id="$NETFRAMEWORK40FULL$"/>
      <Condition Message="This application requires Microsoft .NET Framework 4.0 Runtime in order to run. Please install the .NET Framework and then run this installer again." >
          <![CDATA[Installed OR $NETFRAMEWORK40FULL$]]]]><![CDATA[>
       </Condition>]]>      
    </Code>

See the breaking code into chunks and put in multiple uses of <![CDATA[]]>… Do read Escaping CDATA end token for more info.

Thursday, July 10, 2014

Wix Bootstrapper: Exit/End during installation.

 


During CacheAcquireProgress Event:
this.model.BootstrapperApplication.CacheAcquireProgress += (sender, args) =>
{
//if ever detected InstallState.Cancelled (which can be set from other event like Exit Button Click)
if (this.State == InstallState.Cancelled)
args.Result =
Result.Cancel;
//this would quit installation process.
.
.
.
};
During ExecuteProgress Event:
this.model.BootstrapperApplication.ExecuteProgress += (sender, args) =>
{
//if ever detected InstallState.Cancelled (which can be set from other event like Exit Button Click)
if (this.State == InstallState.Cancelled)
args.Result =
Result.Cancel; //this would quit installation process.
.
.
.

};

So if InstallState.Cancelled is ever setup, it would set args.Result=Result.Cancel and so it would quit installation.

Friday, May 16, 2014

Bootstrapper Error 0x8013101b: Failed to create the managed bootstrapper application.

I developed Wix Burn based Managed bootstrapper, which required .Net Framework v4.0.xxx. It worked perfectly fine when I tried on Windows XP + Service Pack 2. That is, it initially launched .Net FW 4 Setup, which I packed in my Burn Package, and then launched my Boostrapper Installer.

But on Windows 7 without Service Pack 1, it was crashing with following log:
[0530:0B50][2014-01-21T11:39:50]i000: Loading managed bootstrapper application.
[0530:0B50][2014-01-21T11:39:51]e000: Error 0x8013101b: Failed to create the managed bootstrapper application.
[0530:0B50][2014-01-21T11:39:51]e000: Error 0x8013101b: Failed to create UX.
[0530:0B50][2014-01-21T11:39:51]e000: Error 0x8013101b: Failed to load UX.
[0530:0B50][2014-01-21T11:39:51]e000: Error 0x8013101b: Failed while running 
.
After some searching and digging, I finally able to solve it by editing BootstrapperCore.config file. Initially, it was:
< startup useLegacyV2RuntimeActivationPolicy="true" >
        < supportedRuntime version="v4.0" / >
        < supportedFramework version="v4\Client" / >
         < supportedRuntime version="v2.0.50727" / >

< /startup>
         

I removed < supportedRuntime version="v2.0.50727" / >. This causes bootstrapper to launch .Net FW 4.0 installation (Packed with as payload). 

Remember, on Windows 7 without SP1, there is .Net framework 3.5. So supportedRuntime version="v2.0.50727" causing the bootstrapper trying to use Net FW 2.0 or 3.5 and so failing, as my Bootstrapper is based on .Net FW 4.

Now, t updated the machine with .Net FW 4 and then launched my bootstrapper, and now working like charm.. :) 

Monday, March 24, 2014

WPF: Loading RTF document in RichTextBox from Embeded Resouce.

While developing a UI interface for Wix based bootstrapper,  I found that in WPF, Richtextbox Control is not same as it was in Windows Forms. So loading an RTF document in it was a little mess. However, while finding and digging for about two days, I finally came to a simple solution. Let’s say, my WPF project name is Surf.
  1. In your WPF projects, add a Resources.resx file (if its not already there).
  2. Add your RTFDoc.rtf in your Resources.resx file.
  3. Along with your Resources.resx file, there would be code behind file:Resources.Designer.cs. Open it and copy its namespace and Class name. In my case it is  Surf.Resources.Resource1

I used this to load rtf data in my WPF RichTextBox control. Here are the lines from the code behind of my XAML:

using Surf.Resources;
void Surface_Loaded(object sender, RoutedEventArgs e)         
{             
//Loading License Agreement document data.
MemoryStream memLicStream = new MemoryStream(ASCIIEncoding.Default.GetBytes(Resource1.RTFDoc));             

this.MyRTFDocument.Selection.Load(memLicStream, DataFormats.Rtf);
}

System.IO.MemoryStream class is used here to load the rtf data from RTFDoc stored in Resource1.resx. And then it is loaded as a Selection in my RichTextBox Control (MyRTFDocument), while specifying Data Format as Rtf.

Thursday, March 20, 2014

Signing problem with Wix based Bootstrapper.

I faced a problem very similar to defined here,  i.e. Whenever I signed the Bootstrapper, it failed to do the installation with following errors in log:

[1604:24F8][2013-12-04T11:50:00]e000: Error 0x80004005: Failed to extract all files from container.
[1604:2FB4][2013-12-04T11:50:00]e000: Error 0x80004005: Failed to wait for operation complete.
[1604:2FB4][2013-12-04T11:50:00]e000: Error 0x80004005: Failed to open container.
[1604:2FB4][2013-12-04T11:50:00]e000: Error 0x80004005: Failed to open container: WixAttachedContainer.
[1604:2FB4][2013-12-04T11:50:00]e312: Failed to extract payloads from container: WixAttachedContainer to working path: C:\Users\.....\{6ab8eece-89c6-4417-905f-6d9c5136519d}\C7C1FB4E513C19E0F5E8F6856FF2ACC4D7D143A2, error: 0x80004005.
[1604:2574][2013-12-04T11:50:00]e000: Error 0x80004005: Failed while caching, aborting execution.

Using solution defined there, and some guidance from WixToolset insignia page , I'm just summarizing the procedure here:


While building the msi:

  • If you are building separate cab files, then first sign the cab files and do Insignia -im MySetup.msi. Insignia will update your MSI with the digital signature information of its associated external cabs. The file will be updated in-place. Then sign your MSI.
  • If its only one MSI, then simply sign the MSI.

Signing Bootstrapper:

Bootstrapper actually  contains Engine.exe inside and if this is not signed with same certificate, then after you sign your Bootstrapper.exe, it would fail to install and you may have to face problem defined in the beginning of this post, even if your MSI is properly signed.

To solve this, before signing Bootstrapper.exe, using Insignia.exe, do following:

  1. First extract your Engine.exe from your Bootstrapper.exe: insignia -ib Bootstrapper.exe -o Engine.exe
  2. Now sign Engine.exe and put it back to Bootstrapper.exe, and then you can sign the Bootstrapper_Signed.exe.
However if you don't sign this, it would still work. Whenever windows would sense the execution of your Engine.exe, it would show a dialog with signing information.But if you try to execute unsigned Bootstrapper_Signed.exe as Administrator, it would show "Unknown Publisher". That's why it is recommended to sign Bootstrapper_Signed.exe as well.


Monday, May 20, 2013

Executing Custom Actions on the basis of Feature Selection.

I came across this situation that my user has to make selection from Customize Dialog (shows feature tree) and depending upon this selection Custom Actions (actually calls to RegSvr32.exe) have to be executed.

For this, I added a Feature Structure to be shown to user as a Choice options:

    <Feature Id="RegShellExtensions" Level="1" Display="expand" Title="Register Shell Extensions" Description="Register shell extensions with windows" >
<
FeatureId="ShellExtx86" Title="32 Bit Shell Extensions" Description="Register 32 Bit Shell Extensions" Level="1" />
<?
if $(var.PACKAGE_TARGETPLATFORM)=x64?>
<
Feature Id="ShellExtx64" Title="64 Bit Shell Extensions" Description="Register 64 Bit Shell Extensions" Level="1" />
<?
endif?>
</
Feature>


So depending upon which feature user would select, it is referenced in InstallExecuteSequence table to schedule the CustomActions accordingly:


    <InstallExecuteSequence>
      <
Custom Action="RegisterIsmShellMenu" After="CostFinalize">
        <![CDATA[
(NOT Installed) AND (&ShellExtx86=3) AND NOT(!ShellExtx86=3)]]>
      </
Custom>
     
      <?
if $(var.PACKAGE_TARGETPLATFORM)=x64?>
        <
Custom Action="RegisterIsmShellMenuX86" After="CostFinalize">
          <![CDATA[
(NOT Installed) AND (&ShellExtx64=3) AND NOT(!ShellExtx64=3)]]>
        </
Custom>
      <?
endif?>
    </
InstallExecuteSequence>


Important:The term "&ShellExtx86=3" means the action is to install the feature local. The term "NOT(!ShellExtx86=3)" means the feature is not installed local.


For more examples, see Examples of Conditional Statement Syntax.


For Conditional Statement Syntax, see Conditional Statement Syntax

Thursday, April 4, 2013

Making Merge Modules (msm) more configurable…

I rarely have to face this, but have to, that is: develop merge modules which can be configured at runtime. Recently, I developed a merge module which is to write it files location in an INI file. And the location of INI file can’t be hard coded inside. So using  Configuration and  Substitution elements, we can achieve this goal. Here is the code segment:

Wix Merge Module using Configuration and Substitution elements:

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Module Id="FasterGPS"
          Language="1033"
          Version="1.0.0.0">
    <Package Id="{4DDF212E-E8A8-47E6-A010-DDF8D763866C}"
             InstallerVersion="200"
             Manufacturer="PUT-COMPANY-NAME-HERE" />
    <Configuration Name="CONFIGURABLE_AT_INI_FOLDER"
                   Format="Key"
                   Type="Directory"
                   ContextData="IsolationDir"
                   DefaultValue="INSTALLDIR"
                   NonNullable="yes"
                   DisplayName="AT.ini Folder"
                   Description="AT.ini Folder" />
    <Substitution Table="Directory"
                  Row="INI_FOLDER"
                  Column="Directory_Parent"
                  Value="[=CONFIGURABLE_AT_INI_FOLDER]" />
    <Directory Id="TARGETDIR"
               Name="SourceDir">
      <Directory Id="INI_FOLDER" />
      <Directory Id="ME">
        <Component Id="FasterGPS.apk"
                   Guid="{B43FA0CC-4C3D-481B-8521-4667D7701D37}">
          <File Id="FasterGPS.apk"
                KeyPath="yes"
                Source="FasterGPS.apk" />
          <IniFile Id="MyLocationINI"
                   Action="createLine"
                   Directory="INI_FOLDER"
                   Key="CUSTOM_ROOT"
                   Name="MyConfig.ini"
                   Section="ALIAS"
                   Value="[ME]" />
        </Component>
      </Directory>
    </Directory>
  </Module>
</Wix>

Now when you use the merge module, you will have to define ConfigurationData element as mentioned below:

WIX passing Configuration Data to Merge Module:
<Directory Id="MY_INI_FOLDER" >
  <Component Id="MyINIFile"
             Guid="{52B0F9A2-E91C-4CE3-B3A8-0F29EC45DBF3}">
    <File Id="MyINIFile"
          KeyPath="yes"
          Source="MyConfig.ini" />
  </Component>
</Directory>
<Directory Id="SUB"
           Name="Sub apps">
  <Merge Id="FG"
         DiskId="1"
         SourceFile="FasterGPS.apk.msm"
         Language="1033" >
    <ConfigurationData Name="CONFIGURABLE_AT_INI_FOLDER"
                       Value="MY_INI_FOLDER" />
  </Merge>
</Directory>

At run time, value of "MY_INI_FOLDER" would be passed to merge module and would be received in CONFIGURABLE_AT_INI_FOLDER and would be Substituted in Directory Table record for "INI_FOLDER";

Monday, October 22, 2012

Wix: Giving default Users group full access to a folder.

Using VbScript Custom Action:

I had a situation in which Windows default Users group was required Full Permissions upon a folder which was creating by my installers. This is not a big problem. Actual problem which I was facing is that on German Windows, default Users group is not Users group. Its “Benutzer” there actually. So I had to use a vbscript while hardcoding group name and applying Windows built-in command cacls.exe (now its icacls.exe):

VbScript Custom Action:
  1. 'Reading the Locale Value to detect which Regional Language this Windows (NT)  have
  2. nLocale=WshShell.RegRead("HKEY_USERS\.DEFAULT\Control Panel\International\\Locale")
  3. if (nLocale="") or IsEmpty(nLocale) or IsNull(nLocale) then
  4.     nLocale=00000409
  5.     sUserGroup="Users"
  6. end if
  7.  
  8. if nLocale=00000409 then 'Its English
  9.     sUserGroup="Users"
  10. elseif nLocale=00000407 then 'Its German
  11.     sUserGroup="Benutzer"
  12. end if
  13.  
  14. 'Applying permissions
  15. if fso.FolderExists(sLocalRoot) then
  16.         'StrCommand = "cmd /c cacls.exe " & """" & sLocalRoot & """" & " /G EveryOne:F /E"
  17.         StrCommand = "/c cacls.exe " & """" & sLocalRoot & """" & " /G " & sUserGroup & ":F /E /T"    
  18.         
  19.         'Get the Windows Major Version from the Registry
  20.         sOSversion=WshShell.RegRead("HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion")
  21.         If CInt(sOSversion) >= 6 then
  22.             objShell.ShellExecute "cmd.exe ", StrCommand, "", "runas", 1
  23.         Else
  24.             WshShell.Run "Cmd.exe " & " " & StrCommand  
  25.         End If
  26. End if

 

Using Wix Util Extension:

Now using Wix Util extension, this has been shorten to:

1 <!--Referencing property defined by Util Extension-->
2 <PropertyRef Id="WIX_ACCOUNT_USERS"/>
3
4 <!-- +++++++++++++++++++++++++++++++ -Directory Structure +++++++++++++++++++++++++++++++ -->
5 <Directory Id="TARGETDIR" Name="SourceDir">
6 <Directory Id="DATASET" Name="MyDataSet">
7 <Component Id="DoPermissions" DiskId="1" Guid="{2E90BD04-2E1B-4CAE-994B-090D937F3E5A}">
8 <CreateFolder>
9 <util:PermissionEx ChangePermission="yes" GenericAll="yes" User="[WIX_ACCOUNT_USERS]" Traverse="yes" />
10 </CreateFolder>
11 </Component>
12 </Directory>

Property WIX_ACCOUNT_USERS would detect the default Users group and PermissionEx command would apply permissions using its value for a specific folder.


Note: You have to include WixUtilExtension with Candle and Light.

Wednesday, October 17, 2012

How to force GUI uninstall using ARPNOREMOVE property?

 

Background:

Whenever you install a product using its .msi installer, it shows itself in Add/Remove programs list. To modify it, you mostly have 3 options:

  1. Uninstall
  2. Change
  3. Repair
 

AddRemoveProgramsOptions
(Screen shot from Add Remove applet)

If you choose “Uninstall” from this menu, it would simply shows you the Windows Installer progress bar dialog and may remove your product during that i.e. no user input required. Here is the progress bar dialog you may have observed:

NoGUI_Uninstall

And if you choose option 3 i.e. Repair, same progress bar dialog would be presented while reinstalling your product.

In these two options, certain things are absent which are normally available in GUI mode. For instance, custom actions scheduled in InstallUISequence. Custom actions to be executed through Button click events in any dialog, properties defined during UI mode, custom dialogs etc. That is, this would simply skip all these. However custom action scheduled in InstallExecuteSequence may execute here.

However, if you choose option 2 i.e. Change, (Or double click on the program in Add/Remove list) you are presented with a Windows Installer Maintenance Dialog box which leads you to this option dialog:

image

Now again, you have 3 same options, but this time, there would be full GUI mode unlike only progress bar dialog.

Change will let you modify your installer product i.e. you may add/remove features etc.
Repair would simply reinstall the product with the options used to install it last time.
Remove would allow you to uninstall the product, but unlike simple progress bar mode, you will have a full GUI mode with progress bar, buttons etc.

 

Problem:

I got a situation in which I was asked to prevent the users from uninstalling the product using progress bar mode. And I also faced another problem with another product that whenever I was uninstalling using progress bar mode, it only shows that it is removing the product, but was doing nothing. However problems where not there whenever we used GUI mode.

So initially I defined ARPNOREMOVE like:

<Property Id="ARPNOREMOVE" Value="1" />

But this would actually remove the option to Uninstall your product from both Control Panel and its Maintenance Dialog box as shown in following shots:







image image

The only option left to remove this product was from original msi. It will always giving you the option Uninstall in its context menu, even if ARPNOREMOVE is define, as shown in this shot:
image


And this would remove product using progress bar mode.


But this has solved nothing. We want our users to use GUI mode and should be able to uninstall properly.


So to achieve this, I had to redefine Maintenance Dialog while enabling the Remove button forcefully using Condition element so that users would only be able to remove the product from Maintenance Dialog by clicking Remove button.




Maintenance Dialog Redefined



  1. <Dialog Id="My_MaintenanceTypeDlg" Width="370" Height="270" Title="!(loc.MaintenanceTypeDlg_Title)">

  2.         <Control Id="ChangeButton" Type="PushButton" X="40" Y="65" Width="80" Height="17" ToolTip="!(loc.MaintenanceTypeDlgChangeButtonTooltip)" Default="yes" Text="!(loc.MaintenanceTypeDlgChangeButton)">

  3.           <Publish Property="WixUI_InstallMode" Value="Change">1</Publish>

  4.           <Condition Action="disable">ARPNOMODIFY</Condition>

  5.         </Control>

  6.         <Control Id="ChangeText" Type="Text" X="60" Y="85" Width="280" Height="20" Text="!(loc.MaintenanceTypeDlgChangeText)">

  7.           <Condition Action="hide">ARPNOMODIFY</Condition>

  8.         </Control>

  9.         <Control Id="ChangeDisabledText" Type="Text" X="60" Y="85" Width="280" Height="20" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgChangeDisabledText)" Hidden="yes">

  10.           <Condition Action="show">ARPNOMODIFY</Condition>

  11.         </Control>

  12.         <Control Id="RepairButton" Type="PushButton" X="40" Y="118" Width="80" Height="17" ToolTip="!(loc.MaintenanceTypeDlgRepairButtonTooltip)" Text="!(loc.MaintenanceTypeDlgRepairButton)">

  13.           <Publish Property="WixUI_InstallMode" Value="Repair">1</Publish>

  14.           <Condition Action="disable">ARPNOREPAIR</Condition>

  15.         </Control>

  16.         <Control Id="RepairText" Type="Text" X="60" Y="138" Width="280" Height="30" Text="!(loc.MaintenanceTypeDlgRepairText)">

  17.           <Condition Action="hide">ARPNOREPAIR</Condition>

  18.         </Control>

  19.         <Control Id="RepairDisabledText" Type="Text" X="60" Y="138" Width="280" Height="30" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgRepairDisabledText)" Hidden="yes">

  20.           <Condition Action="show">ARPNOREPAIR</Condition>

  21.         </Control>

  22.         <Control Id="RemoveButton" Type="PushButton" X="40" Y="171" Width="80" Height="17" ToolTip="!(loc.MaintenanceTypeDlgRemoveButtonTooltip)" Text="!(loc.MaintenanceTypeDlgRemoveButton)">

  23.           <Publish Property="WixUI_InstallMode" Value="Remove">1</Publish>

  24.         </Control>

  25.         <Control Id="RemoveText" Type="Text" X="60" Y="191" Width="280" Height="20" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgRemoveText)">

  26.         </Control>

  27.         <Control Id="RemoveDisabledText" Type="Text" X="60" Y="191" Width="280" Height="20" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgRemoveDisabledText)" Hidden="yes">

  28.           <Condition Action="hide">ARPNOREMOVE</Condition>

  29.         </Control>

  30.         <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />

  31.         <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Disabled="yes" Text="!(loc.WixUINext)" />

  32.         <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">

  33.           <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>

  34.         </Control>

  35.         <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.MaintenanceTypeDlgBannerBitmap)" />

  36.         <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />

  37.         <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />

  38.         <Control Id="Title" Type="Text" X="15" Y="6" Width="340" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgTitle)" />

  39.         <Control Id="Description" Type="Text" X="25" Y="23" Width="340" Height="20" Transparent="yes" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgDescription)" />

  40.       </Dialog>





Please note that following are the original lines which are modified in above code segment:


<Control Id="RemoveButton" Type="PushButton" X="40" Y="171" Width="80" Height="17" ToolTip="!(loc.MaintenanceTypeDlgRemoveButtonTooltip)" Text="!(loc.MaintenanceTypeDlgRemoveButton)">
<Publish Property="WixUI_InstallMode" Value="Remove">1</Publish>
<Condition Action="disable">ARPNOREMOVE</Condition>
</Control>
<Control Id="RemoveText" Type="Text" X="60" Y="191" Width="280" Height="20" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgRemoveText)">
<Condition Action="hide">ARPNOREMOVE</Condition>
</Control>
<Control Id="RemoveDisabledText" Type="Text" X="60" Y="191" Width="280" Height="20" NoPrefix="yes" Text="!(loc.MaintenanceTypeDlgRemoveDisabledText)" Hidden="yes">
<Condition Action="show">ARPNOREMOVE</Condition>
</Control>


That is, RemoveButton and RemoveText controls are no more bound to APRNOREMOVE property, so these would be active in any case, while RemoveDisabledText would be hidden if ARPNOREMOVE property is present.


Now that would result in forcing the user to be only able to remove product using our Maintenance Dialog –> Remove button which makes sure that we would have InstallUISequence and any other UI related thingies available.


TIP: From above dialog code, you can also extract the use of ARPNOREPAIR and ARPNOMODIFY properties in different ways according to your requirements.