mardi 20 juillet 2010

Installer le Framework .NET sur un poste client via code (sans être administrateur)


Lors d'un projet de migration des applications Winforms du .NET 1.1 vers .NET 3.5, on a réfléchi au moyen de déploiement du Framework .NET3.5 sur les postes d'utilisateurs. Les utilisateurs ne sont pas administrateurs sur les postes. Ils ne peuvent donc pas installer le Framework eux-mêmes. Le déploiement de l'application se fait par ClickOnce via un URL :
http:// [url]. [Domaine].com/[nomapplication].application
Une possibilité était d'inclure le code d'installation de Framework .NET3.5 lors de lancement de l'application.

Les principes :
1. Vérifier si le Framework .NET 3.5 est installé sur le poste client.
2. Si Oui, installer l'application.
3. Si Non, installer d'abord le Framework .NET 3 .5 en utilisant un profil administrateur, puis installer l'application.

Les étapes :
1. J'ai défini dans le fichier de configuration le chemin vers l'exécutable du Framework .NET3.5, le nom ainsi que le mot de passe d'utilisateur qui installera l'application et l'url de l'application.
……..
<appSettings>
<add key= "application" value="http://[URL application]\setup.exe"/>
<add key= "cmd" value="http://[URL application]\setup.exe"/>
<add key= "user" value="[Nom utilisateur]"/>
<add key= "pwd" value="[Mot de passé utilisateur]"/>
<add key= "domain" value="[Domaine utilisateur]"/>
</appSettings>
……
2. J'ai créé une classe qui contient une méthode permettant de lancer un processus sous le contexte d'un utilisateur. J'ai eu le code ci-dessous en cherchant sur internet.

Pour voir plus loin : http://msdn.microsoft.com/en-us/library/ms682431(VS.85).aspx

using System;using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Lanceur
{
        /// 
       /// Description résumée de CreateProcessWithLogon.
       /// 
      public class CreateProcessWithLogon
     {
               public const UInt32 Infinite = 0xffffffff;
              public const Int32 Startf_UseStdHandles = 0x00000100;
              public const Int32 StdOutputHandle = -11;
             public const Int32 StdErrorHandle = -12;
           [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
           public struct StartupInfo
  {
                public int cb;
               public String reserved;
               public String desktop;
              public String title;
              public int x;
              public int y;
              public int xSize;
              public int ySize;
              public int xCountChars;
              public int yCountChars;
              public int fillAttribute;
              public int flags;
              public UInt16 showWindow;
              public UInt16 reserved2;
              public byte reserved3;
              public IntPtr stdInput;
              public IntPtr stdOutput;
              public IntPtr stdError;

public struct ProcessInformation
{
                public IntPtr process;
               public IntPtr thread;
               public int processId;
               public int threadId;
}
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool CreateProcessWithLogonW(
String userName,
String domain,
String password,
UInt32 logonFlags,
String applicationName,
String commandLine,
UInt32 creationFlags,
UInt32 environment,
String currentDirectory,
ref StartupInfo startupInfo,
out ProcessInformation processInformation);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);
[DllImport("Kernel32.dll", SetLastError=true)]
public static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);
[DllImport("Kernel32.dll", SetLastError=true)]
public static extern IntPtr GetStdHandle(IntPtr handle);
[DllImport("Kernel32.dll", SetLastError=true)]
public static extern bool CloseHandle(IntPtr handle);
public static void Launch(string command, string user, string domain, string pwd){
StartupInfo startupInfo = new StartupInfo();
startupInfo.reserved = null;
startupInfo.flags &amp;= Startf_UseStdHandles;
startupInfo.stdOutput = (IntPtr)StdOutputHandle;
startupInfo.stdError = (IntPtr)StdErrorHandle;
UInt32 exitCode = 123456;
ProcessInformation processInfo = new ProcessInformation();
String currentDirectory = System.IO.Directory.GetCurrentDirectory();
try
{
CreateProcessWithLogonW(
user,
domain,
pwd,
(UInt32) 1,
command,
command,
(UInt32) 0,
(UInt32) 0,
currentDirectory,
ref startupInfo,
out processInfo);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.WriteLine("Exécution ...");
WaitForSingleObject(processInfo.process, Infinite);
GetExitCodeProcess(processInfo.process, ref exitCode);
Console.WriteLine("Code sortie: {0}", exitCode);
CloseHandle(processInfo.process);
CloseHandle(processInfo.thread);
}
}
}

 3. Dans le point d'entrée de l'application, j'ai ajouté le code pour vérifier l'existence du Framework .NET 3.5 sur le poste client en vérifiant la présence de clé dans la base de registre.
using System.Windows.Forms;
using System.Data;
using System.Configuration;
using System.Collections;
using Microsoft.Win32;
namespace Lanceur
{
///
/// Classe lanceur
///
public class MainClass
{
///
/// Point d'entrée principal de l'application.
///[STAThread]
static void Main()
{
string application = ConfigurationSettings.AppSettings["application"];
try
{
Console.WriteLine("Vérification de composant requis.");
// Vérifier si framework .NET3.5 est installé
if(!NETInstalled())
{
Console.WriteLine("Installation de composant requis en cours ....");
string cmd = @ConfigurationSettings.AppSettings["cmd"];

// Installation en silence
cmd = cmd + " /q:a /c:'setup.exe /q /norestart' /norestart";
string user = ConfigurationSettings.AppSettings["user"];
string domain = ConfigurationSettings.AppSettings["domain"];
string pwd = ConfigurationSettings.AppSettings["pwd"]; 

// Installation du framework .NET 3.5
CreateProcessWithLogon.Launch(cmd, user,domain,pwd);
Console.WriteLine("Installation de composant requis est teminé.");

// Installation de l'application
Console.WriteLine("Installation de l'application.");
InstallApplication(application);
}
else
{
// Installation de l'application
Console.WriteLine("Installation de l'application.");
InstallApplication(application);
}
}
catch(Exception Ex)
{…}

// Lancement de l'application
try
{
// Code propre à l'application
Application.Run((System.Windows.Forms.Form)(IHMManager.Instance.getIhmPrincipale()));
}
catch(….)
{}
}
}
private static bool NETInstalled()
{
RegistryKey cle = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\NET Framework Setup\\NDP\\v3.5");
         if(cle == null)
        {
return false;
        }
       return true;
}

private static void InstallApplication(string application)
{
    try
  {
       // Prévenir l'utilisateur qi tout se passe bien, de passer par la nouvelle icône pour la prochaine connexion        MessageBox.Show("L'application [NOMAPPLICATION] sera installée sur votre poste. Merci de cliquer sur  la nouvelle icône sur votre bureau pour la prochaine connexion.");
System.Threading.Thread.Sleep(500);
System.Diagnostics.Process.Start(@application);
System.Diagnostics.Process.Start(@application);
 }
catch(Exception Ex)
{
throw Ex;
}
}
}
}

Cette solution n'a finalement pas été retenue. Mais cela me permet de découvrir une possibilité sur le moyen d'installation les éléments requis. 

On utilise finalement un outil PsTools->PsExec qui permet d'installer à distance et en masse une application sur un poste client sous contexte d'un administrateur.

Aucun commentaire:

Enregistrer un commentaire