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.

vendredi 16 mai 2008

PowerShell et ASP.NET Part 1

English Version Available here

Comme nous l'avons vu, PowerShell permet de créer des interfaces graphiques en passant par les winforms .NET. Ainsi, nous pouvons créer des interfaces pour piloter nos scripts. C'est un vrai plus, d'autant que des outils comme AdminScriptEditor rendent cela particulièrement simple.

Dans mes peregrinations, une question m'est revenue assez souvent :

comment pourrait-on intégrer facilement PowerShell avec une page web ?

A juste titre, utiliser un portail web plutôt que des winforms "clients lourds" apporte plusieurs avantages indéniables :

· Pas besoin d'installer PowerShell ou des Snapins sur l'ensemble des postes d'admins pour utiliser des scripts (je pense par exemple aux interfaces de provisioning Active Directory, un gestionnaire de machines virtuelles, etc... )

· ... Donc pouvoir centraliser et déléguer l'administration facilement

· Filtrer l'accès et l'utilisation de Powershell dans l'entreprise (tout s'exécute du serveur et non du poste)

· Traçabilité : d'un point unique, on peut savoir qui fait quoi, et potentiellement gérer le CTRL+Z salvateur

· Peut s'implémenter sur le portail d'entreprise (Sharepoint ou autre)

Tout ceci serait formidable, mais ne va pas sans quelques inconvenients, du moins au premier abord pour qui n'a jamais touché un site ASP.NET :

· Développer en asp.net et C# s'éloigne résolument du scripting.
· Les exemples de méthodes d'invocation de PowerShell via C# ne donne pas forcément envie à un profil infra de se lancer dans le bain.
· On peut alors plutôt s'orienter sur du developpement C# pour arriver au résultat......
· Or il faut avoir la chance d'avoir un developpeur .NET pur et dur sous la main.

C'est pourquoi je vous soumet ici un premier exemple d'interaction très simple entre une page ASP.NET et du script Powershell, qui prend le problème sous un angle plus "InfraPeople-Compliant". Pour rendre les choses plus digestes, nous allons séparer les choses :

· ASP.NET et C# pour gérer uniquement l'interface utilisateur
· Appel de code powershell pur et non de bloc dans du code C#
· Dialogue entre l'output de PowerShell et le site web pour suivre la progression des commandes.

Ceci permet de bien séparer les 2 aspects du portail, et de garder ainsi PowerShell comme moteur de traitement tels qu'on a l'habitude de l'utiliser en scripting classique.

Comme nous allons le voir au cours de ce tutorial, le code est plutôt simple et la page très légère : et pourtant avec ceci nous allons pouvoir effectuer des commandes complexes et riches grâce à PowerShell.

update : Voici une petite vidéo de l'exemple pour vous donner une idée



Je ne vais pas faire ici un cours sur ASP.NET, car d'une part je ne suis pas le mieux placé pour le faire, d'autre part comme vous allez le voir nous n'allons utiliser que les fondamentaux pour arriver à nos fins.

Reste quelques notions de bases indispensables pour pouvoir saisir la suite :

Quelques bases ASP.NET

Pour parler très schématiquement avec une bonne dose de simplification, on peut concevoir une site ASP.NET comme une page HTML qui pourrait faire exécuter du code .NET côté serveur, et non uniquement via le browser côté poste client.

Voici un exemple qui va être notre premier projet. Notez que nous pouvons (et allons!) utiliser la version gratuite de Visual Studio pour le web : Visual Studio Web developper Express Edition que je vous invite à télécharger. Cette version est tout à fait adaptée à nos besoins et nous permettra de prendre en charge l'ensemble du développement, ne nous privons pas. Vous aurez aussi besoin des Ajax Control Toolkit et library à télécharger ici .

Un site ASP.NET à minima est composé des éléments suivants :

Une page ASP.NET (par défaut ici : default.aspx) : c'est elle qui heberge le code de l'interface e la page (les boutons, cases et autres joyeusetés), au format html et ASP. On y trouve aussi souvent du javascript, mais ceci n'est pas important ni utilisé pour notre exemple.

