mardi 30 décembre 2008

Nouvelle version de PoshBoard dispo (v0.3.5) : compatible Silverlight 2.0 Finale


Première bonne résolution de l'année
Sortir une nouvelle version de PoshBoard!

PoshBoard v0.3.5 est maintenant disponible sur codeplex juste là

La version 0.4 arrive, j'attends des mises à jour de version pour dropthings ainsi que d'autre nouveaux composants ...

une présentation de PoshBoard 0.4 aura lieu au prochain Techdays, des nouvelles très bientôt !

Quoi de Neuf ?

  • -PoshBoard est maintenant compatible Silverlight 2.0 Finale et editable entièrement depuis Web Developper Express SP1 !
  • -Mise-à-jour vers les dernières version de AgDatagrid (Decembre 2008) et Visifire (2.0.5 Beta)
  • - Fix de bugs dans la génération des datagrids
  • - Nouvelles fonction d'édition pour les charts Visifire : vous pouvez maintenant selectionner le type de graph et les paramètres (titres...) dans la fenêtre d'édition.
important : Pour construire les graphiques, vous devez maintenant passer une ou plusieurs hashtable avec vos données à la fonction integrée "out-visifire"

out-visifire($myHashTable)
ou
out-visifire($myHashTable1,$MyHashTable2)

  • -Le guide utilisateur a été mis à jour, prenez le temps de le lire pour une installation rapide et sans soucis !

Une autre bonne nouvelle

Jeffrey Snover (Createur de PowerShell) a posté 2 très bons articles sur PoshBoard et la gestion des Hashtables sur le blog de la PowerShell Team : vous pouvez les lire ici et ici.

Merci Jeffrey pour ton support et encouragements sur le projet !

Très bonne année à tous !

N'hésitez pas à me faire part de vos feebacks





mercredi 19 novembre 2008

Des nouvelles de poshboard 0.4

Quelques nouvelles de PoshBoard :

Le développement continue bien sûr ! :) malgré un planning pro un peu chargé ces temps-ci.

Avec l'arrivée de Silverlight 2.0 en finale et l'arrivée prochaine de Dropthings 1.8.0, une nouvelle mouture de PoshBoard va voir le jour :

Au programme, la mise à niveau sur Dropthings et Silverlight 2 bien sûr, mais aussi de nouvelles fonctionnalités, donc voici les plus importantes :
  • Nous allons introduire la création dynamique de contrôles basée sur ironPython, qui va permettre la création à la volée de gadgets (par code python ou avec un petit designer)
  • Je compte aussi introduire la possibilité de gérer l'import/export de widgets pour permettre l'échange de ceux-ci entre nous, gens de bonne compagnie.

Vos suggestions sont plus que jamais les bienvenues, n'hésitez pas à m'en faire part !

des nouvelles concrètes très bientôt donc.

mercredi 10 septembre 2008

PowerShellPlus professional GRATUIT pour les auditeurs de Powerscripting Podcast !

Une info plutôt sympa dans le numéro 40 de PowerScripting Podcast : Si vous téléchargez la Beta de PowerShell plus en suivant le lien fournis sur leur page, vous aurez droit à une licence GRATUITE de PowerShellPlus à sa sortie finale !

un seul conseil : Ecoutez les Powerscripting Podcast (toujours très interessant qui plus est pour ceux qui pratique l'anglais), et suivez le lien ci-dessus pour obternir votre licence !

mercredi 27 août 2008

Guide d'installation pour PoshBoard

Le guide d'installation de PoshBoard est disponible ici.

Update : le guide est maintenant disponible en Français !

Ce guide explique pas à pas comment utiliser la source avec Visual Studio ou Web Developper Express, comment publier le site web et utiliser les widgets.

bonne lecture !

mardi 26 août 2008

PoshBoard 0.3 est de sortie !

update : les sources sont maintenant disponibles sur codeplex

Tout avance bien, et je suis heureux de vous présenter la version 0.3 de Poshboard fraichement sortie des eaux.



La nouvelle version de dropthings permet de simplifier la procédure d'install : vous trouverez un "quickstart" dans la page Releases de Codeplex.

Je prépare un tutorial complet pour installer/configurer le tout ainsi que sur l'utilisation des widgets mais il est tard pour ce soir, nous verrons cela demain ! :) J'ai mis l'essentiel sur la page release de codeplex, n'hésitez pas à poser vos questions dans le forum de discussion sur codeplex

Plusieurs grosses nouveautés sont en préparation qui je suis sûr vous intéresserons particulièrement ! PoshBoard va assurement prendre de l'ampleur dans les prochaines semaines.

La version actuelle est encore en chantier, des petits bugs liés à siverlight, dropthings ou mon code parfois experimental viendront pimenter votre expérience, mais ceci va être rapidement corrigé.

Amusez vous bien et faites moi part de vos impressions et suggestions !

mercredi 30 juillet 2008

PoshBoard, seconde vidéo : PowerShell génère des Datagrids Silverlight

English version here

Voici une nouvelle vidéo de Poshboard, mon projet open source de portail IT. Dans cette vidéo, vous découvrirez les dernières nouveautés arrivant avec la version 0.3 (disponible ici sur codeplex prochainement) :

Lancez la lecture, cliquez sur HD, puis passez en plein écran (ou téléchargez là ici), spécialement car ma capture vidéo est légèrement partie en vrille côté rescaling :-)


Poshboard Seconde video : Datagrid (Silverlight 2, AgDataGrid) et nouvelle GUI from pilosite on Vimeo.


Au programme ?

De nouveaux Widgets :


