msi安装后运行exe吗?

问题描述 投票:0回答:8

使用 Visual Studio 2008 创建一个 msi,通过安装项目来部署我的程序。我需要知道如何让 msi 运行刚刚安装的 exe。自定义动作?如果是这样,请解释在哪里/如何。谢谢。

visual-studio visual-studio-2008 windows-installer setup-project custom-action
8个回答
81
投票

这是一个常见问题。我不使用“只是”自定义操作来做到这一点。我知道的唯一方法是在生成 .msi 后对其进行修改。我运行一个 Javascript 脚本作为构建后事件来做到这一点。它会在安装程序向导中插入一个新对话框,其中有一个复选框显示“启动应用程序 Foo?”。然后有一个自定义操作来运行应用程序(如果选中该复选框)。 它显示为安装向导序列中的最后一个屏幕。看起来像这样:

alt text

这是我用来修改MSI的脚本:

// EnableLaunchApplication.js <msi-file> // Performs a post-build fixup of an msi to launch a specific file when the install has completed // Configurable values var checkboxChecked = true; // Is the checkbox on the finished dialog checked by default? var checkboxText = "Launch [ProductName]"; // Text for the checkbox on the finished dialog var filename = "WindowsApplication1.exe"; // The name of the executable to launch - change this to match the file you want to launch at the end of your setup // Constant values from Windows Installer var msiOpenDatabaseModeTransact = 1; var msiViewModifyInsert = 1; var msiViewModifyUpdate = 2; var msiViewModifyAssign = 3; var msiViewModifyReplace = 4; var msiViewModifyDelete = 6; if (WScript.Arguments.Length != 1) { WScript.StdErr.WriteLine(WScript.ScriptName + " file"); WScript.Quit(1); } var filespec = WScript.Arguments(0); var installer = WScript.CreateObject("WindowsInstaller.Installer"); var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact); var sql; var view; var record; try { var fileId = FindFileIdentifier(database, filename); if (!fileId) throw "Unable to find '" + filename + "' in File table"; WScript.Echo("Updating the Control table..."); // Modify the Control_Next of BannerBmp control to point to the new CheckBox sql = "SELECT `Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help` FROM `Control` WHERE `Dialog_`='FinishedForm' AND `Control`='BannerBmp'"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); record.StringData(11) = "CheckboxLaunch"; view.Modify(msiViewModifyReplace, record); view.Close(); // Insert the new CheckBox control sql = "INSERT INTO `Control` (`Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help`) VALUES ('FinishedForm', 'CheckboxLaunch', 'CheckBox', '9', '201', '343', '12', '3', 'LAUNCHAPP', '{\\VSI_MS_Sans_Serif13.0_0_0}" + checkboxText + "', 'CloseButton', '|')"; view = database.OpenView(sql); view.Execute(); view.Close(); WScript.Echo("Updating the ControlEvent table..."); // Modify the Order of the EndDialog event of the FinishedForm to 1 sql = "SELECT `Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering` FROM `ControlEvent` WHERE `Dialog_`='FinishedForm' AND `Event`='EndDialog'"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); record.IntegerData(6) = 1; view.Modify(msiViewModifyReplace, record); view.Close(); // Insert the Event to launch the application sql = "INSERT INTO `ControlEvent` (`Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering`) VALUES ('FinishedForm', 'CloseButton', 'DoAction', 'VSDCA_Launch', 'LAUNCHAPP=1', '0')"; view = database.OpenView(sql); view.Execute(); view.Close(); WScript.Echo("Updating the CustomAction table..."); // Insert the custom action to launch the application when finished sql = "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES ('VSDCA_Launch', '210', '" + fileId + "', '')"; view = database.OpenView(sql); view.Execute(); view.Close(); if (checkboxChecked) { WScript.Echo("Updating the Property table..."); // Set the default value of the CheckBox sql = "INSERT INTO `Property` (`Property`, `Value`) VALUES ('LAUNCHAPP', '1')"; view = database.OpenView(sql); view.Execute(); view.Close(); } database.Commit(); } catch(e) { WScript.StdErr.WriteLine(e); WScript.Quit(1); } function FindFileIdentifier(database, fileName) { // First, try to find the exact file name var sql = "SELECT `File` FROM `File` WHERE `FileName`='" + fileName + "'"; var view = database.OpenView(sql); view.Execute(); var record = view.Fetch(); if (record) { var value = record.StringData(1); view.Close(); return value; } view.Close(); // The file may be in SFN|LFN format. Look for a filename in this case next sql = "SELECT `File`, `FileName` FROM `File`"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); while (record) { if (StringEndsWith(record.StringData(2), "|" + fileName)) { var value = record.StringData(1); view.Close(); return value; } record = view.Fetch(); } view.Close(); } function StringEndsWith(str, value) { if (str.length < value.length) return false; return (str.indexOf(value, str.length - value.length) != -1); }

