DarkBlog

Aller au contenu | Aller au menu | Aller à la recherche

samedi 1 mars 2014

[Code Sample] Impersonnation ou comment exécuter du code en tant qu'un autre utilisateur!

Voici une librairie qui permet d’exécuter des portions de code sous l’identité d’un autre utilisateur. Peut être utile si vous avez besoin de copier des fichiers vers un dossier sur lequel vous n’avez pas les droits (une autre machine par exemple), ou encore pour exécuter une requête SQL ayant besoin d’un utilisateur particulier…
Le but est ici de pouvoir changer le contexte utilisateur pour un morceau de code particulier tout en s’assurant que la déconnexion se fait bien ;)

Remarque : le fait de changer de contexte fera exécuter ce code “en tant qu’un autre utilisateur”, si cet autre utilisateur n’a pas les droits sur votre poste ou si, par exemple, vous tentez d’accéder à des fichiers locaux, il faudra bien prendre ceci en compte : par exemple copier des fichiers de “c:\users\monuser\documents” vers “c:\users\monautreuser\documents” ne fonctionnera surement pas sans passer par un dossier temporaire avec des droits pour les 2 utilisateurs.

Voici donc deux exemples d’utilisation, suivi de la librairie.

CODE 1 :

private static void DemoForFileCopy()
{
  //Ce code fais une copie des fichiers .doc du dossier Documents (sans les sous-dossiers)
  //de l'utilisateur actuel vers l'utilisateur "OtherUser"
  //A cause de l'impersonation qui executera le code avec les droits
  //de l'utilisateur "OtherUser", nous devons copier les fichier dans
  //un dossier temporaire accessible aux deux utilisateurs!
  string tmpPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"DarkImpersonationDemo");
  if (!System.IO.Directory.Exists(tmpPath))
    System.IO.Directory.CreateDirectory(tmpPath);
  try
  {
    string OtherUser = "user2";
    string OtherUserPassword = "xxx";
    string OtherUserDomain = "dom";
    Console.WriteLine("Copy to a temp folder");
    foreach (var item in System.IO.Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "*.doc*"))
      System.IO.File.Copy(item, System.IO.Path.Combine(tmpPath, System.IO.Path.GetFileName(item)));
    Console.WriteLine("Copy to {0} doc folder", OtherUser);
    using (new DarkImpersonation.Logon(OtherUser, OtherUserPassword, OtherUserDomain, true))
    {
      foreach (var item in System.IO.Directory.GetFiles(tmpPath, "*.doc*"))
        System.IO.File.Copy(item, System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), System.IO.Path.GetFileName(item)), true);
    }
  }
  catch (Exception ex)
  {
    Console.WriteLine("Exception during demo : {0}", ex.Message);
  }
  finally
  {
    Console.WriteLine("Clean temp folder");
    System.IO.Directory.Delete(tmpPath, true);
  }
}

CODE 2 :