Un fichier .cs correspondant à la page ASPX (ici : default.aspx.cs) : c'est ce fichier qui contient ce que l'on appelle le "code-behind". C'est le code côté serveur qui sera invoqué par l'interaction entre l'utilisateur et la page. Ce code est dans un langage .NET (C# ou VB.NET). Dans notre cas nous allons utiliser C# qui est proche de PowerShell dans sa structure.

web.config : fichier de configuration du site web. Pour faire simple, on y trouve la configuration du site asp, méthode d'authentification, invocation des DLL (dans notre cas celles qui permettent d'exécuter le code PowerShell), etc..

Bien sûr, l'outil de developpement nous facilite la tâche, et tout est pré-configuré quand vous créez un nouveau projet. Avant de rentrer concrètement sur la création de notre page ASP.NET, voici la technique utilisée pour faire appel à nos scripts d'un formulaire :

La stratégie d'invocation de PowerShell

Le schéma ci-dessous présente la stratégie utilisée pour cet exemple d'invocation de PowerShell par ASP.NET, que nous allons décrire en détail dans la seconde partie de ce tutorial :


Notre site comporte 2 textbox, une pour taper un script, l'autre pour récupérer le retour eventuel généré par ce script, et un bouton pour lancer l'exécution : le tour est joué. Comme vous le voyez, côté interface on est plutôt dans quelque chose de basique.

Le principe est assez simple, nous allons y revenir en détail dans la partie 2 de ce tutorial, mais voici les bases :

Quand on clique sur le bouton "Executer" voici ce qu'il se passe:

On active un Timer (1), je reviendrai sur ce point plus tard, mais disons pour le moment qu'il s'agit d'un mécanisme qui exécute du code en boucle à une fréquence donnée (d'où le nom de Timer d'ailleurs).

Dans le même temps, le code-behind invoque un runspace et un pipeline PowerShell en asynchrone, prend le contenu de la première Textbox et l'exécute dans le pipeline (2).

Toute les secondes, notre timer vérifie la sortie du pipeline (3) et inscrit ce qu'il trouve dans une variable spéciale dites "de session" (4) (on verra pourquoi dans la seconde partie).

On rafraichit la Textbox de sortie avec le contenu de la variable de session (5). Le timer repart jusqu'à la liberation du pipeline (6).

L'utilisation du Timer nous permet d'avoir une vue relativement temps réel de la sortie du script, du moins toute les secondes. Notez que le script n'est pas obligé d'avoir une sortie : comme tout script PowerShell, il peut se passer plein de choses sans rien voir dans la console, donc prenez garde a ce que vous codez ;).

J'ai gardé le code le plus simple possible pour vous permettre d'identifier les différentes fonctions facilement. Nous enrichirons plus tard notre interface une fois que nous aurons vu la composition du code dans la seconde partie de ce tutorial.

Notez toutefois que nous utilisons un peu d'AJAX pour gérer la seconde boite de dialogue : Ajax nous permet ici de rafraichir la boite seule sans rafraichir toute la page. Nous verrons dans la seconde partie qu'intégrer ce type de composant AJAX avec ASP.NET est très facile, des composants prêt à l'emploi sont fournis sans avoir à coder la moindre ligne de javascript ni d'XML.

Pour le moment vous pouvez déjà tester ce site en ouvrant tout simplement le projet en pièce jointe à cet article avec Web developper Express.

Tapez votre script dans la textbox du haut, le résultat s'affichera en bas.

Note : le script s'exécute côté serveur (ici si vous faites un test en local, c'est sur votre machine que le script s'exécute.) Notez que nous récupérons la sortie "objet" de PowerShell : c'est pour cela que j'utilise dans cet exemple le cmdlet "out-string" pour afficher en sortie ce qui apparaitrait dans la console.

vous pouvez bien sûr lancer n'importe quelle commande Powershell (attention donc aux fausses manips :) ). Le site est "light" pour vous permettre de bien saisir le code : point de contrôle d'erreurs ni fioritures côté interface.