- Gestion des datagrid Silverlight 2 : générez les à partir d'un simple objet PowerShell
- Utilisation du contrôle AgDatagrid de developer Express, une excellente datagrid gratuite et open source en Silverlight bourrée de bonnes idées.
- La gestion de sortie standard PowerShell

Une nouvelle interface :


Cette version est basée sur la version 1.6.0 de Dropthings, voyez la en action et découvrez les nouveautés (design amélioré, gestion dynamique de la taille et position des colonnes...)

Un tutorial d'install arrive, expliquant :

- Comment installer le portail sur un serveur (pré-requis, configuration IIS, sécurité...)
- Comment jouer avec au moyen de Visual Studio 2008 et Web developper express 2008 SP1/ SQL 2008 CTP

Posez vos questions dans les commentaires, et n'hésitez pas à utiliser la gestion des issues/discussions sur le site de codeplex pour me permettre de faire évoluer au mieux le projet !

Bonne vidéo ;)

vendredi 25 juillet 2008

AgDatagrid et Silverlight Datagrid : comment créer dynamiquement une Datasource ?

English version available HERE

Le code source du projet est disponible ici.

Un exemple live juste en dessous !



Je travaille actuellement sur le composant AgDataGrid, un excellent contrôle Silverlight gratuit et open source de DevExpress (Démonstration complète ici). Bien entendu, mon but est d'implémenter celui-ci comme widget dans PowerShell Dashboard ;-)

J'ai rencontré un petit problème, qui est d'ailleurs valable pour le contrôle DataGrid standard de Silverlight 2, et plus généralement pour la génération dynamique d'objets typés en .NET :

Comment créer dynamiquement la Datagrid ?

AgDatagrid et la datagrid Silverlight 2 utilisent un objet de type IList pour construire la Datasource. Quand on utilise du code managé (C#/VB.NET...), vous devez créer une classe représentant vos données pour pouvoir construire cette IList :

   1: public class Person {   
   2:                 public string Name {   
   3:                      get;   
   4:                      set;   
   5:                 }   
   6:     
   7:                 public string City {   
   8:                      get;   
   9:                      set;   
  10:                 }   
  11:     
  12:                 public string State {   
  13:                      get;   
  14:                      set;   
  15:                 }   
  16:            }   
  17:     
  18: new Person() {   
  19: me = "Michael Jordan", City="Chicago", State="IL" },   
  20: new Person() {   
  21: me = "Kobe Bryant", City="Los Angeles", State="CA" },   
  22: new Person() {   
  23: me = "Shaquille O'Neil", City="Miami", State="FL" },   
  24: new Person() {   
  25: me = "Patrick Ewing", City="New York", State="NY" }    


  26:  



C'est impeccable quand vous connaissez votre source de données, mais comment faire quand vous avez du contenu dynamique (comme par exemple le retour d'un script PowerShell) ?

Je veux en effet utiliser la grid pour afficher mes données venant de PowerShell, je ne connais donc pas par avance le type des objets ni le nombre de colonnes...

Voici le point clé : je ne peux pas utiliser de classe statique pour définir mes données dans le contrôle Silverlight.

Game Over ?


Pas encore!

Nous avons deux stratégies à notre disposition pour arriver à notre résultat :

- Utiliser la programmation par langage dynamique avec Silverlight (Ironpython, IronRuby, JScript managé...) pour construire dynamiquement le contrôle ?

- Créer malgré tout dynamiquement la classe en code managé ?

Utiliser les langages dynamiques serait une solution idéale (et pour moi la meilleure à terme). Malheureusement, nous sommes encore au stade de la Beta côté siverlight et DSL, et il est aujourd'hui encore difficile de créer dynamiquement des contrôles Silverlight 2 Beta 2 dans ce contexte (pour le moment) car tout n'est pas encore figé dans le marbre :), je ne vais donc pas rentrer dans cette explication pour le moment (J'attends avec impatience quelques exemples de la part de l'équipe Silverlight sur ce sujet )

Alors, pouvons nous créer dynamiquement la classe dans notre code managé ?

Ma réponse est : OUI !

Comment générer des objets typés dynamiquement en .NET

Après quelques recherches (INTENSIVES est le mot juste), j'ai trouvé un post excellent de Vladimir Bodurov sous le thème : "How to generated dynamically typed objects in .NET" :

http://www.simple-talk.com/dotnet/.net-framework/dynamically-generating--typed-objects-in-.net/

Bingo ! Vladimir nous donne le code magique pour construire nos objets dynamiquement que nous pouvons simplement implémenter comme Datasource, sans coder de classe statique !

Ok, génial. Voyons maintenant comment j'ai utilisé ceci avec AgDatagrid (Vous pourrez très facilement convertir cet exemple avec le contrôle standard Datagrid de Silverlight 2, mais il n'est pas aussi classe et riche que le super contrôle de Devexpress :-) )

Allons-y messieurs dames, jetons un oeil dans le code !

Mon exemple est l'implémentation la plus basique possible afin de bien vous faire comprendre le principe

mon fichier Page.xaml est plutôt simple :

agdynamic2

J'ai posé 2 Textboxes (Une pour les noms de colonnes, une pour les données), un bouton pour construire la datagrid, et la datagrid elle-même. Nous pourrions récupérer les données de l'extérieur, mais ceci est un autre sujet, je souhaite rester volontairement simpliste.

