WIX Custom Action (immediate, deffered, rollback)

Following content is directly reprinted from From MSI to WiX, Part 19 - The Art of Custom Action, Part 1

Author:Alex Shevchuk

Introduction

Today we will start exploring custom actions, how to write them, what makes custom action good custom action and everything else related to custom actions.

Let‘s start with very simple sample.  We have an application which creates a new file during run-time and this file is not part of originally installed files.  On uninstall we need to delete this file as well.  The name of the file is well known.

Here is the source for our test console application:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            using (StreamWriter fs = File.CreateText("Test.txt"))
            {
                fs.Write("Hello");
            }

            Console.WriteLine("Bye");
        }
    }
}

During run time our program creates Test.txt file which we want to remove on our test application uninstall.  Because new file name is well known, all we need to do is to add RemoveFile element:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
  <Product Id="{2F59639E-4626-44f8-AAF0-EE375B766221}"
           Name="Test Application"
           Language="1033"
           Version="0.0.0.0"
           Manufacturer="Your Company">
    <Package Id="????????-????-????-????-????????????"
             Description="Shows how to delete run-time file with known file name."
             Comments="This will appear in the file summary stream."
             InstallerVersion="200"
             Compressed="yes" />

    <Media Id="1" Cabinet="Product.cab" EmbedCab="yes" />

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLLOCATION" Name="Test1" LongName="Test for removal of run time file">

          <Component Id="ProductComponent"
                     Guid="{8F4CC43A-9290-4c93-9B97-B9FC1C3579CC}">
            <File Id="Test.exe" DiskId="1" Name="Test.exe"
                  Source="..\Test\bin\Debug\Test.exe" KeyPath="yes" />

            <RemoveFile Id="Test.txt" On="uninstall" Name="Test.txt" />
          </Component>

        </Directory>
      </Directory>
    </Directory>

    <Feature Id="ProductFeature" Title="Feature Title" Level="1">
      <ComponentRef Id="ProductComponent" />
    </Feature>
  </Product>
</Wix>

To test our install, install application, go to installation folder and run Test.exe.  Application will create Test.txt file.  Uninstall application and make sure that installation folder is gone.

There is no need for custom action in here.  Things are more complicated if our program creates file or files with unknown at installation creation package time.

Here is an updated version of Test application.  It creates a file with arbitrary name:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            using (StreamWriter fs = File.CreateText(DateTime.Now.Ticks.ToString()+ ".log"))
            {
                fs.Write("Hello");
            }

            Console.WriteLine("Bye");
        }
    }
}

Because we don‘t know the name of the new file we need custom action which will find this new file and delete it.  This custom action must be deferred and, for simplicity sake, we will be using VBScript custom action type 38 (seeintroduction to custom actions for more details).  Because our custom action is deferred, we will pass all required parameters through CustomActionData property (see here for details).

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
  <Product Id="{2F59639E-4626-44f8-AAF0-EE375B766221}"
           Name="Test Application"
           Language="1033"
           Version="0.0.0.0"
           Manufacturer="Your Company">
    <Package Id="{58BCF2DD-16A0-4654-B289-F13AADA240CD}"
             Description="Shows how to delete run-time file with known file name."
             Comments="This will appear in the file summary stream."
             InstallerVersion="200"
             Compressed="yes" />

    <Media Id="1" Cabinet="Product.cab" EmbedCab="yes" />

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLLOCATION" Name="Test1" LongName="Test for removal of run time file">

          <Component Id="ProductComponent"
                     Guid="{8F4CC43A-9290-4c93-9B97-B9FC1C3579CC}">
            <File Id="Test.exe" DiskId="1" Name="Test.exe"
                  Source="..\Test\bin\Debug\Test.exe" KeyPath="yes" />
          </Component>

        </Directory>
      </Directory>
    </Directory>

    <CustomAction Id="RemoveTempFile" Script="vbscript" Execute="deferred">
      <![CDATA[
      On error resume next
      Dim fso
      Set fso = CreateObject("Scripting.FileSystemObject")
      fso.DeleteFile(Session.Property("CustomActionData") + "*.log")
      Set fso = Nothing
      ]]>
    </CustomAction>

    <CustomAction Id="SetCustomActionData"
                  Property="RemoveTempFile" Value="[INSTALLLOCATION]" />

    <InstallExecuteSequence>
      <Custom Action="SetCustomActionData" Before="RemoveTempFile">REMOVE="ALL"</Custom>
      <Custom Action="RemoveTempFile" Before="RemoveFiles">REMOVE="ALL"</Custom>
    </InstallExecuteSequence>

    <Feature Id="ProductFeature" Title="Feature Title" Level="1">
      <ComponentRef Id="ProductComponent" />
    </Feature>
  </Product>