Nous verrons dans la seconde partie comment est articulé le code, comment piloter un script avec un formulaire et les questions relatives à la sécurité.

Voici la source : PowerShellASP.zip , n'hésitez pas à poser vos questions en commentaire !

mardi 8 avril 2008

Mindmap PowerShell avec Silverlight

English version available here

Michael S. Scherotter propose sur son blog synergist une application permettant de convertir des mindmap créés avec MindManager en silverlight.

C'est amusant, facile à utiliser et très inspirant pour l'intégration de Silverlight et du mindmapping dans notre vie de développeurs infrastructure de tous les jours :)

Pour illustrer ceci, je vous ai mis ci-dessous un exemple de mindmap avec différentes ressources sur PowerShell. Blogger me limitant un peu sur la taille de la zone de texte, n'hésitez pas à utiliser le bouton "full screen" en haut à droite du contrôle silverlight. Les liens sont bien sûr utilisables.

lundi 7 avril 2008

PowerShell et SCCM Part 2

PowerShell et SCCM Part 2

English Version available here

Intégration de l'ordinateur dans une collection spécifique

Ok, notre ordinateur est inscrit dans SCCM, voyons maintenant comment l'inscrire dans une collection spécifique. Que nous dit le SDK ? l'exemple VBScript est le suivant :

' Add the computer to the all systems collection.
set collection = connection.Get("SMS_Collection.CollectionID='SMS00001'")
set collectionRule=connection.Get("SMS_CollectionRuleDirect").SpawnInstance_
collectionRule.ResourceClassName="SMS_R_System"
collectionRule.ResourceID= outParams.ResourceID
collection.AddMembershipRule collectionRule

Qu'on peut organiser comme suit pour plus de facilité de compréhension :

set collectionRule=connection.Get("SMS_CollectionRuleDirect").SpawnInstance_ collectionRule.ResourceClassName="SMS_R_System" collectionRule.ResourceID= outParams.ResourceID

set collection = connection.Get("SMS_Collection.CollectionID='SMS00001'")
collection.AddMembershipRule collectionRule

L'exemple nous parle de la collection "All Systems", mais comme nous l'avons vu, le fait de déclarer un ordinateur dans SCCM l'inscrit par défaut dans cette collection. Par contre, cet exemple nous donne tout les éléments utiles : il nous montre qu'il faut créer un objet de type "SMS_CollectionRuleDirect", le paramétrer, puis invoquer la méthode AddMemberShiprule avec cet objet en paramètre.

Allons y donc tranquillement, étape par étape

Créer un objet issue de la classe SMS_CollectionRuleDirect

Pour instancier une classe WMI en PowerShell, on peut utiliser la syntaxe suivante :

$objColRuledirect = [WmiClass]"\\$Computer\ROOT\SMS\site_001:SMS_CollectionRuleDirect"

On voit qu'en passant par les psbase.properties les 3 propriétés de l'objet :

Nous avons vu dans l'exemple du SDK que deux propriétés sont à renseigner :

  • ResourceClassName avec la valeur "SMS_R_System"
  • ResourceID avec la propriété ResourceID de l'objet ordinateur créé précédemment

Soit en PowerShell :

$objColRuleDirect.psbase.properties["ResourceClassName"].value = "SMS_R_System"
$objColRuleDirect.psbase.properties["ResourceID"].value = $objCMComputer.resourceID

On voit ci dessous les propriétés de l'objet ordinateur créé comme dans la Part 1 et notre objet CollectionRuleDirect proprement configuré :

Il ne nous reste plus à présent qu'à utiliser cet objet avec la classe SMS_Collection

Utiliser la méthode AddMemberShipRule de la classe SMS_Collection

Qui dit méthode WMI, dit petit coup d'oeil dans WmiExplorer pour avoir notre exemple pré-maché (qui a dit feignant ?) :

Ce qu'il nous faut en paramètre, c'est l'ID de la collection cible. Alors ici 2 options : soit utiliser en dur l'ID de la collection que l'on trouve dans la console SCCM (pas sport), ou déterminer l'ID de la collection par son nom (sport)