Voici le code XAML de la page:



   1: <UserControl x:Class="AgDatagridDynamicData.Page"   
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
   4:     xmlns:ag="clr-namespace:DevExpress.Windows.Controls;assembly=DevExpress.AgDataGrid.v8.2"   
   5:     xmlns:local="clr-namespace:AgDatagridDynamicData"   
   6:     Width="800" Height="600">   
   7:     <Grid x:Name="LayoutRoot" Background="black">   
   8:         <Grid>   
   9:             <Grid.RowDefinitions>   
  10:                 <RowDefinition Height="Auto"></RowDefinition>   
  11:                 <RowDefinition Height="Auto"></RowDefinition>   
  12:                 <RowDefinition Height="Auto"></RowDefinition>   
  13:                 <RowDefinition Height="Auto"></RowDefinition>   
  14:             </Grid.RowDefinitions>   
  15:             <Grid.ColumnDefinitions>   
  16:                 <ColumnDefinition Width="Auto"/>   
  17:                 <ColumnDefinition Width="Auto"/>   
  18:             </Grid.ColumnDefinitions>   
  19:             <TextBlock Width="Auto" Grid.Row="0" Grid.Column="0" Text="Columns (Parse with ';'):" />                   
  20:             <TextBox x:Name="ColumnsData" Background="blackSmoke" Width="500" Grid.Row="0" Grid.Column="1" Text="Column1;Column2;Column3"></TextBox>   
  21:             <TextBlock Width="Auto" Grid.Row="1" Grid.Column="0" Text="Data (Parse Row with '*', cells with ';' :" />
  22:             <TextBox x:Name="CellsData" Background="blackSmoke" Width="500" Grid.Row="1" Grid.Column="1" Text="DataA1;DataA2;DataA3*DataB1;DataB2;DataB3"></TextBox>     
  23:             <Button Click="Button_Click" Grid.Row="2" Grid.ColumnSpan="2" Content="Generate AgDataGrid"></Button>   
  24:             <ag:AgDataGrid Width="800" Height="400" Grid.Row="3" Grid.ColumnSpan="2" AutoGenerateColumns="True" x:Name="mygrid">   
  25:                 </ag:AgDataGrid>   
  26:         </Grid>           
  27:     </Grid>   
  28: </UserControl>



Toute la subtilité est dans la page.xaml.cs, regardons la de plus près.

J'utilise seulement les données des textboxes du contrôle Silverlight (Vous verrez dans la version Widget pour PoshBoard comment vous y prendre pour aller chercher les données à l'extérieur)

J'utilise simplement du parsage de string pour construire ma Datagrid :
  • Les colonnes sont splittées avec un ";"
  • Les données sont splittées avec "*" pour chaque ligne, et dans ces lignes les cellules sont splittées avec un ";"
Bien sûr, il serait plus sage de plutôt utiliser XML pour définir ces données, mais restons dans notre optique de simplicité.

Colonne :

Column1;Column2;Column3

Données :

DataA1;DataA2;DataA3*DataB1;DataB2;DataB3*DataC1;DataC2;DataC3

Première chose, Je met directement la classe DataSourceCreator de Vladimir dans le namespace de mon contrôle, sans toucher à rien (C'est ce que j'apelle du code magique)

agdynamic1


Après avoir insérer le code pour appelé les différentes libs nécessaire à la classe de Vladimir(System.Reflection, etc...), définissons le code principal de ma page :

Voyons notre classe principale (UserControl ) :



   1: public partial class Page : UserControl   
   2:     {   
   3:     string Columns;   
   4:     string Data;   
   5:         public Page()   
   6:         {   
   7:             InitializeComponent();               
   8:         }   
   9:     
  10:         private void Button_Click(object sender, RoutedEventArgs e)   
  11:         {   
  12:     
  13:             mygrid.Columns.Clear();   
  14:             // Adding Columns to the AgDatagrid   
  15:             // With Column type added in the Columns string    
  16:             // we could use a Type checker and setting Column Type   
  17:             // from code behind :    
  18:             // mygrid.Columns.Add(new AgDataGridTextColumn() { FieldName = SingleColumn});     
  19:             Columns = ColumnsData.Text;   
  20:             Data = CellsData.Text;   
  21:             foreach (string SingleColumn in Columns.Split(';'))   
  22:             {              
  23:                 mygrid.AddColumn(SingleColumn);
  24:             }
  25:  
  26:             // Here we set the DataSource with a direct call to the function of
  27:             // Vladimir            
  28:             mygrid.DataSource = GenerateData().ToDataSource();        
  29:         }

Que fait-on ici ?

Rien de spécial dans la fonction Public Page() : J'initialise le composant (c'est le code par défaut)

Tout est dans la fonction "Button_Click" :



   1: private void Button_Click(object sender, RoutedEventArgs e)   
   2: {
   3:  
   4:     mygrid.Columns.Clear();
   5:     // Adding Columns to the AgDatagrid
   6:     // With Column type added in the Columns string 
   7:     // we could use a Type checker and setting Column Type
   8:     // from code behind : 
   9:     // mygrid.Columns.Add(new AgDataGridTextColumn() { FieldName = SingleColumn});  
  10:     Columns = ColumnsData.Text;
  11:     Data = CellsData.Text;
  12:     foreach (string SingleColumn in Columns.Split(';'))
  13:     {                
  14:         mygrid.AddColumn(SingleColumn);
  15:     }
  16:  
  17:     // Here we set the DataSource with a direct call to the function of
  18:     // Vladimir            
  19:     mygrid.DataSource = GenerateData().ToDataSource();        
  20: }