我最初是从
Aaron Stebner 的博客

得到这个,然后修改了它。 将该 Javascript 文件保存到项目目录(与包含 .vdproj 的目录相同),将其命名为

ModifyMsiToEnableLaunchApplication.js

。对于每个独特的安装项目,您需要修改该脚本并将正确的 exe 名称放入其中。然后,您需要在安装项目中将构建后事件设置为:

cscript.exe "$(ProjectDir)ModifyMsiToEnableLaunchApplication.js" "$(BuiltOuputPath)"

请务必正确输入宏的名称 

$(BuiltOuputPath)。单词

Ouput
被 Microsoft 拼写错误,并且
Built
未拼写为
Build
!

应该这样做。

另请参阅

此修改不包括卸载时的“运行 Foo.exe”复选框。


20
投票
Visual Studio 安装程序 > 如何在安装程序结束时启动应用程序


12
投票
'FindFileIdentifier'

'StringEndsWith'
- 使用原始函数),它使我们能够更改控件的 Y 和高度,以及添加复选框控件可见性条件(请参阅
NEW - START
NEW - END
之间标记的 2 条注释):
// EnableLaunchApplication.js <msi-file>
// Performs a post-build fixup of an msi to launch a specific file when the install has completed


// Configurable values
var checkboxChecked = true;                     // Is the checkbox on the finished dialog checked by default?
var checkboxText = "Launch [ProductName]?";     // Text for the checkbox on the finished dialog
var filename = "*.exe";                     // The name of the executable to launch - change * to match the file name you want to launch at the end of your setup


// Constant values from Windows Installer
var msiOpenDatabaseModeTransact = 1;

var msiViewModifyInsert         = 1
var msiViewModifyUpdate         = 2
var msiViewModifyAssign         = 3
var msiViewModifyReplace        = 4
var msiViewModifyDelete         = 6



if (WScript.Arguments.Length != 1)
{
        WScript.StdErr.WriteLine(WScript.ScriptName + " file");
        WScript.Quit(1);
}

var filespec = WScript.Arguments(0);
var installer = WScript.CreateObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);

var sql
var view
var record