</Wix>

Few things to notice in here.  First of all, we need to set CustomActionData property before our RemoveTempFile custom action.  RemoveTempFile custom action must be scheduled before RemoveFiles standard action because this standard action decides on whether installation folder will be removed or not.  If at the end of RemoveFiles standard action folder is not empty, folder won‘t be deleted. That‘s why we want to delete extra files before RemoveFiles standard action.  Also, both custom actions are conditioned to run on complete uninstall only.

Let‘s test our installer.  Install application and run Test.exe a few times.  After every run you will see a new file being created.  Now uninstall the application.  Installation folder should disappear.

So, are we successfully solved the problem?  Let‘s create an uninstall failure condition.  In all good installation packages all changes made during uninstall should be rolled back in case of uninstall failure.  Let‘s see how we are doing.  The easiest way to create an uninstall failure is to schedule deferredcustom action type 54 right before InstallFinalize standard action.  We can‘t use custom action type 19 because this custom action does not allow setting execution option (in other words, it is always immediate custom action) and we want to fail an installation after we ran our RemoveTempFile custom action.

Here is our updated WiX source file:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
  <Product Id="{2F59639E-4626-44f8-AAF0-EE375B766221}"
           Name="Test Application"
           Language="1033"
           Version="0.0.0.0"
           Manufacturer="Your Company">
    <Package Id="{58BCF2DD-16A0-4654-B289-F13AADA240CD}"
             Description="Shows how to delete run-time file with known file name."
             Comments="This will appear in the file summary stream."
             InstallerVersion="200"
             Compressed="yes" />

    <Media Id="1" Cabinet="Product.cab" EmbedCab="yes" />

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLLOCATION" Name="Test1" LongName="Test for removal of run time file">

          <Component Id="ProductComponent"
                     Guid="{8F4CC43A-9290-4c93-9B97-B9FC1C3579CC}">
            <File Id="Test.exe" DiskId="1" Name="Test.exe"
                  Source="..\Test\bin\Debug\Test.exe" KeyPath="yes" />
          </Component>

        </Directory>
      </Directory>
    </Directory>

    <CustomAction Id="RemoveTempFile" Script="vbscript" Execute="deferred">
      <![CDATA[
      On error resume next
      Dim fso
      Set fso = CreateObject("Scripting.FileSystemObject")
      fso.DeleteFile(Session.Property("CustomActionData") + "*.log")
      Set fso = Nothing
      ]]>
    </CustomAction>

    <CustomAction Id="SetCustomActionData"
                  Property="RemoveTempFile" Value="[INSTALLLOCATION]" />

    <Property Id="FailureProgram">
      <![CDATA[
      Function Main()
        Main = 3
      End Function
      ]]>
    </Property>

    <CustomAction Id="FakeFailure"
                  VBScriptCall="Main"
                  Property="FailureProgram"
                  Execute="deferred" />

    <InstallExecuteSequence>
      <Custom Action="SetCustomActionData" Before="RemoveTempFile">REMOVE="ALL"</Custom>
      <Custom Action="RemoveTempFile" Before="RemoveFiles">REMOVE="ALL"</Custom>

      <Custom Action=‘FakeFailure‘ Before=‘InstallFinalize‘>REMOVE="ALL" AND TESTFAIL</Custom>
    </InstallExecuteSequence>

    <Feature Id="ProductFeature" Title="Feature Title" Level="1">
      <ComponentRef Id="ProductComponent" />
    </Feature>
  </Product>
</Wix>