Premièrement, je vide la datagrid (sans ça, ma fonction ferait de l'ajout, pas de la création from scratch à chaque click)

Ensuite nous récupérons les données des 2 textbox, puis nous allons créer les colonnes :


   1: foreach (string SingleColumn in Columns.Split(';'))
   2: {
   3:     mygrid.Columns.Add(new AgDataGridTextColumn() { FieldName = SingleColumn });
   4: }


Je génère ici que des colonnes de type "texte". Nous pourrions très facilement définir le type dans la chaine de caractère pour implémenter tout ce que propose AgDatagrid, mais encore une fois je suis resté basique (La faute peut-être à mon pot Chez Octo d'hier) :

Ensuite, je défini la Datasource:


   1: mygrid.DataSource = GenerateData().ToDataSource(); 


Nous faisons appelle à la fonction GenerateDate dans la classe (J'ai juste récupéré l'exemple de Vladimir Que j'ai réadapté) :



   1: public IEnumerable<IDictionary> GenerateData()
   2: {
   3:     string[] dataSplit = Data.Split('*');
   4:     int RowNumber = dataSplit.Count();
   5:     string[] ColumnSplit = Columns.Split(';');
   6:     int ColumnNumber = ColumnSplit.Count();
   7:     for (var i = 0; i < RowNumber; i++)
   8:     {
   9:         string[] cellData = dataSplit[i].Split(';');
  10:         var dict = new Dictionary<string, object>();
  11:         for (var j = 0; j < ColumnNumber; j++)
  12:         {
  13:             dict[ColumnSplit[j]] = cellData[j];
  14:         }
  15:        yield return dict;                
  16:     }             
  17: }



tout ce passe dans ce code précis. comment cela fonctionne-t-il donc ? On déclare un dictionnaire et nous l'alimentons avec des séries Clés/valeurs :

Le clé est le nom de la colonne visée , value est le contenu de la cellule à ajouter. Comme vous le voyez on défini avec cette méthode dynamiquement le nom de colonne visée à partir de la référence de notre string Column :

dict[ColulmnSplit[J]] = ...

c'est ce procédé qui nous évite d'avoir a définir la classe, c'e'st tout simplement excellent.

Et c'est tout !

Comme vous pouvez le constater, il est très facile d'alimenter dynamiquemenet une Datasource avec ce procédé.

Pour aller plus loin, nous pourrions utiliser un format XML pour la récupération des données, choisir un type de données pour les colonnes en fonction du type fourni en entrée.... les possibilité sont infinies !

Un grand merci à Azret et l'équipe support de DevExpress pour leur aide réactive, et merci à Vladimir Bodurov pour son superbe exemple de code que je vous invite à consulter pour comprendre tout le mécanisme !

Des questions ? les commentaires sont à votre disposition !

AMUSEZ VOUS BIEN AVEC SILVERLIGHT ET AGDATAGRID !

vendredi 18 juillet 2008

PoshBoard présenté dans le PowerShell Podcast 33

Dans le dernier numéro de PowerShell Podcast (excellent podcast sur PowerShell que je vous invite à découvrir si vous n'avez jamais pratiqué), Jonathan Walz et Hal Rottenberg parlent (entre autre!) de mon site et présente le projet PoshBoard.

Merci à eux de l'intérêt qu'il porte à mon travail, ça me fait réellement plaisir ;), vous pouvez retrouver ce podcast sur leur site PowerScripting

jeudi 10 juillet 2008

Poshboard dispo en preview !

Suite à plusieurs demande pour tester le portail, j'ai mis une pré-version disponible dès à présent sur codeplex en cliquant ICI ou rendez vous sur http://www.codeplex.com/poshboard

Amusez-vous bien et n'hésitez pas à me poser vos questions !

samedi 5 juillet 2008

PowerShell Dashboard : ASP.NET, Silverlight et PowerShell dans un shaker !

English Version available here

Mis-à-Jour : le code source d'une préversion est maintenant disponible sur CodePlex, cliquez ICI ou rendez vous sur http://www.codeplex.com/poshboard

Je vous propose une petite vidéo de présentation de mon projet "PowerShell Dashboard" (PoshBoard pour les intimes) actuellement en développement et prochainement disponible via la forge Octo.

Il s'agit d'un projet open source dont l'objectif est de mettre à disposition un portail très modulaire basé sur PowerShell. L'idée est de reprendre le concept des interfaces tels que igoogle, PageFlakes ou Netvibes, mais orienté admin système.

Pourquoi ? pour pouvoir créer très facilement des dashboards infrastructure sans avoir à coder en ASP.NET / Ajax ou autre : la seule compétence requise est le scripting.

Ce projet s'appuie sur Dropthings, le portail ASP.NET / AJAX d'Omar AL Zabir, réorienté en intranet. Pour cette première démonstration et un premier widget, je m'appuie sur le rendu de graphique (Colonnes, camembert...) grâce à l'excellente librairie de graphique Silverlight VISIFIRE, elle aussi gratuite et open source.

Cette première démo présente le concept général du portail que je vous mettrai prochainement à disposition en téléchargement libre. Viendra ensuite d'autres composants et innovations pour vous apporter toujours plus de fun dans l'utilisation de PowerShell ! :)

Trève de blabla, voici la vidéo de démonstration :



PowerShell Dashboard from pilosite on Vimeo.

Cette vidéo pour votre confort étant au format HD 1280x720,Je vous invite très fortement a cliquer sur le l'icône HD (après avoir lancé la lecture) afin de voir cette présentation en haute résolution (Mettez ensuite la vidéo en plein écran pour avoir la résolution native optimale). Vous pouvez aussi simplement cliquer ici et passer en plein écran. Vous pourrez avec ce lient télécharger la vidéo au format wmv.

Des questions ? n'hésitez pas à poster un commentaire ou me joindre par mail en cliquant ici

Vous souhaitez intégrer en avant première ce type de technologie dans votre entreprise, découvrir les autres composants en cours d'élaboration, vous former ou former vos équipes sur PowerShell ? alors contactez moi :

je m'occupe au sein de la société OCTO TECHNOLOGY d'une offre infrastructure dédiée à ces thématiques, et c'est avec grand plaisir que nous pouvons étudier avec vous vos besoins concrets et vous apporter le conseil et l'aide à l'intégration sur tout projets de développement orienté infrastructure tels que ce portail, et bien plus.

Demandez plus d'informations en passant par la rubrique contact de notre site internet !

mercredi 25 juin 2008

Génération de document Office avec PowerShell

Nous avons présenté lors des derniers Techdays un POC de génération automatique de documents basée sur PowerShell et OpenXML.

C'est un concept vraiment interessant côté production car cela permet d'obtenir des documents dynamiques qui peuvent être mis à jour à la demande : Analyse de vos serveurs de messagerie, audit Active Directory, Tableau de bord sur vos serveurs de fichiers...

L'intérêt de cette approche est de mettre cette génération à la portée du scripting (PowerShell en l'occurence), alors qu'il fallait jusqu'à présent soit beaucoups d'astuces (et affronter pas mal de limitation dans la richesse des docs générées), soit passer par du développement VSTO.

Eric White à publié récemment un projet dans ce sens sur Codeplex qui concrètise avec brio cette démarche : il met à notre disposition un snapin particulièrement bien vu qui simplifie énormément les choses et permet de générer n'importe quel type de doc office 2007, et ceux sans avoir office installé grâce à la magie de l'openXML !

Je vous invite à regarder cette vidéo et à télécharger au plus vite le projet sur codeplex, vous ne serez pas déçu du voyage ;)