private static void DemoForSQLExec()
{
  //Pour la démo, nous utiliserons 2 "Quality Servers" utilisant le même utilisateur,
  //2 "Production Servers" utilisant un autre utilisateur
  //et 1 "Development Server" utilisant l'utilisateur actuel
  List<SQLServerDemo> SQLServers = new List<SQLServerDemo>(){
    new SQLServerDemo(){ Server="QSRV1", Domain="domain", User="user_quality", Password="mypass"   , ConnectionString="DataSource=xxx,..."},
    new SQLServerDemo(){ Server="QSRV2", Domain="domain", User="user_quality", Password="mypass"   , ConnectionString="DataSource=xxx,..."},
    new SQLServerDemo(){ Server="PSRV1", Domain="domain", User="user_production", Password="mypass", ConnectionString="DataSource=xxx,..."},
    new SQLServerDemo(){ Server="PSRV2", Domain="domain", User="user_production", Password="mypass", ConnectionString="DataSource=xxx,..."},
    new SQLServerDemo(){ Server="DSRV1"                                                            , ConnectionString="DataSource=xxx,..."}
  };
  string SQLToExecute = "select count(*) from tblQueue";
  Parallel.ForEach(SQLServers, server =>
  {
    try
    {
      using (new DarkImpersonation.Logon(server.User, server.Password, server.Domain, true))
      {
        System.Data.SqlClient.SqlConnection cnx = null;
        System.Data.SqlClient.SqlCommand cmd = null;
        try
        {
          cnx = new System.Data.SqlClient.SqlConnection(server.ConnectionString);
          cnx.Open();
          cmd = new System.Data.SqlClient.SqlCommand(SQLToExecute, cnx);
          server.Result = (int)cmd.ExecuteScalar();
        }
        catch (Exception ex)
        {
          throw ex;
        }
        finally
        {
          if (cmd != null)
            cmd.Dispose();
          if (cnx != null)
          {
            if (cnx.State == System.Data.ConnectionState.Open)
              cnx.Close();
            cnx.Dispose();
          }
        }
      }
    }
    catch (Exception ex)
    {
      server.Result = int.MinValue;
      Console.WriteLine("Server {0} : exception:{1}", server.Server, ex.Message);
    }
  });
  //Show Results
  foreach (var server in SQLServers.Where(t => t.Result != int.MinValue))
  {
    Console.WriteLine("Server {0} : {1}", server.Server, server.Result);
  }
}
class SQLServerDemo
{
  public string Server { get; set; }
  public string User { get; set; }
  public string Domain { get; set; }
  public string Password { get; set; }
  public string ConnectionString { get; set; }
  public int Result { get; set; }
}

La DLL DarkImpersonation : [LINK (zip)]

Le code source du projet complet (démo + dll) : [LINK]

dimanche 5 janvier 2014

[Code Sample] Découpage de fichier

Pour pouvoir envoyer des fichiers un peu gros via WCF (plus de 10Ko dans mon cas) il y a 2 solutions :

  1. Modifier la configuration WCF pour permettre une transmission de données importantes : nous avons alors le risque de dépasser un jour cette limite, le temps de transmission va s’allonger pour chaque fichiers.
  2. Découper les fichiers à l’envoi et les reconstituer à la réception

Dans mon cas, j’avais besoin de découper les fichiers ayant une taille importante dans un dossier depuis un outils de gestion de déploiement, de les envoyer via WCF vers un service distant, puis de les reconstituer en ligne de commande lors du lancement d’un script de mise à jour. Il existe sur le marché des outils permettant de découper et reconstituer des fichiers en ligne de commande ou via une interface graphique, mais je n’ai pas trouvé d’outils permettant de le faire en code d’un coté et en ligne de commande de l’autre coté!

L’idée est donc d’avoir une assembly C# qui fait ce travail. Cet assembly pouvant être appelé depuis un projet console (pour la ligne de commande) et depuis un projet windows form (pour la gestion du déploiement). Une contrainte que j’ai eu également étant la taille : je ne devais pas dépasser les 10Ko.

