Accueil > .Net, C#, WIX > WiX

WiX

J’avais déjà parlé de WIX dans un billet : How to Start : WIX (ça peut être utile de le lire en premier, si vous ne connaissez pas WiX).
Je suis récemment revenu dessus pour créer un installeur « un peu » custom.

Au programme, donc :

  • Ajouter une icône au programme
  • Déployer des fichiers dans un sous-répertoires
  • Modifier un champ dans l’App.Config depuis l’installeur
  • Paramétrer la chaîne de connexion EF depuis l’installeur
  • Déboguer les Custom Actions
  • Passer des paramètres à l’installeur

 

Situation

 

Pour illustrer ce billet par le code, je vais utiliser une application assez simpliste.
Dans la solution, deux projets (importants) : SampleWIX2.Setup, qui est dévolu à WiX et SampleWIX2.ClientConsole, une application console qui va être installée.
Dans les paramètres, je vais avoir des basiques appSettings, mais aussi une chaîne de connexion destinée à Entity Framework.

Dans tout ce qui est WiX et ses fichiers XML, je vais aller au plus simple.
Dans la vraie vie, il est utile d’organiser les balises sur plusieurs fichiers, d’avoir un XML pour la localisation, etc.
Ici, comme ce n’est pas le but, il n’y aura rien de tout ça, tout en vrac🙂

La solution de démonstration est disponible.

 

Ajouter une icône au programme

 

On commence petit joueur.
Comment peut-on ajouter une icône qui sera visible sur l’écran « Programmes et fonctionnalités » ?

C’est réellement super simple.
Dans la balise Product, on va simplement ajouter :

<Icon Id="icone_ap.ico" SourceFile="icone_ap.ico"/>
<!-- http://blog.iconspedia.com/icons/100-free-icons-weby-icon-set-192/ -->
<Property Id="ARPPRODUCTICON" Value="icone_ap.ico" />

Dans la balise Icon, l’attribut SourceFile correspond à l’endroit (dans le projet) où l’on trouve l’icône.
L’attribut Value de la balise Property pointe vers l’Id de la balise Icon.
Et voilà ! Simple, non ?

 

Déployer des fichiers dans un sous-répertoires

 

Pas super complexe non plus, c’est en trois étapes.

La première est très simple : il faut modifier les propriétés sur le fichier pour qu’il soit copié dans le bin lors de la compilation :

WiX - Copie du fichier dans le bin

La deuxième est de déclarer son arborescence :

<Directory Id="TARGETDIR" Name="SourceDir">
  <Directory Id="ProgramFilesFolder">
      <Directory Id="INSTALLLOCATION" Name="SampleWIX2">
        <Directory Id="HelpDirectory" Name="Help"/>
    </Directory>
  </Directory>
</Directory>

Le premier répertoire est là où sera installé l’application.
ProgramFilesFolder correspond au ProgramFiles, c’est à dire « C:\Program Files » ou « C:\Program Files (x86) ».
Ensuite, on aura le répertoire de l’application (ici : SampleWIX2) et enfin le répertoire « Help ».
Ce qui nous donne en chemin final : « C:\Program Files (x86)\SampleWIX2\Help ».
Il est possible d’intercaler autant de balise Directory que souhaité (du genre avec le nom de l’entreprise).

Pour savoir où installer le programme, en fonction du système (64 bits ou non), on peut utiliser le code suivant :

  <?if $(var.Platform) = x64 ?>
  <?define Win64 = "yes" ?>
  <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
  <?else ?>
  <?define Win64 = "no" ?>
  <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
  <?endif ?>

Enfin, pour déployer notre readme, on va ajouter l’extrait suivant :

    <DirectoryRef Id="HelpDirectory">
      <Component Id="Help_Product" Guid="A6441E81-2A0C-4C54-90D9-61BA21841898">
        <File Id="READMEFILE" KeyPath="no" Source="$(var.SampleWIX2.ClientConsole.TargetDir)\Help\readme.txt" Vital="yes" />
      </Component>
    </DirectoryRef>

L’attribut Source correspond au chemin du fichier lors de la compilation.
Ici, on va donc aller chercher le fichier dans le « bin\Debug\Help » (si on compile en Debug).
L’attribut Vital indique que si pour une raison ou une autre le fichier n’est pas déployé, l’installation plante.

 

Modifier un champ dans l’App.Config depuis l’installeur

 

Dans WIX, il n’y a pas de mot clef déclarant la portée d’une propriété.
Du coup, il y a des contraintes spécifiques sur le nom des propriétés en question.
Il y a une aide sur la MSDN à ce propos : Restrictions on Property Names.
Elément ô combien important :

Public property names cannot contain lowercase letters.

Quand on connait pas le nom et qu’on se dit bêtement que les propriétés sont accessibles partout, ça surprend un peu…