vendredi 23 mai 2008

PowerShell et ASP.NET part 3

Nous voici rendu à la troisième et dernière partie de ce petit tutorial sur PowerShell et ASP.NET

Nous allons voir ici comment piloter un script depuis un formulaire web, et comment déployer ce site sur un serveur IIS convenablement.

Piloter un script

Nous avons vu précédemment comment exécuter du code PowerShell à partir d'une Textbox. Voyons maintenant comment nous pouvons piloter un script en modifiant ces paramètres à la volée.

Ceci est pratique quand vous voulez par exemple faire un portail de provisioning ou d'audit. Plutôt que de proposer à l'utilisateur de taper du code PowerShell, vous pouvez plutôt proposer un formulaire qui va se charger d'exécuter un script avec des paramètres.

Pour illuster ceci, voici un exemple de recherche d'information avec WMI qui peut prendre en argument un nom de machine. j'ai légèrement modifié notre exemple de départ en rajoutant une petite Textbox qui va prendre ce nom de machine :

powerASP4

Voyons maintenant ce qui change côté code. En fait, très peu de chose : Nous allons à présent lire un script incorporé au site web, modifier le contenu et l'exécuter.

Voici le script ajouté au projet :

$strComputer = "*ComputerName*"
get-wmiobject -class "Win32_LogicalDisk" -namespace "root\cimv2" -computername $strComputer where{$_.DriveType –eq 3}out-string


Un script très classique de requête WMI. Comme vous pouvez le voir, j'ai mis le nom de machine en argument. J'ai mis une référence arbitraire de nom "*ComputerName*", qui va me servir pour remplacer cette valeur de variable par le contenu de la TextBox du formulaire.

Voyons le changement opéré dans le code :



        // On the fly script modification before execution


        StreamReader objReader = new StreamReader(Server.MapPath("~/scripts/WmiAsset.ps1"));
        string strContent = objReader.ReadToEnd();
        strContent = strContent.Replace("*ComputerName*", TxtComputerName.Text);
        this.executePowerShellCode(strContent);


Première chose, nous allons lire le fichier contenant le code de notre script. Je l'ai rangé dans un sous-répertoire nommé "scripts", et on utilise simplement à nouveau la librairie system.IO


StreamReader objReader = new StreamReader(Server.MapPath("~/scripts/WmiAsset.ps1"));


Ensuite nous allons mettre l'intégralité du contenu dans une variable, et fermer l'objet StreamReader qui ne nous servira plus :


string strContent = objReader.ReadToEnd();

Ensuite nous utilisons simplement la méthode replace sur cette string, en changeant le code avec le contenu de notre Textbox qui contient le nom de machine.


strContent = strContent.Replace("*ComputerName*", TxtComputerName.Text);

C'est pour ça qu'il est important que le contenu de la variable modifiée soit unique (ici *ComputerName*). Comme vous avez la maitrise du code PowerShell utilisé, ceci est normalement facile à obtenir.

Voilà qui est fait, il ne nous reste plus qu'à exécuter le code comme dans l'exemple précédent.


this.executePowerShellCode(strContent);

Et voilà, rien de bien compliqué si vous avez saisi les précédentes parties. L'intérêt ici est qu'il suffit, en cas de modification de la requête à effectuer, d'aller simplement modifier le fichier .PS1 du répertoire script sur le serveur web, sans avoir à lancer un éditeur C#/ASP.NET.

Vous pouvez bien sûr mettre plusieurs paramètres dans le formulaire. Il reste des choses importantes à implémenter pour une gestion "pro" (prise en compte des erreurs de scripts PowerShell, gestion plus poussé des modes de sorties de script, un contrôle plus adapté que la Textbox pour le suivi des sorties de scripts, etc...) mais vous avez maintenant les bases pour vous lancer, et surtout je l'espère une meilleure compréhension du principe d'interaction envisageable entre le monde web et le scripting PowerShell.

Voyons a présent comment déployer ce portail sur un serveur IIS.

Déploiement du site web

L'intérêt de notre configuration est de pouvoir utiliser un compte de service pour l'exécution des tâches (en l'occurence, l'exécution de nos scripts). Cela simplifie la définition des droits et le modèle de délégation, car il n'est plus nécessaire de donner des droits administratifs spécifiques à chaque utilisateur.