Voici le résultat, le code complet est disponible à la fin de cet article avec une version compilée.

  1. Le projet
      Créer un projet Console pour pouvoir lancer le découpage/reconstitution en ligne de commande :
    image
    Dans cet exemple je me suis mis en .Net 4.0 mais le code peut être compilé en .Net 3.5 (utilisation de Linq donc à minima 3.5)

      Ajouter un projet ClassLibrary : c’est ici que sera le code de découpage/reconstitution des fichiers
    image
    La classe sera une “static” pour faciliter l’utilisation :
    namespace DarkFileSplitterCore
    {
      public static class SplitMerge
      {
        public enum Action
        {
          Merge, Split
        }
        public static void JustDoIt(string SourceFolder, string DestinationFolder, Action action, bool OverwriteDestination = true, int MaxBytesPerFile = 10000)
        {
          //...
    
  2. Un peu de récursivité
      Le but n’étant pas de découper 1 fichier unique mais un dossier, on va devoir parcourir les sous-dossiers et les fichiers qui les composent.
    public static void JustDoIt(string SourceFolder, string DestinationFolder, Action action, bool OverwriteDestination = true, int MaxBytesPerFile = 10000)
    {
      #region Prepare Folders
      if (!System.IO.Directory.Exists(SourceFolder))
        throw new ArgumentException("Source folder does not exist!", "SourceFolder");
      if (OverwriteDestination)
      {
        if (System.IO.Directory.Exists(DestinationFolder))
          System.IO.Directory.Delete(DestinationFolder, true);
        System.IO.Directory.CreateDirectory(DestinationFolder);
      }
      #endregion
      foreach (var folder in System.IO.Directory.GetDirectories(SourceFolder))
        JustDoIt(folder, Path.Combine(DestinationFolder, folder.Replace(SourceFolder, "").TrimStart('\\')), action, OverwriteDestination, MaxBytesPerFile);
    
  3. Le Split
      Reste donc à parcourir les fichiers du dossier source et de les découper :
    foreach (var file in System.IO.Directory.GetFiles(SourceFolder))
    {
      byte[] data = System.IO.File.ReadAllBytes(file); //Chargement du fichier en mémoire
      string DestinationFile = Path.Combine(DestinationFolder, file.Split('\\').Last());
      if (data.Length < MaxBytesPerFile)
      {
        //Le fichier est plus petit que la limite, écriture direct sans "split"
        System.IO.File.WriteAllBytes(DestinationFile, data);
      }
      else
      {
        //Génération des fichiers avec la taille limite
        int FileIndex = 0;
        while (data.Length > MaxBytesPerFile)
        {
          System.IO.File.WriteAllBytes(string.Format("{0}.{1:00000}.DarkFile", DestinationFile, FileIndex), data.Take(MaxBytesPerFile).ToArray());
          FileIndex++;
          data = data.Skip(MaxBytesPerFile).ToArray(); //on retire du tableau les données déjà sauvées
        }
        if (data.Length != 0) //Le reste à écrire, ce fichier sera plus petit que la limite
          System.IO.File.WriteAllBytes(string.Format("{0}.{1:00000}.DarkFile", DestinationFile, FileIndex), data);
      }
    }
    
  4. Le Merge
      Il n’y a qu’a parcourir les fichiers et régénérer les fichiers, un peu de code :
    List<byte> data = new List<byte>();
    foreach (var file in System.IO.Directory.GetFiles(SourceFolder))
    {
      if (file.EndsWith(".DarkFile"))
      {
        int FileIndex = int.Parse(file.Replace(".DarkFile", "").Split('.').Last());
        string OriginalFile = file.Replace(string.Format(".{0:00000}.DarkFile", FileIndex), "");
        if (FileIndex == 0)
          data = new List<byte>();
        data.AddRange(System.IO.File.ReadAllBytes(file));
        //On écrit à chaque boucle le fichier "cumulé", ceci évite d'avoir à tester le changement de fichier dans la boucle
        //Bien sur on pourrais optimiser en détectant le changement de fichier (OriginalFile)
        //et en écrivant dans le fichier les datas cumulées...
        System.IO.File.WriteAllBytes(Path.Combine(DestinationFolder, OriginalFile.Split('\\').Last()), data.ToArray());
      }
      else  
      {
        //Fichier non découpé
        System.IO.File.Copy(file, Path.Combine(DestinationFolder, file.Split('\\').Last()));
      }
    }
    

 

Tout ceci permet d’avoir :
- une dll / assembly capable de découper/reconstituer des fichiers dans des dossiers/sous-dossiers
- une ligne de commande pour réaliser ces opérations (voir le code source complet pour un exemple)

La dernière étape est donc la transmission après découpage du dossier via WCF, mais ceci est un autre sujet Clignement d'œil

 

Code source complet : [LINK]
EXE & DLL : [LINK]
Code + version compilé : [LINK]

mardi 3 décembre 2013

[Code Sample] Copier un dossier avec ses sous-dossiers et ses fichiers

Bonjour,

  Voici une petite méthode C# qui gère la copie de dossiers. Ce code est suffisamment documenté, je n’ajouterai donc pas plus de détails Clignement d'œil n’hésitez pas à utiliser les commentaires si vous avec des questions/remarques!

/// <summary>
/// Copie complète d'un dossier dans un autre
/// </summary>
/// <param name="sourceFolder">Source des fichiers/dossiers</param>
/// <param name="destinationFolder">Destination</param>
/// <param name="filter">Filtre pour récupérer les fichiers, par défault *.*</param>
/// <param name="overwriteDestinationFolder">Permet de supprimer le dossier destination avant la copie, si true alors le prochain paramètre est inutile. Valeur par défault : true.</param>
/// <param name="overwriteDestinationFiles">Permet d'écraser les fichiers déjà existant dans le dossier destination. Vrai (true) par défaut, utilisé uniquement si [overwriteDestinationFolder] est Faux (false).</param>
/// <remarks>Si une erreur apparait durant une des étapes de la copie (écrasement de fichier ouvert, suppression de dossier bloqué, opération avec accès refusée, ...) 
/// cette méthode renverra l'exception à l'appelant.</remarks>
private void CopyFolder(string sourceFolder, string destinationFolder, string filter = "*.*", bool overwriteDestinationFolder = true, bool overwriteDestinationFiles = true)
{
  //HACK: System.IO.Path.Combine donne des résultats étrange lorsque les dossiers commencent et/ou finissent par '\'
  sourceFolder = sourceFolder.Trim('\\');
  destinationFolder = destinationFolder.Trim('\\');
  if (overwriteDestinationFolder && System.IO.Directory.Exists(destinationFolder))
  {
    try
    {
      System.IO.Directory.Delete(destinationFolder, true);
      //La commande de suppression envoi la demande de suppression à l'OS.
      //Récréer le dossier peut déclencher une exception car il ne serait peut-être pas encore supprimé!
      while (System.IO.Directory.Exists(destinationFolder))
        System.Threading.Thread.Sleep(1);
    }catch
    {
      //impossible de supprimer le dossier, tentative de suppression du contenu
      System.IO.Directory.GetDirectories(destinationFolder).ToList().ForEach(t =>
      {
        System.IO.Directory.Delete(t, true);
        while (System.IO.Directory.Exists(t)) System.Threading.Thread.Sleep(1);
      });
      System.IO.Directory.GetFiles(destinationFolder).ToList().ForEach(t =>
      {
        System.IO.File.Delete(t);
      });
    }
  }
  if (!System.IO.Directory.Exists(destinationFolder))
    System.IO.Directory.CreateDirectory(destinationFolder);
  foreach (var fld in System.IO.Directory.GetDirectories(sourceFolder))
  {
    //HACK : GetFileName retourne la dernière partie du chemin a partir du dernier '\'. Ceci permet de récupérer le dernier "dossier" dans l'arborescence :)
    //ceci : System.IO.Path.Combine(destinationFolder, System.IO.Path.GetFileName(fld))
    //donnera le nouveau dossier destination pour les appels récursifs
    //NOTE : si l'écrasement du dossier destination est vrai, il a déjà été nettoyé précédement nous n'avons donc pas besoin de le renvoyer pour les sous-dossiers
    CopyFolder(fld, System.IO.Path.Combine(destinationFolder, System.IO.Path.GetFileName(fld)), filter, false, overwriteDestinationFiles);
  }
  foreach (var file in System.IO.Directory.GetFiles(sourceFolder, filter))
    System.IO.File.Copy(file, System.IO.Path.Combine(destinationFolder, System.IO.Path.GetFileName(file)), overwriteDestinationFiles);
}

 

  En espérant que ça puisse aider!

 

English Version : here