try
{
    var fileId = FindFileIdentifier(database, filename);
    if (!fileId)
            throw "Unable to find '" + filename + "' in File table";


    WScript.Echo("Updating the Control table...");
    // Modify the Control_Next of BannerBmp control to point to the new CheckBox
    sql = "SELECT `Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help` FROM `Control` WHERE `Dialog_`='FinishedForm' AND `Control`='BannerBmp'";
    view = database.OpenView(sql);
    view.Execute();
    record = view.Fetch();
    record.StringData(11) = "CheckboxLaunch";
    view.Modify(msiViewModifyReplace, record);
    view.Close();

    // NEW - START
    // Insert the new CheckBox control
    // I changed the value for Y below from 201 to 191 in order to make the checkbox more obvious to the user's eye. In order to do so, and avoid the controls 'BodyText' & 'BodyTextRemove' in the same form to
    // overlap the checkbox, I added yet 2 more sql statements that change the values of the heights for the 'BodyText' & 'BodyTextRemove' from 138 to 128. This way I can play around with the values without using
    // the Orca msi editor.
    var CheckBoxY = 191; //This was initially set to 201
    var NewHeight = 128; //This was initially set to 138
    sql = "INSERT INTO `Control` (`Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help`) VALUES ('FinishedForm', 'CheckboxLaunch', 'CheckBox', '9', '" + CheckBoxY + "', '343', '12', '3', 'LAUNCHAPP', '{\\VSI_MS_Sans_Serif13.0_0_0}" + checkboxText + "', 'CloseButton', '|')";
    view = database.OpenView(sql);
    view.Execute();
    view.Close();

    sql = "Select `Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help` FROM `Control` WHERE `Dialog_` = 'FinishedForm' AND `Control` = 'BodyText'";
    view = database.OpenView(sql);
    view.Execute();
    record = view.Fetch();
    record.IntegerData(7) = NewHeight;
    view.Modify(msiViewModifyReplace, record);
    view.Close();

    sql = "Select `Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help` FROM `Control` WHERE `Dialog_` = 'FinishedForm' AND `Control` = 'BodyTextRemove'";
    view = database.OpenView(sql);
    view.Execute();
    record = view.Fetch();
    record.IntegerData(7) = NewHeight;
    view.Modify(msiViewModifyReplace, record);
    view.Close();
    // NEW - END

    WScript.Echo("Updating the ControlEvent table...");
    // Modify the Order of the EndDialog event of the FinishedForm to 1
    sql = "SELECT `Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering` FROM `ControlEvent` WHERE `Dialog_`='FinishedForm' AND `Event`='EndDialog'";
    view = database.OpenView(sql);
    view.Execute();
    record = view.Fetch();
    record.IntegerData(6) = 1;
    view.Modify(msiViewModifyReplace, record);
    view.Close();

    // Insert the Event to launch the application
    sql = "INSERT INTO `ControlEvent` (`Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering`) VALUES ('FinishedForm', 'CloseButton', 'DoAction', 'VSDCA_Launch', 'LAUNCHAPP=1', '0')";
    view = database.OpenView(sql);
    view.Execute();
    view.Close();



    WScript.Echo("Updating the CustomAction table...");
    // Insert the custom action to launch the application when finished
    sql = "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES ('VSDCA_Launch', '210', '" + fileId + "', '')";
    view = database.OpenView(sql);
    view.Execute();
    view.Close();

    if (checkboxChecked)
    {
            WScript.Echo("Updating the Property table...");
            // Set the default value of the CheckBox
            sql = "INSERT INTO `Property` (`Property`, `Value`) VALUES ('LAUNCHAPP', '1')";
            view = database.OpenView(sql);
            view.Execute();
            view.Close();
    }


    // NEW - START
    WScript.Echo("Updating the ControlCondition table...");
    // Insert the conditions where the Launch Application Checkbox appears
    sql = "INSERT INTO `ControlCondition` (`Dialog_`, `Control_`, `Action`, `Condition`) VALUES ('FinishedForm', 'CheckboxLaunch', 'Show', 'REMOVE=\"\"')";
    view = database.OpenView(sql);
    view.Execute();
    view.Close();

    sql = "INSERT INTO `ControlCondition` (`Dialog_`, `Control_`, `Action`, `Condition`) VALUES ('FinishedForm', 'CheckboxLaunch', 'Hide', 'REMOVE&#060;&#062;\"\"')";
    view = database.OpenView(sql);
    view.Execute();
    view.Close();
    //NEW - END


    database.Commit();
}
catch(e)
{
    WScript.StdErr.WriteLine(e);
    WScript.Quit(1);
}



8
投票
“隐藏复选框错误”

,我发现了以下内容,上面的Cheeso'sMuleskinner's答案没有解释: 脚本的更改(由

Muleskinner

提供)将复选框的 Y 位置设置为 201(我猜控件的顶部 Y 像素)。如果您将 Y 更改为 151(以便将其垂直居中对齐),则会出现 bug “突然”。原因是MSI的Control表中还有一个控件,即