Nous avons néanmoins le besoin de connaitre l'identité de l'utilisateur à l'origine de l'exécution d'une commande afin de se conformer au pré-requis de sécurité sur la production.

La stratégie pour votre déploiement côté IIS doit donc être la suivante :

Tout d'abord filtrer l'accès au site. Pour cela, il suffit simplement de définir des droits appropriés côté NTFS sur l'emplacement de publication. L'idéal étant de définir un groupe d'accès auquel vous donner les droits de lecture sur le site (côte NTFS), et de rajouter les membres adéquats.

Côté IIS, il suffit de faire une publication classique, en désactivant l'accès "anonymous" au site web, et de sélectionner "Integrated Windows Authentification"

powerASP5

Ensuite, il faut définir un application pool pour déléguer l'exécution du code à un compte de service. Pour cela, créer un nouvel application pool, ouvrez ses propriétés et définissez l'Identité dans l'onget approprié.

powerASP6

Il ne vous reste plus qu'à définir cet application pool dans votre site web :

PowerASP7

Modifiez enfin votre web.config pour prendre en charge l'impersonalisation.

Vous devez avoir la ligne "authentication mode", rajoutez en dessous le code suivant :


        <authentication mode="Windows"/>
    <identity impersonate="true"/>
Et voilà, votre site est maintenant prêt pour l'impersonalisation, et l'accès est filtré par les droits NTFS.

En espérant que ce petit tutorial vous aura donner quelques idées, je reviendrai très prochainement sur un concept un peu plus poussé de portail web combinant ASP.NET 3.5, PowerShell, AJAX et Silverlight !

Voici la source de l'exemple de cette partie PowerShellASP2

mercredi 21 mai 2008

PowerShell et ASP.NET Part 2

Rappel : le code source du site présenté est disponible ici.

Vous avez besoins des éléments suivants pour l'utiliser :

WebDevelopper Express Edition

Ajax controls toolkit et library

Voyons maintenant un peu plus dans le détail l’architecture de ce site web et les différents blocs de code.

WEB.CONFIG

Commençons par le plus simple : le fichier web.config de notre projet.

J’ai apporté ici très peu de modifications : notre premier objectif est de rajouter les références des assemblies PowerShell au projet, afin de pouvoir créer un runspace et un pipeline pour exécuter notre code.

<assemblies> 
    <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> 
    <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> 
    <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> 
    <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> 
    <add assembly="System.Management, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/> 
    <add assembly="System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> 
</assemblies>

Ceci va nous permettre de référencer ces dll dans le code-behind. Bien, passons maintenant a quelque chose de plus consistant : voyons notre interface utilisateur !

DEFAULT.ASPX

Vous pouvez faire votre design en utilisant simplement du glisser/déposer des différents éléments (textbox, combobox, etc…). Cet aspect est assez simple, pour ceux souhaitant apprendre les bases de la création d’interface ASP.NET, je vous propose la lecture des excellents tutos de Didier Danse sur developpez.com, notamment les 2 premiers tomes.

Hormis ces aspects classiques de création de formulaire, voici les particularités de notre site web pour PowerShell :

UpdatePanel

Dans la première partie de ce tutorial, je vous est indiqué que nous utilisons un peu d’Ajax pour :

· Gérer un Timer
· Gérer le rafraichissement de la Textbox de sortie

Pour pouvoir utiliser les extensions AJAX dans notre page web, il faut comme pré-requis tout d’abord placer un ScriptManager dans le document (qui va permettre la prise en charge des autres contrôles AJAX)

Je l’ai placé en début de form, classiquement :

<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>

Passons à la suite, en faisant abstraction des petites fioritures en haut de la page, le gros de notre interface est ici :

<asp:TextBox ID="TxtPowerShellScript" runat="server" TextMode="MultiLine" Width="597px"
            Height="194px" BackColor="#012456" ForeColor="#EEEDF0" Wrap="False"></asp:TextBox>
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Button ID="BtnExecuteScript" runat="server" Text="Launch Script" OnClick="BtnExecuteScript_Click" />
                <br />
                Output :
                <br />
                <asp:TextBox ID="TxtResult" runat="server" Height="199px" TextMode="MultiLine" Width="600px"
                    Wrap="False" BackColor="#012456" ForeColor="#EEEDF0"></asp:TextBox>
                <br />
                <asp:Timer ID="Timer1" runat="server" Enabled="False" Interval="100" OnTick="Timer1_Tick" />
            </ContentTemplate>
        </asp:UpdatePanel>
Le premier élément est notre TextBox pour taper le script :

<asp:TextBox ID="TxtPowerShellScript" runat="server" TextMode="MultiLine" Width="597px"
          Height="194px" BackColor="#012456" ForeColor="#EEEDF0" Wrap="False"></asp:TextBox>

Rien de particulier ici, je l’ai nommé “TxtPowerShellScript” pour respecter un peu de normalisation, je l’ai définit en multiline et mis des couleurs proches de la console PowerShell pour le fun.

Ensuite, vous pouvez voir que mon bouton et la deuxième TextBox sont encapsulés dans un contrôle UpdatePanel. C’est ceci qui me permet de ne rafraichir que le contenu encapsulé et non la page entière.

On y retrouve aussi notre Timer :

<
asp:Timer ID="Timer1" runat="server" Enabled="False" Interval="100" OnTick="Timer1_Tick" />




L’intervalle (Interval="100") defini le temps de rafraichissement du timer (en milliseconde), et le OnTick (OnTick="Timer1_Tick") la fonction appelée dans le Code-Behind. J’ai désactivé le Timer par défaut (Enabled="False")afin de ne pas faire de charge inutile. Il sera activé qu’au click sur notre bouton.