Soyons sportif, allons à la pêche à l'ID avec Powershell !

C'est ici très simple, créons "classiquement" une connexion à la classe SMS_Collection, et filtrons sur le nom de notre collection (ici dans mon exemple "PowerShell Collection")

$Collection = gwmi -computer $computer -namespace "root\sms\site_001" -class "SMS_Collection" $PoshCollec = $collectionwhere{$_.Name -eq "PowerShell Collection"}

Il ne nous reste plus qu'à adapter le code de WmiExplorer avec comme CollectionID l'ID issu de cet objet, et comme CollectionRule, l'objet que l'on a créé à l'étape précédente :

$Class = "SMS_Collection"
$Method = "AddMembershipRule"
$CollectionID = $PoshCollec.CollectionID
$filter="CollectionID = '$CollectionID'"
$MC = Get-WmiObject $class -computer $Computer -Namespace "ROOT\SMS\site_001" -filter $filter
$InParams = $mc.psbase.GetMethodParameters($Method)
$InParams.collectionRule = $objColRuleDirect
$inparams.PSBase.properties select name,Value Format-Table
$R = $mc.PSBase.InvokeMethod($Method, $inParams, $Null)

Voilà le travail : notre ordinateur est à présent inscrit dans la collection "PowerShell Collection".

Avec un peu de recherche, et la bonne compréhension des techniques expliquées dans ce message, vous devriez pouvoir créer tout type de script PowerShell autour de SCCM. Notez aussi que la plupart des codes sont aussi utilisable dans SMS ;)

Ci-dessous un exemple complet des différents extraits :

**************************************************************************

## Nom : Script d'import de machine dans SCCM
## Auteur : Antoine Habert
devinfra@gmail.com

### Variables
$strTargetMac = "0A:0B:0C:0D:0E:0F"
$strTargetComputerAccount = "NomDeMachine"
$computer = "."
$strTargetCollection = "La collection cible"

# Create computer in SCCM
$Class = "SMS_Site"
$Method = "ImportMachineEntry"
$MC = [WmiClass]"\\$Computer\ROOT\SMS\site_001:$Class"
$InParams = $mc.psbase.GetMethodParameters($Method)
$InParams.MACAddress = $strTargetMac
$InParams.NetbiosName = $strTargetComputerAccount
$InParams.OverwriteExistingRecord = $true
$inparams.PSBase.properties select name,Value Format-Table
$objCMComputer = $mc.PSBase.InvokeMethod($Method, $inParams, $Null)

# Create Collection Rule Direct
$objColRuledirect = [WmiClass]"\\$Computer\ROOT\SMS\site_001:SMS_CollectionRuleDirect"
$objColRuleDirect.psbase.properties["ResourceClassName"].value = "SMS_R_System"
$objColRuleDirect.psbase.properties["ResourceID"].value = $objCMComputer.resourceID


# Target Collection connection
$Collection = gwmi -computer $computer -namespace "root\sms\site_001" -class "SMS_Collection"
$PoshCollec = $collectionwhere{$_.Name -eq $strTargetCollection}

# Add Computer to Target Collection
$Class = "SMS_Collection"
$Method = "AddMembershipRule"
$CollectionID = $PoshCollec.CollectionID
$filter="CollectionID = '$CollectionID'"
$MC = Get-WmiObject $class -computer $Computer -Namespace "ROOT\SMS\site_001" -filter $filter
$InParams = $mc.psbase.GetMethodParameters($Method)
$InParams.collectionRule = $objColRuleDirect

$inparams.PSBase.properties select name,Value Format-Table

$R = $mc.PSBase.InvokeMethod($Method, $inParams, $Null)

**************************************************************************

vendredi 4 avril 2008

Webcast PowerShell en ligne

Les webcasts des techdays sont en ligne !

Hormis le fameux et implacable "demo effect" venu titiller la douce quiétude du déroulement de la session, vous y trouverez normalement quelques infos interessantes sur PowerShell et les solutions infrastructure Microsoft Retrouvez notre présentation ici