Dans le scénario présent, je vais permettre à la personne qui installe l’application de paramétrer le fichier app.config.
Ce qui veut dire que ma propriété va être attachée à un formulaire.

Dans le descriptif de l’UI, j’aurais donc ceci :

<Control Type="Text" Width="275" Height="10" X="25" Y="60" Id="Prop1Label" Text="Propriété 1 :" />
<Control Type="Edit" Width="275" Height="15" X="25" Y="80" Id="Prop1TextBox" Property="PROPRIETE1VALEUR" />

La première balise décrit le libellé du champ, la seconde correspond au champ dans lequel l’utilisateur va entrer ce qu’il veut.
L’important, ici, c’est l’attribut Property, c’est en effet lui qui va indiquer dans quelle propriété on va mettre l’input utilisateur.
Et TOUT en majuscule, comme vu précédemment.

Et pour modifier le fichier de configuration, c’est dans les composants déployés que l’on s’insère :

<util:XmlFile Id="AppSettingKey1"
              File="[INSTALLLOCATION]\SampleWIX2.ClientConsole.exe.config"
              Action="setValue" ElementPath="/configuration/appSettings/add[\[]@key='KEY1'[\]]/@value"
              Value="[PROPRIETE1VALEUR]" />

Le util correspond au xmlns disponible ici : http://schemas.microsoft.com/wix/UtilExtension.
L’attribut File indique quel fichier (de type XML) on va modifier (la propriété INSTALLLOCATION est définie dans l’arborescence des répertoires).
L’attribut Action permet de savoir ce que l’on va faire.
L’attribut ElementPath correspond à une requête XPath (et là, faut se souvenir comment on écrit les requêtes XPath ^^)
Enfin, l’attribut Value correspond à la valeur. Ici, une propriété (à cause des crochets).

Maintenant, dans l’installeur, on peut s’amuser à paramétrer le fichier de configuration.

 

Paramétrer la chaîne de connexion EF depuis l’installeur

 

On augmente encore un peu la difficulté.
Maintenant, on va réutiliser nos propriétés vues au paragraphe précédent pour constituer une chaîne de connexion EF-style.

Là, ça devient un peu touchy.
Il va falloir faire du code C# pour créer un Custom Action.
Je vous renvois donc vers l’article Creating Custom Action for WIX Written in Managed Code without Votive. Il est bien fait et du coup, c’est pas la peine de le refaire ! ^^ (et puis comme je n’aborde pas que ce sujet, ici…)

Voici donc la Custom Action en question :

namespace SampleWIX2.CustomActions
{
    using System;
    using System.Data.EntityClient;
    using System.Data.SqlClient;
    using Microsoft.Deployment.WindowsInstaller;

    public static class CustomActions
    {
        private const string ConstanteCustomActionException = "ConstanteCustomActionException: ";

        private const string ConstanteModel = "CONNECTIONSTRINGMODEL";
        private const string ConstanteServer = "PROPRIETE1VALEUR";
        private const string ConstanteCatalog = "PROPRIETE2VALEUR";
        private const string ConstanteUtilisateur = "PROPRIETE3VALEUR";
        private const string ConstanteMotDePasse = "PROPRIETE4VALEUR";
        private const string ConstanteConnectionString = "CONNECTIONSTRING";

        [CustomAction]
        public static ActionResult CreerConnectionString(Session session)
        {
            var result = ActionResult.Failure;
            try
            {
                if (session == null) { throw new ArgumentNullException("session"); }

                var model = session[ConstanteModel];
                var server = session[ConstanteServer];
                var catalog = session[ConstanteCatalog];
                var user = session[ConstanteUtilisateur];
                var password = session[ConstanteMotDePasse];

                var metadata =
                    string.Format("res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl", model);

                var sqlBuilder = new SqlConnectionStringBuilder
                                     {
                                         MultipleActiveResultSets = true,
                                         DataSource = server,
                                         InitialCatalog = catalog,
                                         UserID = user,
                                         Password = password,
                                         ApplicationName = "EntityFramework"
                                     };

                var entityBuilder = new EntityConnectionStringBuilder
                                        {
                                            ProviderConnectionString = sqlBuilder.ToString(),
                                            Metadata = metadata,
                                            Provider = "System.Data.SqlClient"
                                        };

                session[ConstanteConnectionString] = entityBuilder.ToString();
                result = ActionResult.Success;
            }
            catch (Exception ex)
            {
                if (session != null)
                    session.Log(ConstanteCustomActionException + ex);
            }
            return result;
        }
    }
}

Bien évidemment, il faut changer les noms des variables, là, c’est pour la démonstration, donc pas très grave.
La propriété CONNECTIONSTRINGMODEL va être en dur dans le code de WiX pour la simple raison que le modèle n’est pas une donnée de paramétrage.

    <Property Id="CONNECTIONSTRINGMODEL" Value="SAMPLESModel" />