Voilà pour l’interface. Vous voyez qu’hormis la question de l’UpdatePanel (qui est après tout un contrôle comme les autres), nous ne sortons résolument pas des sentiers battus. Voyons maintenant ce qui se passe en coulisse dans notre Code-Behind, qui est le gros du morceau.

DEFAULT.ASPX.CS

Nous arrivons maintenant dans le code C# qui va gérer notre invocation de PowerShell et le rafraichissement de la TextBox de sortie.

Pour commencer par le commencement, vous pouvez voir en début de code que j’appel en plus des instances par défaut du site asp.net généré par Visual 3 dll :


using System.Management.Automation;

using System.Management.Automation.Runspaces;

using System.IO;


System.Management.Automation et .Runspaces sont nécessaires pour faire créer le Runspace et le Pipeline. System.IO va nous servir pour la conversion du contenu de la TextBox de script comme nous allons le voir plus loin.

Passons maintenant au code proprement dit. Nous allons tout d’abord créer les objets Runspace et Pipeline pour qu’il soit exposé sur l’ensemble de notre classe.

Runspace runspace = RunspaceFactory.CreateRunspace();
Pipeline pipe;

Nous allons maintenant analyser le code en partant de l’action de l’utilisateur : le click sur le bouton. La fonction (que vous trouvez ligne 85) est la suivante :

protected void BtnExecuteScript_Click(object sender, EventArgs e)
{
    string strCurrentId = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
    // Enable timer and disable button, clear TxtResult textbox
    this.Timer1.Enabled = true;
    this.BtnExecuteScript.Enabled = false;
    this.TxtResult.Text = "";

    // put the username at the beginning of the output (optional)
    Session["PowerTrace"] = "Initiateur de la demande : " + strCurrentID + "\r\n";

     // Gather script from the TxtPowerShellScript and convert it from html to clean text
    // then call executePowerShellCode function with the result
      string strContent = TxtPowerShellScript.Text;
    StringWriter writer = new StringWriter();
     Server.HtmlDecode(strContent, writer);
    this.executePowerShellCode(writer.ToString());
}

Voyons un peu ce qu’il se passe. Pour commencer, je crée une string strCurrentID qui va récupérer le nom de l’utilisateur en cours. Ceci n’est pas obligatoire dans notre exemple, mais nous verrons par la suite qu’il est intéressant pour nous de connaitre cette information. En effet, dans le cadre du déploiement de ce site ASP.NET, c’est un compte de service qui sera utilisé pour exécuter le code côté serveur.

Or, comme nous souhaitons (si ce n’est pas le cas, vous devriez !) tracer l’activité du site, il est important de connaitre l’utilisateur à l’origine de l’action, histoire par exemple de savoir à qui tirer les oreilles en cas d’utilisation abusive de notre belle page.

System.Security.Principal.WindowsIdentity.GetCurrent().Name nous donne cette information en récupérant le Login de l’utilisateur.

Ensuite, nous allons activer le Timer :

this.Timer1.Enabled = true;



Désactiver le bouton (pour éviter le lancement par erreur du script alors qu’un script est déjà en exécution)

this.BtnExecuteScript.Enabled = false;


et vider la TextBox de sortie :

this.TxtResult.Text = "";



Ensuite nous initierions notre fameuse variable de Session que j’ai appelé « PowerTrace » avec le nom de la personne exécutant la commande

Session["PowerTrace"] = "Initiateur de la demande : " + strCurrentID + "\r\n";


Un peu d’explication s’impose sur ce choix. Toute la subtilité ici est de pouvoir transmettre à notre page asp.net ce que récupère le Code-Behind quand nous rafraichissons la page. Comme les 2 mondes sont isolés (chacun s’exécute dans son contexte : le page web côté client et le code-behind côté serveur), il nous faut trouver un moyen pour transmettre des informations de l’un à l’autre (ici en l’occurrence, passer la sortie de notre script à la TextBox).


Il existe plusieurs solutions plus ou moins élégantes (créer un fichier texte temporaire, rajouter la sortie dans l’URL…) mais la variable de session est dans notre contexte la meilleure des méthodes. Cela permet de mettre à disposition des informations lisible du côté de l’interface qui se conserve pendant l’ensemble de la session de l’utilisateur. Sinon, par défaut, au rafraîchissement toutes les informations récoltées côté code-behind disparaîtraient. Comme notre sortie de scripts ne prendra jamais une place démesurée en mémoire et qu’il s’agit d’une utilisation en intranet relativement restreinte, c’est ici un bon choix facile à mettre en place.

Le contenu de cette variable est donc exploitable pendant toute la durée de la session de l’utilisateur. Ceci étant fait, nous allons maintenant récupérer le contenu de la TextBox contenant le script :

string strContent = TxtPowerShellScript.Text;



Problème ici : nous sommes dans un contexte HTML, le contenu de cette TextBox est donc au format HTML. Ceci ne nous permet pas d’exécuter le code tels que, car des informations se glissent dans le texte (retour chariot notamment, qui se font avec la syntaxe \r\n). Le code est donc inutilisable par PowerShell tels quel.

Heureusement pour nous, il existe une méthode qui nous permet de décoder l’HTML pour obtenir du texte classique : HtmlDecode

C’est elle que j’utilise ici : je créé une instance d’un StringWriter (qui permet de créer une string), et j’utilise cette méthode pour inscrire le texte « décodé » dans cette string.

StringWriter writer = new StringWriter()
Server.HtmlDecode(strContent, writer);



Je fais appel ensuite à ma fonction d’exécution de code PowerShell avec le résultat :