FakeFailure custom action is running VBScript stored in the FailureProgram property.  This script just returns the value 3 which means that script has failed.  This return code will force Windows Installer engine to fail an uninstall and roll back all changes made up to this point.  We also put a condition on FakeFailure custom action to run on uninstall and only if TESTFAIL property is defined.  The reason for this is that we don‘t want our installation package to be stuck in uninstall failure.  Doing that will require use of MsiZap tool to remove our installation from the system.  Instead, we allow uninstall to succeed normally, but if we want to fail an installation we must use command line to do that:

msiexec /uninstall Project.msi TESTFAIL=YES

Let‘s install our test application, run Test.exe few times.  Make sure that we have few files created in the same folder where Test.exe is located.  Now try to uninstall program by running above mentioned command in the command window.  Installation will fail.  Go back to installation folder where Test.exe file is located and observe that all files created by Test.exe are gone.

Depending on what those files are that may be OK, but that could be a big problem.  Imagine, that our Test.exe is a personal finance management software (something like Microsoft Money) and it stores last synchronization  with bank‘s server data.  By removing those extra files we are forcing our software to resynch the whole transaction history on the next run.  That‘s bad.

So, how we can fix the problem.  Remember, that in addition to immediate and deferred custom actions we have also rollback (runs during rollback installation phase) and commit (runs at the end of successful transaction) custom actions.  Changes that we are going to make are:

  • Deferred custom action will move files to be deleted into temporary folder
  • Commit custom action will delete files from the temporary folder
  • Rollback custom action will move files back from temporary folder to installation folder

Because scripts will be larger than custom action type 38 can store we will be using custom action type 6 instead (again, see introduction to custom actionsfor more details).

Here is the deferred custom action (MoveTempFile.vbs):

Function Main()

    On Error Resume Next

    Dim properties, tempFile, fso, folder

    Const msiDoActionStatusSuccess = 1

    properties = Split(Session.Property("CustomActionData"), ";", -1, 1)
    tempFile = properties(1) & "{42A7DCCB-868B-4D11-BBBE-5A32B8DF5CC9}\"

    Set fso = CreateObject("Scripting.FileSystemObject")
    Set folder = fso.CreateFolder(tempFile)
    fso.DeleteFile tempFile & "*.log", true
    fso.MoveFile properties(0) & "*.log", tempFile
    Set folder = Nothing
    Set fso = Nothing

    Main = msiDoActionStatusSuccess

End Function

In CustomActionData we will pass both INSTALLLOCATION and TempFolderproperties.  This script will create subfolder in the TempFolder.  Subfolder‘s name is the UpgradeCode Guid.  It will move log files from installation folder to temporary folder.

Commit script (CommitTempFile.vbs) will delete log files from temporary folder:

Function Main()

    On Error Resume Next

    Dim properties, tempFile, fso, folder

    Const msiDoActionStatusSuccess = 1

    properties = Split(Session.Property("CustomActionData"), ";", -1, 1)
    tempFile = properties(1) & "{42A7DCCB-868B-4D11-BBBE-5A32B8DF5CC9}\"

    Set fso = CreateObject("Scripting.FileSystemObject")
    fso.DeleteFile tempFile & "*.log", true
    Set fso = Nothing

    Main = msiDoActionStatusSuccess

End Function

And Rollback script will move files back from temporary folder to installation folder:

Function Main()

    On Error Resume Next

    Dim properties, tempFile, fso

    Const msiDoActionStatusSuccess = 1

    properties = Split(Session.Property("CustomActionData"), ";", -1, 1)
    tempFile = properties(1) & "{42A7DCCB-868B-4D11-BBBE-5A32B8DF5CC9}\"

    Set fso = CreateObject("Scripting.FileSystemObject")
    fso.MoveFile tempFile & "*.log", properties(0)
    Set fso = Nothing

    Main = msiDoActionStatusSuccess

End Function

