Custom Action in Wix to manipulate string and set

2019-07-23 06:38发布

问题:

Hi I am using Wix to create an Installer that has to write a registry value with the path of the file that the installer copies on the user's system. The problem is that the registry entry should be written in this format

file:///C:/Program Files/....

In the Wix code project I have the INSTALLFOLDER Directory Id which points to

C:\Program Files\....

I am really struggling to convert the latter notation into former. I created a Custom Action hoping to set a property so that I can use that. Following is the code

Custom Action (separate DLL for now, can it be inlined?)

 public class CustomActions
    {
        [CustomAction]
        public static ActionResult CustomAction1(Session session)
        {
            session.Log("Begin CustomAction1");
            string origValue = session["INSTALLFOLDER"];
            MessageBox.Show(origValue);
            string retVal = origValue.Replace("\\", "//");
            MessageBox.Show(retVal);
            session["Custom_Prop"] = retVal;
            return ActionResult.Success;
        }
    }

And the Product.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">

  <Product Id="*" Name="SetupProject1" Language="1033" Version="1.2.0.0" Manufacturer="nik" UpgradeCode="4a74ff86-49a9-4011-9794-e1c18077172f">
    <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

    <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
    <MediaTemplate />

    <Feature Id="ProductFeature" Title="SetupProject1" Level="1">
      <ComponentGroupRef Id="ProductComponents" />
    </Feature>

    <InstallExecuteSequence>
      <Custom Action='FooAction' Before='InstallFinalize'>NOT Installed</Custom>
    </InstallExecuteSequence>

  </Product>

  <Fragment>
    <CustomAction Id='FooAction' BinaryKey='FooBinary' DllEntry='CustomAction1' Execute='immediate'
                  Return='check'/>

    <Binary Id='FooBinary' SourceFile='MyCustomAction.CA.dll'/>
  </Fragment>


  <Fragment>
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLFOLDER" Name="SetupProject1" />
      </Directory>
    </Directory>
  </Fragment>

  <Fragment>
    <Property Id="Custom_Prop" Value="[ProgramFilesFolder]"></Property>
    <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
      <!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
      <!-- <Component Id="ProductComponent"> -->
      <!-- TODO: Insert files, registry keys, and other resources here. -->
      <!-- </Component> -->
      <Component Id="cmp_Add_Mainfest_To_Registry" Guid="955A3A76-F010-4FCB-BCAF-B297AFD1C05B">

        <RegistryKey Root="HKCU" Key="SOFTWARE\company">

          <RegistryValue Name="LoadBehavior" Value="3" Type="integer" Action="write" />

          <RegistryValue Name="Manifest" Value="[Custom_Prop]" Type="string" Action="write"  KeyPath="yes"/>
        </RegistryKey>
      </Component>
    </ComponentGroup>
  </Fragment>
</Wix>

However when I run this setup the value written in the registry is the literal string [ProgramFolder] and not its evaluation into either C:\ or C:/

Can someone help?

回答1:

The reason why my code wasn't working was this line

 <InstallExecuteSequence>
      <Custom Action='FooAction' Before='InstallFinalize'>NOT Installed</Custom>
    </InstallExecuteSequence>

On changing the value of Before attribute as below made this work

<InstallExecuteSequence>
      <Custom Action='FooAction' Before='CostFinalize'>NOT Installed</Custom>
    </InstallExecuteSequence>

