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

[Code Sample] Copy Folder with sub-folders and files

Hello,

  Here is a small c# method that manage copy of folders. The code is documented enough so I won’t give more details Clignement d'œil do not hesitate to use comments if any questions/remarks!

/// <summary>
/// Copy full folder to another
/// </summary>
/// <param name="sourceFolder">Source of files/folders</param>
/// <param name="destinationFolder">Destination</param>
/// <param name="filter">Filtering to get files, default is *.*</param>
/// <param name="overwriteDestinationFolder">Allow to drop destination folder before copy, if true next parameter is useless. Default is true.</param>
/// <param name="overwriteDestinationFiles">Allow to overwrite files that already exists in destination folder. Default is true, used only if [overwriteDestinationFolder] is false.</param>
/// <remarks>If errors occurs during any step of copy (overwriting open files, deleting locked folders, access denied operations, ...) 
/// this method will just rethrow the exceptions.</remarks>
private void CopyFolder(string sourceFolder, string destinationFolder, string filter = "*.*", bool overwriteDestinationFolder = true, bool overwriteDestinationFiles = true)
{
  //HACK: System.IO.Path.Combine lead to strange results when folders starts and/or ends with '\'
  sourceFolder = sourceFolder.Trim('\\');
  destinationFolder = destinationFolder.Trim('\\');
  if (overwriteDestinationFolder && System.IO.Directory.Exists(destinationFolder))
  {
    try
    {
      System.IO.Directory.Delete(destinationFolder, true);
      //The delete command will send the delete to the OS.
      //Recreating the directory can throw exception because it may not be already deleted!
      while (System.IO.Directory.Exists(destinationFolder))
        System.Threading.Thread.Sleep(1);
    }catch
    {
      //unable to drop the folder, try deleting content
      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 : the GetFileName returns the last part of the path from the latest '\'. This allows to get the last "folder" in the folder tree :)
    //this : System.IO.Path.Combine(destinationFolder, System.IO.Path.GetFileName(fld))
    //will gives the new destination folder for recursive call
    //NOTE : if overwrite of destination folder is true, it has already been cleaned so no needs to send this again for subfolders
    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);
}

 

  Hope this could help!

 

Version Française : ici

vendredi 22 novembre 2013

HowTo: Génération de app.config différent entre "debug" et "release" pour des projets non web

Voici un premier post sur mon blog fraichement nettoyé.

Si vous avez travaillé sur des projets web, vous avez surement utilisé la fonctionnalité qui permet d’avoir une configuration différente (web.config) selon le mode de compilation (par défaut DEBUG ou RELEASE). Si ce sujet vous interresse je vous invite a lire ceci : MSDN (en anglais, VF : MSDN via Translator attention, traduction un peu limite)

A l’origine cette fonctionnalité n’est pas présente “out of the box” pour les autres types de projets. Malgrè tout, il est possible d’implémenter le même pattern en utilisant un AddIn pour Visual Studio : “Configuration Transform”. Voici un exemple d’utilisation sur un projet console.

Nous démarrons donc à partir d’un projet qui a un paramètre (Setting) et un fichier de configuration unique (app.config).

image

Pour ajouter des paramètres spécifiques pour les modes de compilation DEBUG/RELEASE, nous selectionons le fichier “App.config”, avec le menu contextuel (click droit) et on choisi “Add Config Transforms”.

image

Cette opération ajoutera 2 fichiers attaché au App.config, un par configuration de compilation.

image Message étrange “pas de changement” mais ca ajoute bien les fichiers :

image

Si on ouvre le fichier Release, on peut constater qu’il est vide. Si par exemple vous avez un paramètre “SomeSpecial” qui doit avoir une valeur différente pour le mode Release, vous pouvez y ajouter le code suivant :

   1: <?xml version="1.0"?>
   2: <!-- For more information on using app.config transformation
   3:       visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
   4:  <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
   5:    <applicationSettings>
   6:      <WpfApplication1.Properties.Settings>
   7:        <setting name="SomeSpecial" serializeAs="String"
   8:                 xdt:Transform="Replace" xdt:Locator="Match(name)">
   9:          <value>Something for RELEASE</value>
  10:        </setting>
  11:      </WpfApplication1.Properties.Settings>
  12:    </applicationSettings>
  13:  </configuration>

Dans le code ci-dessus, la ligne 7 permet de préciser que la node “setting” doit être remplacé pour celle dont le “name” correspond à “SomeSpecial”. Ceci permet au compilateur en mode Release de remplacer la valeur dans le app.config généré.

Grâce à cette fonctionnalité, vous pouvez vous assurer de ne pas déployer en production de mauvais paramètres, ceux du mode Debug par exemple.

Une dernière chose :

En ajoutant des modèles de configuration, on peut ainsi avoir des générations comme ceci :

image

Le “Add Config Transform” ajoute donc les nouveaux fichiers pour les configurations ajoutées :

image

Dans mon cas, j'ai utilisé ceci pour une application client/serveur, le serveur étant un paramètre de l'application : celui de développement n'est bien sur pas le même que celui de production ou celui de l’environnement de validation (Integration, Quality, …)

Merci à Bruno pour l'info, source : ElBruno

mardi 19 novembre 2013

Cleanup & Re-opening

That’s it, I finished cleaning up this blog which was not updated for a long time!

You will find here soon some post talking about C#, Visual Studio, and a little of “blabla” of course! Some post will be translation of some other blogs for which I didn't found french version, I will try to post others in french and in english too.

From tips to full projects, also with code samples, I will listen to you if have any remarks/suggestions/ideas…

For now, I let comments in “moderation mode” to reduce spam.

See Ya!

Nettoyage & ré-ouverture :-)

Voila, j’ai terminé de nettoyer ce blog qui n’étais plus maintenu depuis un moment!

Vous trouverez donc ici prochainement des post relatif à C#, Visual Studio, et un peu de blabla bien sur! Certains messages seront des traductions d’autres blog pour lesquels je n’ai pas trouvé de version française, je tacherai de poster le reste en français mais aussi en anglais.

De l’astuce au projet complet, en passant par des exemples de codes, je reste à votre écoute si vous avez des remarques/suggestions/idées/…

Pour le moment je met les commentaires en mode “modéré” pour limiter le spam.

A bientôt!