Here is the updated WiX source code:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
  <Product Id="{2F59639E-4626-44f8-AAF0-EE375B766221}"
           Name="Test Application"
           Language="1033"
           Version="0.0.0.0"
           Manufacturer="Your Company"
           UpgradeCode="{42A7DCCB-868B-4D11-BBBE-5A32B8DF5CC9}">
    <Package Id="{58BCF2DD-16A0-4654-B289-F13AADA240CD}"
             Description="Shows how to delete run-time file with known file name."
             Comments="This will appear in the file summary stream."
             InstallerVersion="200"
             Compressed="yes" />

    <Media Id="1" Cabinet="Product.cab" EmbedCab="yes" />

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLLOCATION" Name="Test1" LongName="Test for removal of run time file">

          <Component Id="ProductComponent"
                     Guid="{8F4CC43A-9290-4c93-9B97-B9FC1C3579CC}">
            <File Id="Test.exe" DiskId="1" Name="Test.exe"
                  Source="..\Test\bin\Debug\Test.exe" KeyPath="yes" />
          </Component>

        </Directory>
      </Directory>
    </Directory>

    <Binary Id="MoveTempFileScript" SourceFile="MoveTempFile.vbs" />
    <Binary Id="CommitTempFileScript" SourceFile="CommitTempFile.vbs" />
    <Binary Id="RollbackTempFileScript" SourceFile="RollbackTempFile.vbs" />

    <CustomAction Id="MoveTempFile"
                  BinaryKey="MoveTempFileScript"
                  VBScriptCall="Main"
                  Execute="deferred"
                  Return="check" />

    <CustomAction Id="CommitTempFile"
                  BinaryKey="CommitTempFileScript"
                  VBScriptCall="Main"
                  Execute="commit"
                  Return="check" />

    <CustomAction Id="RollbackTempFile"
                  BinaryKey="RollbackTempFileScript"
                  VBScriptCall="Main"
                  Execute="rollback"
                  Return="check" />

    <CustomAction Id="SetMoveData"
                  Property="MoveTempFile" Value="[INSTALLLOCATION];[TempFolder]" />

    <CustomAction Id="SetCommitData"
                  Property="CommitTempFile" Value="[INSTALLLOCATION];[TempFolder]" />

    <CustomAction Id="SetRollbackData"
                  Property="RollbackTempFile" Value="[INSTALLLOCATION];[TempFolder]" />

    <Property Id="FailureProgram">
      <![CDATA[
      Function Main()
        Main = 3
      End Function
      ]]>
    </Property>

    <CustomAction Id="FakeFailure"
                  VBScriptCall="Main"
                  Property="FailureProgram"
                  Execute="deferred" />

    <InstallExecuteSequence>
      <Custom Action="SetMoveData" Before="MoveTempFile">REMOVE="ALL"</Custom>
      <Custom Action="MoveTempFile" Before="RemoveFiles">REMOVE="ALL"</Custom>

      <Custom Action="SetCommitData" Before="CommitTempFile">REMOVE="ALL"</Custom>
      <Custom Action="CommitTempFile" After="MoveTempFile">REMOVE="ALL"</Custom>

      <Custom Action="SetRollbackData" Before="RollbackTempFile">REMOVE="ALL"</Custom>
      <Custom Action="RollbackTempFile" After="MoveTempFile">REMOVE="ALL"</Custom>

      <Custom Action=‘FakeFailure‘ Before=‘InstallFinalize‘>REMOVE="ALL" AND TESTFAIL</Custom>
    </InstallExecuteSequence>

    <Feature Id="ProductFeature" Title="Feature Title" Level="1">
      <ComponentRef Id="ProductComponent" />
    </Feature>
  </Product>
</Wix>

You can test it now to make sure that on uninstall failure all files get restored.

So, are we done yet?  Not quite.  Why?  Because there is much better alternativefor this type of tasks.  What we can do is populate RemoveFile table, just like WiX did for us in our first sample when we wanted to remove file with well-known name.  Just open msi file from Step 1 using Orca tool.  You will see RemoveFiletable on the left:

FileKey Component_ FileName DirProperty InstallMode
Test.txt ProductComponent Test.txt INSTALLLOCATION 2

Time to make an excuse:  Those who pay attention to what they read have alredy noticed that FileName column of RemoveFile table has WildCardFilename type and therefore allows using wildcards.  So, why I did not make my component something like this and called it done:

<Component Id="ProductComponent" Guid="{8F4CC43A-9290-4c93-9B97-B9FC1C3579CC}">
            <File Id="Test.exe" DiskId="1" Name="Test.exe" Source="..\Test\bin\Debug\Test.exe" KeyPath="yes" />
            <RemoveFile Id="RemoveTempFiles" On="uninstall" Name="*.log" />
</Component>

Well, my goal was to show why do we need rollback and commit custom actions.  Let‘s imagine for a moment that we are dealing with unknown at compile time set of file extensions and continue with our discussion.