Une "Charentaise Edition", mitonnée à la maison (enfin disons sur mon lieu de travail) est disponible sur le site de Neos-SDI, plus complète.

Je vais poster différents tutoriaux liés à cette session (le premier est déjà disponible sous ce message)

N'hésitez pas à me laisser des commentaires ou des demandes d'infos ici.

Sur ce, bon film !

PowerShell et SCCM Part 1

PowerShell et SCCM (Part 1)

ENGLISH version available here

Introduction

Contrairement à SCVMM ou Exchange 2007, SCCM ne propose pas nativement de cmdlets... Par contre, joie, SCCM expose l’ensemble de ses fonctionnalités via WMI. Nous allons voir ensemble comment utiliser les classes, méthodes et propriétés de SCCM avec PowerShell (Ceci est aussi valable pour SMS).

Avant de commencer

Pour vous faciliter la vie, 2 téléchargements sont nécessaires :
  • Premièrement, téléchargez le SDK de SCCM fraichement
    disponible ici

  • Prenez aussi le WMI Explorer de MOW qui va vous faciliter le
    code pour SCCM comme nous allons le voir plus loin
Premier script

Il y a quelques subtilités à connaitre pour pouvoir exploiter les méthodes WMI exposées par SCCM.

Commençons par découvrir les namespaces, Lancez wmiexplorer sur votre serveur SCCM
Un namespace "ROOT\SMS" est présent, au deuxième niveau, vous verrez «ROOT\SMS\inv_schema » que nous allons ignorer pour le moment, et en dessous un namespace normé "ROOT\SMS\site_nomdusite"

Double-cliquez sur l’instance d’un site pour faire apparaitre les classes :

Les 570 (!) classes exposées vont apparaitre en bas à gauche. Oui ça fait du monde, mais rassurez vous nous allons pouvoir faire le tri facilement en fonction de notre besoin.

Pour notre premier exemple, nous allons créer un script pour ajouter un ordinateur à une collection. Dans un premier temps, jetons un œil au SDK pour trouver la classe qui nous intéresse.


Ici, on voit à droite que l’ajout d’un ordinateur passe par la méthode « ImportMachineEntry » de la classe SMS_Site. Le SDK montre généralement des exemples de code VB/C# ou vbs, mais la syntaxe PowerShell diffère comme nous allons le voir plus loin. L’important ici pour nous est d’identifier la classe et la méthode utilisée pour accomplir la tâche.

Dans l’exemple vbscript du SDK, on constate qu’il faut passer par un connection.get pour instancier la classe, puis faire un spawnInstance de la méthode, charger les paramètres requis (macaddress, netbiosname…) puis utiliser ExecMethod..

' Connect
Set siteClass = connection.Get("SMS_Site")
Set inParams = siteClass.Methods_("ImportMachineEntry"). _
inParameters.SpawnInstance_()

' Add the input parameters.
inParams.Properties_.Item("MACAddress") = macAddress
inParams.Properties_.Item("NetbiosName") = netBiosName
inParams.Properties_.Item("OverwriteExistingRecord") = False
inParams.Properties_.Item("SMBIOSGUID") = smBiosGuid

' Add the computer
Set outParams = connection.ExecMethod("SMS_Site", "ImportMachineEntry", inParams)

La question qui nous brûle le clavier est donc :

comment faire la même chose en PowerShell ?
Utilisons wmiExplorer de MoW pour trouver la réponse :

On double clique sur la classe SMS_Site pour inventorier les propriétés et méthodes de la classe. Vous constaterez qu’à droite, il existe effectivement une méthode appelée « ImportMachineEntry »

Double Cliquez sur cette méthode. L’intérêt du WmiExplorer de MoW est qu’il permet d’obtenir des exemples d’utilisation des méthodes WMI :

Comble du raffinement, il donne un exemple de script et énumère les différents paramètres à renseigner avec leur type (string, booléen,etc..)... Concrètement, il ne nous reste plus qu’à renseigner cet exemple avec les paramètres nécessaires :
  • MacAddress (addresse MAC de la machine à importer)
  • NetbiosName (Nom de la machine dans l’inventaire SCCM), etc…