Dans le fragment (balise Fragment) dévolue aux composants, on va rajouter le binaire correspondant à l’assembly des custom actions.
Et on va aussi faire en sorte que l’action soit référencée.

    <Binary Id="CustomActions" SourceFile="$(var.SampleWIX2.CustomActions.TargetDir)$(var.SampleWIX2.CustomActions.TargetName).CA.dll" />
    <CustomAction Id="PasserParamCreerConnectionString" Property="CreerConnectionString"
                  Value="SampleWIX2.ClientConsole.exe.config|[INSTALLLOCATION]SampleWIX2.ClientConsole.exe.config" Execute="immediate" Return="check" />
    <CustomAction Id="CreerConnectionString" BinaryKey="CustomActions" DllEntry="CreerConnectionString" Execute="immediate" Return="check" />

Il faut bien sûr une correspondance entre les deux attributs Id de la balise Binary et BinaryKey de la balise CustomAction.

Au sein de la balise Product, on va ajouter le code suivant, ayant pour but d’exécuter la custom action :

    <InstallExecuteSequence>
      <!-- On log si le fichier de conf n'était pas présent et si il y a eu relivraison -->
      <Custom Action="CreerConnectionString" Before="InstallInitialize">NOT FILECFGEXISTS AND NOT INSTALLED AND NOT (REMOVE="ALL")</Custom>
    </InstallExecuteSequence>

Et enfin, la modification du fichier de configuration :

      <util:XmlFile Id="connectionStrings"
                    File="[INSTALLLOCATION]SampleWIX2.ClientConsole.exe.config"
                    Action="setValue" ElementPath="/configuration/connectionStrings/add[\[]@name='SAMPLESEntities'[\]]/@connectionString"
                    Value="[CONNECTIONSTRING]" />

Voilà, avec ça, on arrive à créer une chaîne de connexion Entity Framework directement depuis l’installeur.

Note importante :
Dans la chaîne de connexion Entity Framework, lors du référencement des metadata, on peut avoir un truc du style :

<add name="SAMPLESEntities"
    connectionString="metadata=res://*/Samples.csdl|res://*/Samples.ssdl|res://*/Samples.msl;[...]"
    providerName="System.Data.EntityClient" />

Les metadata vont indiquer à Entity Framework, lorsqu’il créé un context, dans quelle assembly chercher le model.
Le * va lui indiquer qu’il va devoir se débrouiller tout seul pour trouver la bonne assembly. Il va donc chercher dans TOUTES les assemblies présentes.
Si, à la place on lui indique « metadata=res://SampleWIX2.ClientConsole/Samples.csdl », il ne va aller chercher QUE dans cette assembly.
Le gain de temps est réellement négligeable quand il y a peu d’assemblies, mais quand on optimise EF, ça peut valoir le coup de le savoir.

 

Déboguer les Custom Actions

 

Dès qu’on fait des custom actions, si ça ne marche pas du premier coût, c’est quand même SUPER galère à comprendre ce qu’il se passe.
Il y a donc quelques astuces à savoir pour déboguer en tout quiétude.

Ainsi, le plus simple et le plus efficace reste de lancer le débogueur de Visual Studio.
Pour se faire, rien de bien compliqué : il suffit d’ajouter une seule ligne de code en tout début de la méthode custom action :

System.Diagnostics.Debugger.Launch();

Ainsi, durant l’installation, on aura cette fenêtre :
Debogueur WiX

Il suffit donc de choisir l’instance de Visual Studio qui correspond (la 2nde sur la capture) et le tour est joué :
Debogueur WiX - Code

Une autre option consiste à lancer l’installation depuis la ligne de commande :

msiexec /i SampleWIX2.Setup.msi /Lime logfile.txt

Ça donne un fichier de log qu’il est possible (la bonne blague…) d’analyser.
Je le mets pour le fun :

