• Un script powershell pour créer une base de dev à partir de la prod

    Il y maintenant quelques années, alors que je travaillais pour mon premier client : Promod, l'équipe de développement était dotée d'une chose merveilleuse : chaque lundi, notre base de développement était mise à jour à partir des données de la production. Depuis ce jour, j'ai essayé de mettre en place cette astuce sur chacun des projets sur lesquels j'ai eu l'occasion de travailler.

    Je ne vais pas vous dire que c'était fait de façon élégante, ce n'était pas le cas ! Tous les lundi, une restauration de la base de production avait lieu dans l'environnement de dev. Toutes les modifications que nous faisions sur la base étant sauvegardées sous forme de script sql, il était assez facile de rejouer (automatiquement) ces scripts pour obtenir une base de données de developpement opérationnelle.

    Cette semaine, en voulant instaurer ce principe sur un nouveau projet, la procédure vbscript que j'avais déjà utilisé à maintes reprises n'as pas voulu fonctionner pour une raison indeterminée. J'en ai donc profité pour la ré-écrire en Powershell...

    Le script se découpe en plusieurs phases :

    • Dans un premier temps, il faut s'assurer que personne ne bloque la base. La je dois avouer que, comme j'ai plusieurs bases sur lesquelles appliquer ce même principe, j'ai utilisé une méthode un peu bourrin : je redemarre le service Sql Server, en n'oubliant pas de démarrer aussi les services dépedants :
     $depserv = (get-service mssqlserver).DependentServices 
                  | ?{$_.Status -eq 'Started'}
     restart-service mssqlserver -force
     $depserv | start-service
    • un script sql, lancé par osql, s'occupe de la restauration proprement dite
    copy \\backupsrv\live\newproject.bak d:\dev\newproject.bak
    osql -E -Q "RESTORE DATABASE NewProject from disk=
          'd:\dev\newproject.bak' with replace"
    del d:\dev\newproject.bak
    • il suffit ensuite de récupérer tous les scripts de mise à jour depuis Team Foundation Server
    &($env:tfpath) get newproject\datascripts /r
    
    • et de les appliquer un par un sur la base
     dir ($env:scriptdir)+"\*.sql" 
         |% {osql -E -d NewProject -i $_.FullName}

    En (grosso-modo) 10 lignes - en prenant en compte la définition des variables d'environnement pointant vers les dossiers importants et un peu de gestion d'erreur-, le script powershell remplace sa version vbscript de pas loin de 150 lignes. Impressionnant non ?

  • Scripts Powershell de sauvegarde SQLServer

    Bon, pendant que je m'amusais à casser mon serveur, Powershell RC2 est sorti. A l'occasion de cette release, je vous propose deux petites fonctions Powershell :

    • backup-database dbname rootfolder qui lance le backup d'une base (sur le serveur local) SQL Server 2005 dans un dossier
    • backup-alldatabase rootfolder qui fait le tour des bases non-systèmes et lance un backup-database dessus.

    function backup-database($dbName, $rootFolder)
    {
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") 
    | out-null [System.IO.Directory]::CreateDirectory($rootFolder) | out-null $_srv=New-Object "Microsoft.SqlServer.Management.Smo.Server" "(local)" $_bck=new-object "Microsoft.SqlServer.Management.Smo.Backup" $_bck.Action = 'Database'
    $_fil=new-object "Microsoft.SqlServer.Management.Smo.BackupDeviceItem" $_fil.DeviceType='File' $_fil.Name=[System.IO.Path]::Combine($rootFolder, $dbName + "-"
    + [DateTime]::Today.ToString("yyyy-MM-dd")+".bak") $_bck.Devices.Add($_fil) $_bck.Database=$dbName $_bck.SqlBackup($_srv) write-host "Sauvegarde de " $dbName " terminee" } function backup-alldatabase($rootFolder) { [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
    | out-null $_srv=New-Object "Microsoft.SqlServer.Management.Smo.Server" "(local)" $_srv.Databases |
    ?{$_.IsSystemObject -eq 0} |
    %{backup-database $_.Name $rootFolder} }
    Technorati tags: , ,

    del.icio.us tags: , ,

  • Foreach sous Powershell

    Powershell, comme nous l'avons déjà entr'aperçu, est un shell très orienté développeurs. On y retrouve en effet un nombre de paradigmes de la programmation "objets". Parmis ceux-ci, la notion d'itération (c'est à dire la possibilité d'executer un traitement plusieurs fois) est representée par 3 commandes : forwhile et foreach. C'est sur cette derniere - qui est la plus intéressante - que nous allons un peu nous attarder.

    Commençons par un petit exemple très simple : imaginons que vous souhaitiez obtenir la liste des fichiers de sauvegarde SQL dans un dossier. La commande suivante devrait parfaitement vous satisfaire :

    gci *.bak

    Nous allons donc un peu complexifier cet exemple et imaginer que nous allons restaurer (dans un environnement de developpement, de pré-prod ou tout ce que vous voudrez) tous ces fichiers sur l'instance locale de SqlServer (nous utiliserons pour cela la fonction restore-database que vous trouverez ici - Attention, cette fonction est juste un exemple de ce que l'on peut faire pour restaurer une base de données SQL, elle n'est pas destinée à être utilisée en production).Pour ne pas trop chercher à rendre incompréhensible le script , les fichiers de sauvegardes seront supposés être nommés nom_de_la_base-date_de_la_sauvegarde.

    gci *.bak | foreach 
    {
    restore-database ...
    $_.Name.SubString($_.Name.IndexOf("-")) ...
    $_.Name }

    Expliquons un peu cette ligne de commande : l'instruction foreach va executer le block entre accolades pour chacun des objets qu'elle obtiendra de la pipeline. Pour savoir l'objet courant de l'itération (sur quel objet on se trouve actuellement...), nous avons à disposition la variable reservée $_. $_.Name va donc énumérer successivement tous les noms de fichiers récupérés par le gci.

    Ce qui est interessant de savoir, c'est que le contenu d'un foreach n'a pas besoin de se limiter à une seule instruction. Le script suivant est parfaitement valable :

    gci *.bak | foreach 
    {
    restore-database ...
    $_.Name.SubString($_.Name.IndexOf("-")) ...
    $_.Name move $_ ..\done }

    Pour savoir si vous avez bien compris, je vous invite a essayer d'ecrire :

    • un script qui modifie les dates de dernieres modifications de tous les fichiers bak pour simuler la commande touch.
    • un script qui vous donne l'état des services MSSQLSERVER et IISADMIN ainsi que leurs "enfants" (l'agent Sql, les services http, https, etc.). Pour cela, je vous donne une petite aide : pour obtenir les 2 services "racines", vous pouvez faire
    "mssqlserver","isadmin" | get-service
    • un script qui retire tous les fichiers de sauvegarde de toutes les bases sauf le plus récent (et pas question de tricher avec une comparaison par rapport à la date du jour : vous pourriez avoir plusieurs sauvegardes le même jour)

  • Ajouter une commande à Powershell

    Voyons un peu comment faire pour intégrer vos propres commandes dans Powershell, vous allez voir cela n'a rien de sorcier.

    1) Création d'une Cmdet

    Une Cmdlet (prononcez «Commande-lète») est tout simplement une classe .net représentant une commande sous Powershell comme par exemple Get-ChildItem (la cmdlet standard utilisée - ou plutôt aliasée - pour faire un bon vieux dir).

    Comment crée-t-on une Cmdlet donc ? Eh bien, c'est assez simple :

    • créez un nouveau projet «bibilothèque de classe» sous VS2005
    • ajoutez comme référence la dll «System.Management.Automation» (vous la trouverez dans Program Files\Windows PowerShell\v1.0 [update] il vous faudra la récupérer du SDK)
    • créez une nouvelle classe et faites la dériver de System.Management.Automation.Cmdlet
    • ajoutez lui un attribut System.Management.Automation.CmdletAttribute. Ce parametre vous permet de définir le type d'action (paramètre verb) et l'objet (paramètre nounName) sur lequel celui-ci se réalise - pour en savoir plus sur les verbes autorisés, reportez vous à la table suivante : http://blogs.msdn.com/powershell/archive/2006/04/25/583257.aspx)
    • surchargez la méthode BeginProcessing() si vous souhaitez réaliser un traitement avant de traiter les éventuels éléments de la PipeLine
    • et/ou surchargez la méthode EndProcessing() si vous souhaitez réaliser un traitement après de traiter les éventuels éléments de la PipeLine (c'est aussi ici que se font habituellement les traitements de commandes qui ne gèrent pas la Pipeline)
    [Cmdlet("Get","MyUser")]
    public class GetMyUsersCmdlet : Cmdlet
    {
      protected override void< EndProcessing()
      {
        // effectuer le traitement
      }
    }

    Voila, vous avez déjà une Cmdlet qui peut fonctionner. Bon, évidemment, pour l'instant il n'est pas vraiment question d'une commande intéressante, puisque vous ne pouvez pas lui passer de paramètres ni obtenir de résultat. Cela pourrait être suffisant si vous réalisez une commande destinée à rebooter la machine, mais pour le reste c'est un peu juste.

    Voyons donc comment on peut passer des paramètres, mais nous ne nous occuperons pas encore de la Pipeline - cela sera pour un prochain post :

    • pour chacun, ajoutez une propriété en lecture/ecriture (ou un champ) dans votre classe - en faisant bien attention au type que vous utilisez : pas question d'utiliser des types bizarres - mais vous pouvez très bien utiliser une enum.
    • ajoutez sur chacun de ces éléments un System.Management.Automation.ParameterAttribute. Celui-ci vous permettra, après quelques essais, de mieux contrôler la façon dont s'enchainent ou sont obligatoires ou non tous les paramètres.
    • dans votre traitement, utilisez la propriété normalement : elle aura été «remplie» automatiquement par le moteur de PowerShell

    public enum TypeUser
    {
      Administrateurs,
      Utilisateurs
    }

    [Cmdlet("Get","MyUser")]
    public class GetMyUsersCmdlet : Cmdlet
    {
     private TypeUser _typeUser
                = TypeUser.Utilisateurs;
      [Parameter(Mandatory=true)]
      public TypeUser TypeUser
      {
     get { return _typeUser; }
       set { _typeUser = value; }
      }

      protected override void EndProcessing()
      {
       if (_typeUser == TypeUser.Administrateurs)
       {
          // faire le traitement pour les
          // administrateurs
       }
       else
       {
          // faire le traitement pour les
          // utilisateurs normaux
       }
      }
    }

    Il reste encore une chose à faire pour avoir une Cmdlet digne de ce nom : renvoyer des données. Cela est très simple : il suffit d'appeler la méthode WriteObject pour chacunes des «lignes» de résultats. (Rappelons au passage que les Cmdlets ne renvoie pas forcément du texte, mais peuvent renvoyer n'importe quel type d'objet, dans la limite du raisonnable)

    public enum TypeUser
    {
      Administrateurs,
      Utilisateurs
    }

    public class MyUser
    {
         ...
    }

    [Cmdlet("Get","MyUser")]
    public class GetMyUsersCmdlet : Cmdlet
    {
      private TypeUser _typeUser 
                = TypeUser.Utilisateurs;
      [Parameter(Mandatory=true)]
      public TypeUser TypeUser
      {
       get { return _typeUser; }
       set { _typeUser = value; }
      }

      protected override void EndProcessing()
      {
       if (_typeUser == TypeUser.Administrateurs)
       {
          foreach( ... )
          {
             MyUser usr = new MyUser( ...);
             WriteObject(usr);
          }
       }
       else
       {
          ...

       }
      }
    }

    Dans le prochain post, nous verrons comment ajouter votre toute nouvelle Cmdlet dans Powershell.

  • Powershell

    Entrer dans le monde merveilleux des équipes de prod, fait de |,<, >,grep et autres joyeusetés, n'a jamais vraiment été un de mes rêves - trouvant qu'une console MMC et/ou un peu de macros Word/Excel faisait le même boulot ou presque. Cela va peut-être changer avec l'arrivée (en RC) de Windows Powershell (déjà sorti en beta sous le nom de code Monad) !
    Cela fait maintenant a peu près deux heures que je joue avec ce futur remplaçant de la Commande "MSDOS" et, franchement,c'est tout simplement génial.

    Allez, commençons par quelques exemples sympa permettant de voir toute la puissance de ce bidule :

    Cette ligne de commande récupère tous les services sur la machine :
    get-service

    Pour envoyer une ou plusieurs lignes dans un fichier il suffit de faire :
    set-content c:\resultat.txt
    (le set-content s’arrete automatiquement en envoyant une ligne vide)

    Pour les puristes, | existe toujours, on peut donc ecrire :
    get-service | set-content c:\services.txt
    (n’essayez pas cette commande, vous n’auriez pas ce que vous souhaitez !)
    ou plutôt : get-service | export-csv c:\services.csv

    bon, évidemment c’est assez différent de ce que l’on ecrirai en unix et vous allez me dire : "mais c’est super verbeux". Je vous l’accorde – et Microsoft aussi – d’où la notion d’alias qui permettent d’ecrire ceci en lieu et place de la commande précédente :
    gsv | epcsv c:\services.csv

    Jusque là, mis à part une syntaxe un peu particulière, rien de bien spécial… sauf que… l’exemple n°3 (vous savez celui qui ne marche pas) montre quelque chose de primordial en PowerShell : les résultats des différentes commandes, plutôt que de n’être que du texte qu’il faudra analyser pour pouvoir manipuler, sont de vrais objets. Sur lesquels ils est possible d’appeler des propriétés ou des méthodes.

    Le mieux étant de présenter un petit exemple : pour obtenir les processus en cours d’execution, on utilise la commande get-process. Celle-ci vous renvoie, en fait, une liste d’objets System.Diagnostics.Process (oui, oui, l’objet .net), d’ailleurs on peut facilement le vérifier avec la commande for-each (sur laquelle je reviendrai dans un autre billet) : gps | foreach {$_.MainWindowTitle} (affiche le titre de la fenêtre principale pour chaque processus)

    Objet .net ai-je dis ? eh oui ! toute la beauté de PowerShell (et certainement le cauchemard  des gens de la prod qui auront à l’utiliser), c’est que TOUT est en .net : les commandes, les résultats de celles-ci, les environnements etc.

    Du coup pour nous, les développeurs, c’est du bonheur à l’état pur : imaginez les scripts suivants (si vous êtes familier avec .net vous allez aimer)

    [DateTime]::Now.AddDays(-5).ToShortDateString()
    permet d’obtenir une date cinq jours dans le passé, idéal pour comparer avec la date d’un fichier à archiver…

    (new-object Random).next(5000)
    permet d’obtenir un chiffre au hasard entre 0 et 5000

     Sympa non ?

  • Intégration de Powershell

    Cela fait quelques semaines/mois que je poste beaucoup moins, la raison en est assez simple : je suis un peu «à la bourre» sur un projet dont je vous parlerai d’ici quelques semaines. Cela ne veut pas dire que je n’ai pas eu l’occasion de faire des choses dont j’ai envie de parler et parmi celles-ci : l’intégration de PowerShell dans nos applications.

    Si vous avez déjà developpé des applications pour une distribution de type ISV, ou encore mieux une application ASP, vous avez déjà du vous développer des petits programmes pour administrer votre produit et réaliser des tâches telles que :

    • créer des comptes utilisateurs
    • redéfinir des mots de passe
    • faire un rapport d’utilisation pour facturation
    • etc.

    C’est la qu’est entré en jeu PowerShell pour nous.

    L’extensibilité de ce produit est un vrai plaisir… Quoi de plus agréable en effet de pouvoir écrire des commandes de ce type :

    gci monappli:\client1\users
       | foreach {$_.SendPasswordMail()}

    Et pour cela, rien de bien sorcier : deux ou trois classes à implémenter en .net et c’est tout !

    On pourrait même envisager - sous réserve de possibilités techniques... j'ai un peu peur que l'ajout des assemblies powershell dans le GAC limite les possibilité de sécuriser cette affaire - de proposer une version edulcorée de ce Powershell en accès «libre» pour les clients (enfin «libre» : après authentification quand même…). La seule contrainte étant alors de rétirer les modules standard de Powershell. Un client soucieux, par exemple, d’obtenir une liste de factures non payées pourrait se faire son propre export avec un simple :

    gci monappli:\factures 
      | where {$_.PayeeIntegralement -eq false}
     
       | out-mail monadresse@monsite.fr

     et s’il souhaite réaliser un export spécifiques de ses articles :

    gci monappli:\catalogue
       | where {$_.PrixTTC -lt 50}
     
       | export-appliformat -format articles

    Dans les articles suivants, je vous parlerai de comment cela peut être fait.