So, what we are going to do with our immediate custom action is populate this table with the names of the files created by our application during run-time.  After that, Windows Installer will take care of Commit and Rollback actions, so we won‘t need these custom actions anymore.  Custom action must be an immediate custom action because during deferred phase session and database handle are no longer accessible.

Here is the source of the custom action:

Function Main()

    On Error Resume Next

    Dim fso, folder, files
    Dim DirProperty, ComponentId, InstallFolder
    Dim database, view, record

    Const msiDoActionStatusSuccess = 1
    Const msiViewModifyInsertTemporary = 7

    InstallFolder = Session.Property("INSTALLLOCATION")
    ComponentId = "ProductComponent"
    DirProperty = "INSTALLLOCATION"

    Set database = Session.Database
    Set view = database.OpenView("SELECT `FileKey`, `Component_`, `FileName`, `DirProperty`, `InstallMode` FROM `RemoveFile`")
    view.Execute

    Set fso = CreateObject("Scripting.FileSystemObject")
    Set folder = fso.GetFolder(InstallFolder)
    Set files = folder.Files
    For Each file in files
        If fso.GetExtensionName(file.name) = "log" Then
            Set record = installer.CreateRecord(5)
            record.StringData(1) = file.name
            record.StringData(2) = ComponentId
            record.StringData(3) = file.name
            record.StringData(4) = DirProperty
            record.IntegerData(5) = 2

            view.Modify msiViewModifyInsertTemporary, record
        End If
    Next

    view.Close
    Set view = Nothing
    database.Commit
    Set database = Nothing

    Set files = Nothing
    Set folder = Nothing
    Set fso = Nothing

    Main = msiDoActionStatusSuccess

End Function

It opens installer‘s database and inserts temporary records into RemoveFiletable.

Here is updated WiX source code:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
  <Product Id="{2F59639E-4626-44f8-AAF0-EE375B766221}"
           Name="Test Application"
           Language="1033"
           Version="0.0.0.0"
           Manufacturer="Your Company"
           UpgradeCode="{42A7DCCB-868B-4D11-BBBE-5A32B8DF5CC9}">
    <Package Id="{58BCF2DD-16A0-4654-B289-F13AADA240CD}"
             Description="Shows how to delete run-time file with known file name."
             Comments="This will appear in the file summary stream."
             InstallerVersion="200"
             Compressed="yes" />

    <Media Id="1" Cabinet="Product.cab" EmbedCab="yes" />

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLLOCATION" Name="Test1" LongName="Test for removal of run time file">

          <Component Id="ProductComponent"
                     Guid="{8F4CC43A-9290-4c93-9B97-B9FC1C3579CC}">
            <File Id="Test.exe" DiskId="1" Name="Test.exe"
                  Source="..\Test\bin\Debug\Test.exe" KeyPath="yes" />

            <!--
            Uncomment next line and comment out MoveTempFile custom action
            to test wildcard file removal option
            <RemoveFile Id="RemoveTempFiles" On="uninstall" Name="*.log" />
            -->
          </Component>

        </Directory>
      </Directory>
    </Directory>

    <EnsureTable Id="RemoveFile" />

    <Binary Id="MoveTempFileScript" SourceFile="MoveTempFile.vbs" />

    <CustomAction Id="MoveTempFile"
                  BinaryKey="MoveTempFileScript"
                  VBScriptCall="Main"
                  Execute="immediate"
                  Return="check" />

    <Property Id="FailureProgram">
      <![CDATA[
      Function Main()
        Main = 3
      End Function
      ]]>
    </Property>

    <CustomAction Id="FakeFailure"
                  VBScriptCall="Main"
                  Property="FailureProgram"
                  Execute="deferred" />

    <InstallExecuteSequence>
      <Custom Action="MoveTempFile" Before="RemoveFiles">REMOVE="ALL"</Custom>

      <Custom Action=‘FakeFailure‘ Before=‘InstallFinalize‘>REMOVE="ALL" AND TESTFAIL</Custom>
    </InstallExecuteSequence>

    <Feature Id="ProductFeature" Title="Feature Title" Level="1">
      <ComponentRef Id="ProductComponent" />
    </Feature>
  </Product>
