51

Utilisation de Visual Studio 2008 pour créer un fichier msi afin de déployer mon programme avec un projet d'installation. J'ai besoin de savoir comment faire fonctionner le msi avec l'exe qu'il vient d'installer. Une action personnalisée? Si oui, veuillez expliquer où et comment. Merci.Exécutez exe après l'installation de msi?

Répondre

75

Ceci est une question commune. Je ne le fais pas avec juste une action personnalisée. Le seul moyen que je connaisse est de modifier le .msi après qu'il a été généré. Je cours un script Javascript en tant qu'événement post-build pour faire exactement cela. Il insère une nouvelle boîte de dialogue dans l'assistant d'installation, avec une case à cocher indiquant "Lancer l'application Foo?". Et puis il y a une action personnalisée pour exécuter l'application, si la case est cochée.

Il apparaît comme le dernier écran de la séquence de l'assistant d'installation. Ressemble à ceci:

alt text


Voici le script que j'utilise pour modifier le 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); 
} 

Je suis à l'origine de ce Aaron Stebner's blog, puis modifié.

Enregistrez ce fichier Javascript dans le répertoire du projet (le même répertoire que contient .vdproj), nommez-le ModifyMsiToEnableLaunchApplication.js. Pour chaque projet d'installation unique, vous devez modifier ce script et y mettre le nom d'exe approprié. Et puis, vous devez définir l'événement après génération dans le projet d'installation d'être ceci:

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

Assurez-vous de taper le nom de la macro $(BuiltOuputPath) correctement. Le mot Ouput est mal orthographié par Microsoft, et Built n'est pas orthographié Build!

Que oughtta le faire.

Voir aussi: this modification qui n'inclut pas la case à cocher "run Foo.exe" sur UNINSTALL.

+3

Wow Cheeso ... joli script – Nestor

+1

comme un peu de magie. Je ne sais pas si MS a ajouté la capacité dans le projet d'installation pour VS2010. Je pense que c'est aussi possible de le faire avec WiX, mais je n'ai jamais utilisé Wix, donc ça marche pour moi. – Cheeso

+2

Si l'interface est cachée (msi étant poussé avec des commandes d'installation silencieuse) cela fonctionnera-t-il encore? – Shawn

2

Oui .. J'écrirait une action personnalisée, et le coller à la fin de la table InstallExecutionSequence

+1

Je ne vois pas InstallExecutionSequence table? Sous des actions personnalisées, je vois Installer, Commit, Rollback et désinstaller. L'action personnalisée pointe-t-elle sur la sortie du programme principal? – Shawn

+1

La table InstallExecutionSequence est interne au MSI et n'est pas exposée via Visual Studio. Vous aurez besoin d'un éditeur MSI comme Orca pour l'éditer. La modification manuelle des fichiers MSI n'est pas une tâche triviale. –

+1

Je suis d'accord: modifier les fichiers MSI à la main n'est pas facile. Ce n'est pas facile ou trivial d'utiliser Orca. Vous pouvez utiliser le script fourni ci-dessous pour automatiser la tâche. Rapide et facile, testé et éprouvé. – Cheeso

4

En ce qui concerne la 'PostBuildEvent' a échoué avec le code d'erreur '1' 'Erreur non spécifiée' d'erreur, changer le PostBuildEvent de

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

à

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

En ce qui concerne la case cachée bug vous pouvez modifier la ligne 54 du script pour devenir:

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', '|')"; 
+0

Merci pour les mises à jour. – Cheeso

8

En ce qui concerne le « bug case caché » Je compris ce qui suit qui est pas expliquée par Cheeso de et les réponses de Muleskinner ci-dessus:

Le changement du script (fourni par Muleskinner) place la position Y de la case à 201 (Je suppose que le top Y pixel pour le contrôle).Si vous changez Y, disons, 151 (pour l'aligner verticalement), le bug "soudainement" apparaît. La raison en est qu'il y a un autre contrôle dans la table de contrôle du msi, à savoir le 'BodyText' (champ 'Dialog' = 'FinishedForm') dont le Y est mis à 63 et sa hauteur à 138. C'est 138 + 63 = 201. Par conséquent, si vous modifiez la valeur Y de la case à cocher, le contrôle 'BodyText' chevauche le contrôle nouvellement ajouté et c'est pourquoi l'utilisateur doit passer la souris dessus pour afficher la case à cocher. Si vous n'avez pas 'BodyText' ou si son nombre de caractères est assez petit, vous pouvez changer (en utilisant l'éditeur d'Orca msi comme je le fais, ou en modifiant le script ci-dessus) les Y et les Hauteurs de ces 2 contrôles afin de pouvoir une position Y différente pour la case à cocher nouvellement ajoutée. La même chose s'applique pour le contrôle: 'BodyTextRemove' dans lequel encore une fois nous devons modifier sa valeur de hauteur (qui apparaît lors de la désinstallation)

Espérons que cela aide tous les utilisateurs qui avaient la même question que moi sur ce "bug"

Néanmoins, le script fait un très bon travail!

Une autre question était de savoir comment rendre invisible la case à cocher pendant la procédure de désistement. Utilisation de l'éditeur Orca NOBO ajouté les éléments suivants 2 lignes dans la table ControlCondition du msi:

Ligne 1 (Lorsque le contrôle doit être affiché):

(Dialog) FinishedForm (Control) CheckboxLaunch (Action) afficher (condition) RETIRER = ""

Row 2 (Lorsque le contrôle doit être invisible):

(Dialog) FinishedForm (Control) CheckboxLaunch (action) Masquer (Condition) SUPPRIMER <> ""

P.S. J'utilise VS 2010, sur Windows 7 (x64), mais je crois que ceux-ci devraient aussi fonctionner avec les versions précédentes.

+0

Donc nous devons éditer le msi pour que nous déplacions la case? – ViFer

11

OK !!! Voici le code (sans les 2 fonctions auxiliaires 'FindFileIdentifier' & 'StringEndsWith' à la fin - utilisez les originaux), ce qui nous donne la possibilité de changer les Y et les hauteurs des contrôles, ainsi que d'ajouter les conditions de visibilité du contrôle Checkbox (voir les 2 commentaires qui sont marqués entre « NOUVEAU - START » à 'NOUVEAU - FIN):


// 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); 
} 
+0

Cela a corrigé la case à cocher de l'application de lancement apparaissant après la suppression! –

+0

Qu'est-ce que ça a fait exactement? C'est peut-être juste que je me lève trop tard, mais j'ai de la difficulté à analyser ce que vous avez écrit là-bas, @Fidelis. – Cheeso

19

Cela semble être une solution beaucoup plus simple: Visual Studio Installer > How To Launch App at End of Installer

+1

La solution originale était requise pour VS2005 (et peut-être VS2008)? –

+0

Apparemment, cette solution plus simple n'affiche pas l'option checkbox. –

+0

dans ce problème de solution est la configuration sera coincé juste avant son achèvement ... presque à 98% point. et il se termine juste quand vous quittez l'application. – mhesabi

Questions connexes