Détaillons un peu ce script pour comprendre le fonctionnement de l’invocation de méthode de cette classe :

Quand on créé notre connexion à la classe SMS_Site et qu’on liste comme ci-dessus l’objet $mc, on voit qu’il n’affiche que « SMS_Site »

En faisant un Get_Member, on constate qu’il n’y a pas de méthode « ImportMachineEntry » d’exposée. Cela vient du formatage des objets propres à PowerShell. Pour avoir accès aux propriétés et méthodes de cette classe, il faut passer par la méthode psbase de powershell pour avoir un accès « RAW » à l’objet (Plus d’info sur psbase ici) :



On voit ici que $mc.psbase nous propose effectivement "Methods" et "Properties". Listons maintenant les méthodes :



On retrouve effectivement notre méthode ImportMachineEntry. Dans l’exemple vbscript du SDK, on voit qu’il faut faire un spawnInstance pour définir les paramètres de la méthode :

Set inParams = siteClass.Methods_("ImportMachineEntry"). _
inParameters.SpawnInstance_()

inParams.Properties_.Item("MACAddress") = macAddress
inParams.Properties_.Item("NetbiosName") = netBiosName
inParams.Properties_.Item("OverwriteExistingRecord") = False
inParams.Properties_.Item("SMBIOSGUID") = smBiosGuid

Pour faire la meme chose en PowerShell, regardons l’exemple du script généré par l’outil de MoW :

$InParams = $mc.psbase.GetMethodParameters(“ImportMachineEntry”)

$InParams.MACAddress = [string]
$InParams.NetbiosName = [string]
$InParams.OverwriteExistingRecord = [boolean]
$InParams.SMBIOSGUID = [string]

Comme vous pouvez le constater, le code est plutôt simple en PowerShell : ici le GetMethodParameters équivaut au inParameters.SpawnInstance_() du vbscript.

Détaillons un peu ce $InParams :
On voit effectivement nos 4 propriétés MacAddress, NetbiosName, OverwriteExistingRecord, SMBIOSGUID qui sont vides. Notez au passage que le type est bien exposé dans la colonne définition (OverwriteExistingRecord est de type System.Boolean, string pour les autres), et qu’on peut lire et écrire (get;set)

Bien, renseignons les 3 propriétés voulues (contrairement à ce qui est indiqué, la propriétéMSBIOSGUID n’est pas obligatoire pour ajouter une machine : c’est soit la MacAddress, soit le GUID… Seule l’experience permet de le savoir :) )


En affichant $InParams après avoir renseigné les propriétés, on voit bien que l’objet est chargé.

Reste à exécuter la méthode. L’exemple Vbscript donnait :

Set outParams = connection.ExecMethod("SMS_Site", "ImportMachineEntry", inParams)

En PowerShell, l’execution se gère comme suit (toujours en suivant simplement l’exemple généré par l’outil de MoW)

$mc.PSBase.InvokeMethod($Method, $inParams, $Null)

Voila notre machine importée dans SCCM, pour l’instant uniquement dans All Systems.
Image Hosted by ImageShack.us
Reste la troisième étape indiquée par le SDK : Ajouter cette machine à une autre collection.

Nous verrons cela dans la seconde partie de ce post.

Rejouissez-vous ! Vous savez à présent où trouver les différentes tâches liées à SCCM et les convertir en PowerShell.

lundi 17 mars 2008

SDK SCCM V4 disponible

la v4 (et première release hors Beta) du SDK de SCCM est dispo en téléchargement, juste sorti du four à cette adresse :

http://www.microsoft.com/downloads/details.aspx?FamilyId=064A995F-EF13-4200-81AD-E3AF6218EDCC&displaylang=en

j'upload prochainement avec la sortie du webcast de notre session techdays (normalement fin de semaine ou semaine prochaine) des exemples pour créer des cmdlets pour SCCM basés sur ce SDK.

à dans quelques jours !