this.executePowerShellCode(writer.ToString());


Passons donc à l’analyse de cette fonction « executePowerShellCode »

private void executePowerShellCode(string code)
{
    runspace.Open();
    pipe = runspace.CreatePipeline(code);
    pipe.Input.Close();

    // Call output_DataReady when data arrived in the pipe
    pipe.Output.DataReady += new EventHandler(Output_DataReady);
    // Call pipe_StateChanged 
    pipe.StateChanged += new EventHandler<PipelineStateEventArgs>(pipe_StateChanged);
    pipe.InvokeAsync();
}

Je n’est pas réinventé la roue, j’utilise la méthode la plus connue que vous trouverez dans pas mal d’exemple sur le net :

Je commence par ouvrir le runspace

runspace.Open();



Ensuite je créé un pipeline dans ce runspace avec le code retourné précédemment

pipe = runspace.CreatePipeline(code);



Je ferme le pipeline en écriture

pipe.Input.Close();



Ensuite nous créons un gestionnaire d’évènement, qui va tout simplement appeler la fonction « Output_DataReady » quand quelque chose se présente dans la sortie du pipeline

pipe.Output.DataReady += new EventHandler(Output_DataReady);

Nous créons ensuite un autre gestionnaire d’évènement, qui lui va appelé la fonction “pipe_StateChanged” quand le pipeline change d’état. En l’occurrence ici, nous allons tester si l’état du pipeline est « Terminé », qui signifie la fin du script et donc des actions à effectuer tels que la réactivation du bouton Executer comme nous le verrons plus loin.

Ensuite nous appelons la méthode « InvokeAsync() » qui nous permet d’exécuter le pipeline en asynchrone.

pipe.InvokeAsync();


Ok maintenant, notre code est en attente d’activité côté pipeline. Voyons a présent ce qu’il se passe quand des données arrivent dans la sortie du pipeline avec notre fonction Output_DataReady


void Output_DataReady(object sender, EventArgs e)
{
    PipelineReader<PSObject> reader = (PipelineReader<PSObject>)sender;
    String strPowershellTrace = reader.Read().ToString();
    Session["PowerTrace"] += strPowershellTrace + "\r\n";
}

Ici nous allons lire la sortie du pipeline que nous inscrivons dans l’objet reader :

PipelineReader<PSObject> reader = (PipelineReader<PSObject>)sender;



Ensuite, nous définissons une variable de type string qui récupère cette sortie sous forme de… string !

String strPowershellTrace = reader.Read().ToString();


Il ne nous reste plus qu’a rajouter cette variable à notre variable de session :

Session["PowerTrace"] += strPowershellTrace + "\r\n";



Notez que je rajoute “\r\n” derrière cette variable pour gérer le retour chariot. Vous voyez, c’est plutôt simple à comprendre. Pour finir sur le processus d’exécution de code PowerShell, voyons la dernière fonction qui gère le changement d’état du pipeline :

Ici, je pose une condition sur l’état du pipeline : si il a le statut « Completed », nous allons agir, sinon nous ne faisons rien.

if (pipe.PipelineStateInfo.State == PipelineState.Completed)



Alors, que fait-on quand le statut est Completed ? Premièrement nous allons fermer le runspace

Ensuite, tant que la Variable de Sessions n’est pas nulle et qu’elle contient quelque chose (nombre de caractère superieur à 0), nous n’y touchons pas. Ceci afin de laisser le temps au Timer d’inscrire son contenu dans la TextBox de Sortie. Nous verrons cela tout de suite après en analysant la fonction du Timer.

while ((Session["PowerTrace"] != null) && (Session["PowerTrace"].ToString().Length > 0))
{
}
runspace.Close();

Il ne nous reste plus ensuite qu’à supprimer cette variable de session dans l’attente d’un nouveau script :

Session.Remove("PowerTrace");



Voyons maintenant notre fameux Timer :

protected void Timer1_Tick(object sender, EventArgs e)
    {
        if (Session["PowerTrace"] == null)
        {
            this.BtnExecuteScript.Enabled = true;
            Timer1.Enabled = false;
            this.TxtResult.Text += "Fin du script";
        }
        else
        {
            String strPoshTrace = Session["PowerTrace"].ToString();
              this.TxtResult.Text += strPoshTrace;
            Session["PowerTrace"] = "";
        }
    }
Indépendamment des autres fonctions, celle-ci est appelé par notre Timer à intervalles réguliés (dans notre cas toute les 100 ms, on peut bien sûr élargir un peu ce temps)

Ici nous faisons la chose suivante :

Si la Variable de Session est Null (donc directement après sa suppression par la fonction pipe_StateChanged), nous allons :

· Réactiver le bouton
· Désactiver le Timer
· Inscrire « Fin du script » dans la TextBox de sortie

this.BtnExecuteScript.Enabled = true;
Timer1.Enabled = false;
this.TxtResult.Text += "Fin du script";

Sinon, on récupère le contenu de la variable de session dans une string

String strPoshTrace = Session["PowerTrace"].ToString();



On ajoute celle-ci à la TextBox de sortie

this.TxtResult.Text += strPoshTrace;



et on vide la variable de session de son contenu

Session["PowerTrace"] = "";



Notez que nous ne supprimons pas ici la variable, ce qui fait que notre condition dans la fonction pipe_StateChanged est valide :

while ((Session["PowerTrace"] != null) && (Session["PowerTrace"].ToString().Length > 0))

Voilà pour notre tour du propriétaire de ce premier projet. Nous verrons dans la troisième partie de ce tutorial comment piloter un script à partir d’un formulaire, et comment déployer ce site web sur un serveur IIS avec la sécurité adéquate.