</Wix>

One thing to notice here is that I am using new EnsureTable element.  WiX does not create standard tables if they are empty.  Because we don‘t have anywhere in our WiX code RemoveFile elements WiX will not create RemoveFile table.  HavingEnsureTable element tells to WiX compiler that RemoveFile table must be created even though it is empty.

In Part 2 we will try to make this custom action reusable.

Code is attached.

The Art of Custom Actions1.zip

时间: 2024-07-30 13:43:34

WIX Custom Action (immediate, deffered, rollback)的相关文章

SharePoint 2010/SharePoint 2013 Custom Action: 基于Site Collection 滚动文字的通知.

应用场景: 有时候我们的站点需要在每个页面实现滚动文字的通知,怎么在不修改Master Page的情况下实现这个功能?我们可以使用Javascript 和 Custom Action 来实现. 创建一个Custom Action.主要使用到 Location = 'ScriptLink' 属性, 该属性可以动态的加载JavaScript 文件链接和代码块到模板页.代码如下: <Elements xmlns="http://schemas.microsoft.com/sharepoint/&

Dynamics CRM 2015/2016 Web API:Unbound Custom Action 和 Bound Custom Action

今天我们再来看看Bound/Unbound Custom Action吧,什么是Custom Action?不知道的小伙伴们就out了,Dynamics CRM 2013就有了这个功能啦.和WhoAmI这类消息一样,我们都可以通过代码去调用它们,只不过呢,今天我要给大家讲讲怎么用Web API的方式去调用它们. Custom Action也被划分为Bound和Unbound两种类型了,它们的具体含义和之前讲的Function和Action没有区别,唯一的区别就是,这里的Custom Action

Custom Action : dynamic link library

工具:VS2010, Installshield 2008 实现功能: 创建一个C++ win32 DLL的工程,MSI 工程需要调用这个DLL,并将MSI工程中的两个参数,传递给DLL, 参数1:Property 表中的 ProductName 参数2:操作 MSI 工程的 installer database 的 Handle 对参数1的操作:通过对话框的方式显示出来. 对参数2的操作:读取 Property 表中的 ProductName 属性,通过对话框的方式显示出来. 步骤一.VS20

SharePoint 2013 - User Custom Action

1. User Custom Action包含Ribbon和ECB,以及Site Action菜单等: 2. 系统默认ECB的Class为: ms-core-menu-box --> ECB所在的div使用的class ms-core-menu-list --> ECB所在的ul使用的class ms-core-menu-item --> ECB每一条item所在的li使用的class ms-core-menu-link --> ECB每一条item所在的a链接使用的class m

Fix: When installing msi, custom action about deleting registy key does not work

Problem description: The problem may happen when UAC is enabled, it is usually because of the insufficient right to delete the registry key. Solution: 1. Run installer in command prompt as administrator 2. Set custom action In-script execution to be

在VS2012里创建SharePoint Ribbon Custom Action

原文地址:Creating SharePoint Ribbon CustomActions with Visual Studio 2012 本文由SPFarmer翻译 在SharePoint 2010里,创建一个ribbon custom actions并不是一个愉快的经历.你需要知道内部的XML的操作,以及选择正确CommandUIDefinition Id的后台的原理.我们的社区有一些工具,比如 CKSDEV 可以帮一些忙.现在我们高兴的看到,有了一个新的Ribbon Custom Acti

SVN安装过程出现“Custom action InstallWMISchemaExecute failed:服务不存在,或已被标记为删除”

在安装SVN服务端的时候,总是出现下面的错误,进而停止安装.具体的错误如下: 安装了好多的版本,都出现了这样的问题.我的系统是window7 64位的. 哪位路过的大神给评论一下,给点建议.帮我解决一下!在此谢谢啦!

WiX: uninstall older version of the application

I have installer generated by WiX and I want it to ask: "You have already installed this app. Do you want to uninstall it?" when run. Currently it installs the app once more and uninstalls it incorrectly if there was another version installed be

[笔记]WiX制作msi安装包的例子

WiX是制作msi安装文件的工具,看了半天文档,感觉没有什么比一个例子更简单粗暴的了. <?xml version="1.0" encoding="UTF-8"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> <Product Id="*" Name="HelloMSI" Language="1033&