'BodyText' ('Dialog' field = 'FinishedForm')
,它的Y设置为63,高度设置为138。即
138 + 63 = 201
。因此,如果更改复选框的 Y 值,
'BodyText'
控件会与新添加的控件重叠,这就是用户需要将鼠标放在上面才能显示复选框的原因。如果您没有
'BodyText'
,或其字符数足够小,您可以更改(通过像我一样使用
Orca MSI
编辑器,或通过更改上面的脚本)这 2 个控件的 Ys 和高度,以便能够为新添加的复选框容纳不同的 Y 位置。这同样适用于控件:'BodyTextRemove',我们应该再次更改其高度值(在卸载期间出现)。
希望如此!这可以帮助所有有相同问题的用户,就像我对这个

“bug”

尽管如此,剧本做得非常好!

另一个问题是如何在卸载过程中使

Checkbox

不可见? 使用

Orca MSI

编辑器,我在 MSI 的 ControlCondition 表中添加了以下 2 行:

第 1 行(应显示控件时):

(Dialog)FinishedForm (Control)CheckboxLaunch (Action)Show (Condition)REMOVE=""

第 2 行(当控件应该不可见时):

(Dialog)FinishedForm (Control)CheckboxLaunch (Action)Hide (Condition)REMOVE<>""

P.S.

我正在 Windows 7 (x64) 上使用 VS 2010,但我相信这些也应该适用于以前的版本。


6
投票

更改以下行以使用“Line1”控件而不是“CloseButton”,以便控件的选项卡顺序全部连接。

sql = "INSERT INTO `Control` (`Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help`) VALUES ('FinishedForm', 'CheckboxLaunch', 'CheckBox', '9', '201', '343', '12', '3', 'LAUNCHAPP', '{\\VSI_MS_Sans_Serif13.0_0_0}" + checkboxText + "', 'CloseButton', '|')";



4
投票
'PostBuildEvent' failed with error code '1' 'Unspecified error'

错误,请将 PostBuildEvent 从 更改为 cscript.exe \"$(ProjectDir)ModifyMsiToEnableLaunchApplication.js\" \"$(BuiltOuputPath)\"

cscript.exe "$(ProjectDir)ModifyMsiToEnableLaunchApplication.js" "$(BuiltOuputPath)"

关于
隐藏复选框的bug

你可以将脚本的第54行编辑为: sql = "INSERT INTO `Control` (`Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help`) VALUES ('FinishedForm', 'CheckboxLaunch', 'CheckBox', '9', '201', '343', '12', '3', 'LAUNCHAPP', '{\\VSI_MS_Sans_Serif13.0_0_0}" + checkboxText + "', 'CloseButton', '|')";



3
投票