However given my needs were very simple I decided to not have a separate DLL for CustomAction and instead went ahead with a Custom Action in vbscript within the Wix Project. So now the code looks like

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">

  <Product Id="*" Name="SetupProject1" Language="1033" Version="1.3.1.0" Manufacturer="nik" UpgradeCode="4a74ff86-49a9-4011-9794-e1c18077172f">
    <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

    <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
    <MediaTemplate />

    <Feature Id="ProductFeature" Title="SetupProject1" Level="1">
      <ComponentGroupRef Id="ProductComponents" />
    </Feature>

     <InstallExecuteSequence>
      <Custom Action="VBScriptCommand" After="CostFinalize">NOT REMOVE</Custom>
    </InstallExecuteSequence>

  </Product>

  <Fragment>


    <CustomAction Id="VBScriptCommand" Script="vbscript">
      <![CDATA[         
    value = Session.Property("INSTALLFOLDER")
    origPath=Session.Property("INSTALLFOLDER")
    If Right(webdir, 1) = "\" Then
      value = Left(value, Len(value) - 1) 
    End If

    Session.Property("SOME_PROPERTY") = Replace(origPath,"\","//")      
  ]]>
    </CustomAction>

   </Fragment>

  <Fragment>
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLFOLDER" Name="SetupProject1" />
      </Directory>
    </Directory>
  </Fragment>

  <Fragment>
    <Property Id="Custom_Prop" Value="[ProgramFilesFolder]"></Property>
    <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">

      <Component Id="cmp_Add_Mainfest_To_Registry" Guid="955A3A76-F010-4FCB-BCAF-B297AFD1C05B">

        <RegistryKey Root="HKCU" Key="SOFTWARE\something">

          <RegistryValue Name="LoadBehavior" Value="3" Type="integer" Action="write" />
          <!--<RegistryValue Name="Manifest" Value="[#FILE_VstoManifest]|vstolocal" Type="string" Action="write" />-->
          <RegistryValue Name="Manifest" Value="file:///[SOME_PROPERTY]" Type="string" Action="write"  KeyPath="yes"/>
        </RegistryKey>
      </Component>
    </ComponentGroup>
  </Fragment>
</Wix>

Perhaps the purists won't like this but why use a Shot gun to kill a fly?



回答2:

Nikhil helped me with his answer. My installs all go to subfolders so when I find an old component I need the parent folder for install, so I came here for an answer.
Combined with this get parent I found how to get the parent folder since I have a known fixed install sub path.

    <!-- Set INSTALLFOLDER from SERVERINSTALLFOLDER without the \Server\ -->
    <CustomAction Id="VBScriptInstallFolderFromFoundServer" Script="vbscript">
      <![CDATA[         
        pathvalue = Session.Property("SERVERINSTALLFOLDER")
        if pathvalue <> "" Then
          Session.Property("INSTALLFOLDER") = Left(pathvalue,Len(pathvalue)-Len("\Server\"))
        End If
      ]]>
    </CustomAction>

Combined with Locate Installation directory of another product

    <Property Id="SERVERINSTALLFOLDER">
      <!-- Id="C_SERVER_SERVERHOST.EXE" Guid="{xxx GUID OF my exe component xxx}" -->
      <ComponentSearch Id="ServerComponentSearch" Type="file" Guid="{xxx GUID OF my exe component xxx}">
        <DirectorySearch Id="ServerComponentDirectorySearch" Depth="0" AssignToProperty="yes" />
      </ComponentSearch>
    </Property>

And with Wix remember property pattern storing the INSTALLFOLDER path in registry.
I can now update old, or install new getting the correct install path of previous install as suggestion.
Not quite the answer to the question but as I was lead here to get this, my answer will help others on the same path...
My InstallUISequence and InstallExecuteSequence :

      <!-- Save INSTALLFOLDER parameter to CMDLINE_INSTALLFOLDER -->
      <Custom Action='SaveCmdLineValue' Before='AppSearch' />
      <!-- Set INSTALLFOLDER from SERVERINSTALLFOLDER without the \Server\ -->
      <Custom Action="VBScriptInstallFolderFromFoundServer" After="AppSearch">
        SERVERINSTALLFOLDER
      </Custom>
      <!-- Set INSTALLFOLDER from parameter CMDLINE_INSTALLFOLDER -->
      <Custom Action='SetFromCmdLineValue' After='VBScriptInstallFolderFromFoundServer'>
        CMDLINE_INSTALLFOLDER
      </Custom>

And lastly... in Product I to refer to the Fragment I put these in:

    <!-- Install to previous install path From parameter, OR from found installation OR from registry -->
    <CustomActionRef Id='SaveCmdLineValue' />
    <PropertyRef Id='INSTALLFOLDER'/><!-- include Fragment -->
    <PropertyRef Id='SERVERINSTALLFOLDER'/><!-- include Fragment -->
    <CustomActionRef Id='VBScriptInstallFolderFromFoundServer' /><!-- include Fragment -->


标签: c# wix