Запустить exe после установки msi?
Использование Visual Studio 2008 для создания MSI для развертывания моей программы с проектом установки. Мне нужно знать, как заставить MSI запускать только что установленный exe-файл. Пользовательское действие? Если да, пожалуйста, объясните, где и как. Благодарю.
6 ответов
Это общий вопрос. Я делаю это не просто с помощью специальных действий. Единственный способ, которым я знаю, - это изменить.msi после того, как он был сгенерирован. Я запускаю сценарий Javascript как событие после сборки, чтобы сделать именно это. Он вставляет новое диалоговое окно в мастер установки с флажком "Запустить приложение Foo?". А затем есть пользовательское действие для запуска приложения, если флажок установлен.
Он отображается как последний экран в последовательности мастера установки. Выглядит так:
Это скрипт, который я использую для изменения 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);
}
Первоначально я получил это из блога Аарона Стебнера, а затем изменил его.
Сохраните этот файл Javascript в каталог проекта (тот же каталог, в котором содержится.vdproj), назовите его ModifyMsiToEnableLaunchApplication.js
, Для каждого уникального проекта установки вам нужно изменить этот скрипт и ввести в него правильное имя exe. И затем, вам нужно установить событие post-build в проекте установки так:
cscript.exe "$(ProjectDir)ModifyMsiToEnableLaunchApplication.js" "$(BuiltOuputPath)"
Обязательно наберите имя макроса $(BuiltOuputPath)
правильно. Слово Ouput
неправильно написано Microsoft, и Built
не пишется Build
!
Это должно сделать это.
См. Также: это изменение, которое не включает флажок "run Foo.exe" в UNINSTALL.
Это кажется НАМНОГО более простым решением: установщик Visual Studio> Как запустить приложение в конце установщика
ХОРОШО!!! Вот код (без 2 вспомогательных функций 'FindFileIdentifier' и 'StringEndsWith' в конце - используйте вместо них оригинальные), который дает нам возможность изменять Ys и Heights для элементов управления, а также добавлять условия видимости элемента управления Checkbox (см. 2 комментария, которые отмечены между "NEW - START" и "NEW - END"):
// EnableLaunchApplication.js
// 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<>\"\"')";
view = database.OpenView(sql);
view.Execute();
view.Close();
//NEW - END
database.Commit();
}
catch(e)
{
WScript.StdErr.WriteLine(e);
WScript.Quit(1);
}
Относительно "скрытой ошибки флажка" я выяснил следующее, что не объясняется ответами Чизо и Мулескиннера выше:
Изменение скрипта (предоставлено Muleskinner) устанавливает позицию флажка Y на 201 (я думаю, верхний пиксель Y для элемента управления). Если вы измените Y, скажем, на 151 (чтобы выровнять его по центру по вертикали), ошибка "внезапно" появится. Причина этого заключается в том, что в таблице элементов управления MSI есть еще один элемент управления, а именно "BodyText" (поле "Dialog" = "FinishedForm"), для которого Y установлен на 63, а его высота на 138. Это 138 + 63 = 201. Поэтому, если вы измените значение Y для флажка, элемент управления "BodyText" перекрывает вновь добавленный элемент управления, и поэтому пользователю необходимо навести указатель мыши, чтобы отобразить флажок. Если у вас нет "BodyText" или его количество символов достаточно мало, вы можете изменить (с помощью редактора Orca msi, как я, или изменив скрипт выше) Ys и Heights этих 2 элементов управления, чтобы иметь возможность и вместить другая позиция Y для вновь добавленного флажка. То же самое относится и к элементу управления: "BodyTextRemove", в котором снова мы должны изменить его значение высоты (которое появляется во время удаления)
Надеюсь, что это поможет всем пользователям, у которых был тот же вопрос, что и у меня об этой "ошибке"
Тем не менее, скрипт делает действительно хорошую работу!
Другой вопрос заключался в том, как сделать невидимым флажок во время процедуры удаления. С помощью редактора Orca msi я добавил следующие 2 строки в таблицу ControlCondition MSI:
Строка 1 (когда должен быть показан контроль):
(Диалог)FinishedForm (Управление)CheckboxLaunch (Действие) Показать (Условие)REMOVE=""
Строка 2 (когда контроль должен быть невидимым):
(Диалог)FinishedForm (Управление)CheckboxLaunch (Действие) Скрыть (Условие) УДАЛИТЬ<> ""
PS Я использую VS 2010 на Windows 7 (x64), но я считаю, что они должны работать и с предыдущими версиями.
В этом сценарии EnableLaunchApplication.js есть небольшая ошибка, из-за которой последовательность вкладок Control_Next неверна. Это вызовет код ошибки 2810 при запуске установки.
Измените следующую строку, чтобы использовать элемент управления "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',
'|')";
Относительно ошибки "PostBuildEvent" с кодом ошибки "1" "Ошибка неуказанного" измените PostBuildEvent с
cscript.exe \"$(ProjectDir)ModifyMsiToEnableLaunchApplication.js\" \"$(BuiltOuputPath)\"
в
cscript.exe "$(ProjectDir)ModifyMsiToEnableLaunchApplication.js" "$(BuiltOuputPath)"
Что касается скрытой ошибки флажка, вы можете отредактировать строку 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', '|')";
После предложения указанного пользователя3349200, вот завершенный сценарий JS без ошибки установки 2810.
// 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);
}
Да.. Я бы написал пользовательское действие и вставил его в конец таблицы InstallExecutionSequence