// post-build-script: CALL cscript.exe "$(ProjectDir)EnableLaunchApplication.js" "$(BuiltOuputPath)" // EnableLaunchApplication.js <msi-file> // Performs a post-build fixup of an msi to launch a specific file when the install has completed // Configurable values var checkboxChecked = true; // Is the checkbox on the finished dialog checked by default? var checkboxText = "Launch [ProductName]"; // Text for the checkbox on the finished dialog var filename = "YourApp.exe"; // The name of the executable to launch - change this to match the file you want to launch at the end of your setup // Constant values from Windows Installer var msiOpenDatabaseModeTransact = 1; var msiViewModifyInsert = 1 var msiViewModifyUpdate = 2 var msiViewModifyAssign = 3 var msiViewModifyReplace = 4 var msiViewModifyDelete = 6 if (WScript.Arguments.Length != 1) { WScript.StdErr.WriteLine(WScript.ScriptName + " file"); WScript.Quit(1); } var filespec = WScript.Arguments(0); var installer = WScript.CreateObject("WindowsInstaller.Installer"); var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact); var sql var view var record try { var fileId = FindFileIdentifier(database, filename); if (!fileId) throw "Unable to find '" + filename + "' in File table"; WScript.Echo("Updating the Control table..."); // Modify the Control_Next of BannerBmp control to point to the new CheckBox sql = "SELECT `Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help` FROM `Control` WHERE `Dialog_`='FinishedForm' AND `Control`='BannerBmp'"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); record.StringData(11) = "CheckboxLaunch"; view.Modify(msiViewModifyReplace, record); view.Close(); // Resize the BodyText and BodyTextRemove controls to be reasonable sql = "SELECT `Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help` FROM `Control` WHERE `Dialog_`='FinishedForm' AND `Control`='BodyTextRemove'"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); record.IntegerData(7) = 33; view.Modify(msiViewModifyReplace, record); view.Close(); sql = "SELECT `Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help` FROM `Control` WHERE `Dialog_`='FinishedForm' AND `Control`='BodyText'"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); record.IntegerData(7) = 33; view.Modify(msiViewModifyReplace, record); view.Close(); // Insert the new CheckBox control sql = "INSERT INTO `Control` (`Dialog_`, `Control`, `Type`, `X`, `Y`, `Width`, `Height`, `Attributes`, `Property`, `Text`, `Control_Next`, `Help`) VALUES ('FinishedForm', 'CheckboxLaunch', 'CheckBox', '18', '117', '343', '12', '3', 'LAUNCHAPP', '{\\VSI_MS_Sans_Serif13.0_0_0}" + checkboxText + "', 'Line1', '|')"; view = database.OpenView(sql); view.Execute(); view.Close(); WScript.Echo("Updating the ControlEvent table..."); // Modify the Order of the EndDialog event of the FinishedForm to 1 sql = "SELECT `Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering` FROM `ControlEvent` WHERE `Dialog_`='FinishedForm' AND `Event`='EndDialog'"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); record.IntegerData(6) = 1; view.Modify(msiViewModifyReplace, record); view.Close(); // Insert the Event to launch the application sql = "INSERT INTO `ControlEvent` (`Dialog_`, `Control_`, `Event`, `Argument`, `Condition`, `Ordering`) VALUES ('FinishedForm', 'CloseButton', 'DoAction', 'VSDCA_Launch', 'LAUNCHAPP=1', '0')"; view = database.OpenView(sql); view.Execute(); view.Close(); WScript.Echo("Updating the CustomAction table..."); // Insert the custom action to launch the application when finished sql = "INSERT INTO `CustomAction` (`Action`, `Type`, `Source`, `Target`) VALUES ('VSDCA_Launch', '210', '" + fileId + "', '')"; view = database.OpenView(sql); view.Execute(); view.Close(); if (checkboxChecked) { WScript.Echo("Updating the Property table..."); // Set the default value of the CheckBox sql = "INSERT INTO `Property` (`Property`, `Value`) VALUES ('LAUNCHAPP', '1')"; view = database.OpenView(sql); view.Execute(); view.Close(); } database.Commit(); } catch(e) { WScript.StdErr.WriteLine(e); WScript.Quit(1); } function FindFileIdentifier(database, fileName) { var sql var view var record // First, try to find the exact file name sql = "SELECT `File` FROM `File` WHERE `FileName`='" + fileName + "'"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); if (record) { var value = record.StringData(1); view.Close(); return value; } view.Close(); // The file may be in SFN|LFN format. Look for a filename in this case next sql = "SELECT `File`, `FileName` FROM `File`"; view = database.OpenView(sql); view.Execute(); record = view.Fetch(); while (record) { if (StringEndsWith(record.StringData(2), "|" + fileName)) { var value = record.StringData(1); view.Close(); return value; } record = view.Fetch(); } view.Close(); } function StringEndsWith(str, value) { if (str.length < value.length) return false; return (str.indexOf(value, str.length - value.length) != -1); }


2
投票

© www.soinside.com 2019 - 2024. All rights reserved.