=== Début de l’écriture dans le journal : 21/03/2014  11:07:15 ===
Début de l’action 11:07:15 : INSTALL.
Début de l’action 11:07:15 : PrepareDlg.
Informations 2898.For WixUI_Font_Normal textstyle, the system created a 'Tahoma' font, in 0 character set, of 13 pixels height.
Informations 2898.For WixUI_Font_Bigger textstyle, the system created a 'Tahoma' font, in 0 character set, of 19 pixels height.
Fin de l’action 11:07:15 : PrepareDlg. Valeur renvoyée : 1.
Début de l’action 11:07:15 : AppSearch.
Fin de l’action 11:07:15 : AppSearch. Valeur renvoyée : 0.
Début de l’action 11:07:15 : ValidateProductID.
Fin de l’action 11:07:15 : ValidateProductID. Valeur renvoyée : 1.
Début de l’action 11:07:15 : CostInitialize.
Fin de l’action 11:07:15 : CostInitialize. Valeur renvoyée : 1.
Début de l’action 11:07:15 : FileCost.
Fin de l’action 11:07:15 : FileCost. Valeur renvoyée : 1.
Début de l’action 11:07:15 : CostFinalize.
Fin de l’action 11:07:15 : CostFinalize. Valeur renvoyée : 1.
Début de l’action 11:07:15 : WelcomeDlg.
Informations 2898.For WixUI_Font_Title textstyle, the system created a 'Tahoma' font, in 0 character set, of 14 pixels height.
Fin de l’action 11:07:59 : WelcomeDlg. Valeur renvoyée : 1.
Début de l’action 11:07:59 : ProgressDlg.
Fin de l’action 11:07:59 : ProgressDlg. Valeur renvoyée : 1.
Début de l’action 11:07:59 : ExecuteAction.
Début de l’action 11:08:04 : INSTALL.
Début de l’action 11:08:04 : ValidateProductID.
Fin de l’action 11:08:04 : ValidateProductID. Valeur renvoyée : 1.
Début de l’action 11:08:04 : CostInitialize.
Fin de l’action 11:08:04 : CostInitialize. Valeur renvoyée : 1.
Début de l’action 11:08:04 : FileCost.
Fin de l’action 11:08:04 : FileCost. Valeur renvoyée : 1.
Début de l’action 11:08:04 : CostFinalize.
Fin de l’action 11:08:04 : CostFinalize. Valeur renvoyée : 1.
Début de l’action 11:08:04 : InstallValidate.
Fin de l’action 11:08:04 : InstallValidate. Valeur renvoyée : 1.
Début de l’action 11:08:04 : CreerConnectionString.
SFXCA: Extracting custom action to temporary directory: C:\Windows\Installer\MSI3B08.tmp-\
SFXCA: Binding to CLR version v4.0.30319
Calling custom action SampleWIX2.CustomActions!SampleWIX2.CustomActions.CustomActions.CreerConnectionString
CustomAction CreerConnectionString returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)
Fin de l’action 11:08:07 : CreerConnectionString. Valeur renvoyée : 3.
Fin de l’action 11:08:07 : INSTALL. Valeur renvoyée : 3.
Fin de l’action 11:08:07 : ExecuteAction. Valeur renvoyée : 3.
Début de l’action 11:08:07 : FatalError.
Fin de l’action 11:08:08 : FatalError. Valeur renvoyée : 2.
Fin de l’action 11:08:08 : INSTALL. Valeur renvoyée : 3.
=== Fin de l’écriture dans le journal : 21/03/2014  11:08:08 ===
MSI (c) (B4:6C) [11:08:08:400]: Produit : SampleWIX2 -- L’installation a échoué.

MSI (c) (B4:6C) [11:08:08:400]: Windows Installer a installé le produit. Nom du produit : SampleWIX2. Version du produit : 1.0.0.0. Langue du produit : 1036. Fabricant : Kerrubin. Réussite de l’installation ou état d’erreur : 1603.

Ici, je n’ai pas accepté le débogage, donc ça a planté et rien ne s’est installé.
Sur les msi un peu plus complet, c’est une vraie gageure de relire le tout, mais ça peut au moins réduire le champs des investigations en cas de problème.

 

Passer des paramètres à l’installeur

 

Last but not least : comment passer des informations au msi ?
Ici, nous allons voir qu’il est possible, en ligne de commande uniquement, de passer des paramètres au msi pour pré-remplir les champs.
C’est assez simple en pratique.

Il suffit de passer les paramètres comme il suit :

msiexec /i SampleWIX2.Setup.msi INSTALLLOCATION="D:\TEMP" PROPRIETE1VALEUR="serveur" PROPRIETE2VALEUR="catalog" PROPRIETE3VALEUR="user" PROPRIETE4VALEUR="pwd"

 

Conclusion

 

Voilà, cela conclut ce billet et, je pense, les billets que je ferais au sujet de WiX (puisque j’en ai fais le tour pour mon usage)?
WiX est donc réellement TRÈS puissant, mais sa complexité va de pair.
Il est possible de faire beaucoup de chose avec.

Par exemple, dans ma mission actuelle, nous avons une interface graphique associée à un fichier XML qui liste, par catégorie de serveur (poste client, serveur applicatif, serveur batch…) ce qu’il possible d’installer, avec les msis qui vont bien.
Un deuxième fichier XML de configuration liste tous les paramètres à passer aux msis, ce qui fait qu’il est possible, depuis cette interface, d’installer plusieurs msis en mode silencieux (sans interface graphique) et surtout sans intervention humaine.
Le service d’exploitation a donc très peu d’actions à réaliser et donc autant d’erreur humaine en moins.

Catégories :.Net, C#, WIX
  1. Aucun commentaire pour l’instant.
  1. No trackbacks yet.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :