Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Auteur : Sergei Antonov
Introduction
Grâce à PowerShell, les administrateurs IIS bénéficient d’un nouvel outil à utiliser. L’article suivant se concentre sur les tâches d’administration pour IIS 7.0 et versions ultérieures. Toutefois, PowerShell peut être utilisé pour les serveurs IIS 6.0 existants.
Cet article se concentre sur l’administration du serveur IIS distant à l’aide de PowerShell sur l’ordinateur client. Au moment de la rédaction de cet article, cela n’est possible que si vous utilisez le fournisseur WMI fourni par l’équipe IIS avec Vista et Windows Server® 2008. Dans ce cas, votre ordinateur client n’a pas besoin de contenu lié à IIS ; WMI fournit la connexion à la configuration réelle disponible sur le serveur distant.
Remarque
Vous pouvez également utiliser Microsoft.Web.Administration dans PowerShell pour exécuter des fonctions d’administration. Toutefois, cet article ne se concentre pas sur cette technique.
L’équipe PowerShell a créé une commande spéciale à utiliser pour accéder aux objets WMI, à savoir get-wmiobject. Normalement, elle retourne un objet, créé dans PowerShell, qui expose les propriétés et méthodes WMI, au lieu de l’objet de code managé habituel retourné pour les classes standard.
Cet objet synthétique expose les métadonnées définies dans l’espace de noms WMI, et non les métadonnées de System.ManagementBaseObject, qui sont utilisées comme base. L’utilisateur dispose ainsi de la vue d’espace de noms qui a été exposée par le fournisseur WMI et qui reflète le modèle d’administration de l’entité configurée.
Malheureusement, cette commande ne fonctionne pas avec les espaces de noms IIS. Pour PowerShell version 1.0, get-wmiobject prend uniquement en charge le niveau d’authentification par défaut pour la connexion DCOM distante. Cela ne suffit pas pour IIS 6.0 ou IIS 7.0 et versions ultérieures. Lorsque les utilisateurs configurent IIS, il peut être nécessaire d’envoyer des mots de passe et d’autres données sensibles via la connexion réseau pour les modifier et les stocker dans la configuration. Pour prendre cette fonction en charge, les fournisseurs IIS nécessitent le niveau d’authentification « Confidentialité du paquet ». Il n’existe aucun moyen de fournir cette exigence à la cmdlet get-wmiobject.
Avec cette limitation, nous avons deux possibilités :
- Utiliser PowerShell comme interface de scripts générique pour l’espace de noms System.Management. Utilisez également PowerShell pour effectuer les opérations suivantes : écrire du code de script qui configure la connexion à l’espace de noms WMI distant, récupérer et enregistrer des données d’administration à l’aide d’objets System.Management, accessibles via PowerShell. Cela est similaire à la programmation C#, mais en langage PowerShell. En règle générale, cela fonctionne bien. Toutefois, dans le cas de WMI, PowerShell applique un adaptateur spécial qui convertit automatiquement tous les objets System.Management en objets PowerShell synthétiques qui exposent l’espace de noms WMI en tant qu’API principale. Pour cette raison, nous devons surmonter les modifications de l’adaptateur et écrire du code supplémentaire pour accéder à la sous-structure System.Management « native », ce qui transforme rapidement cette programmation en un exercice inutilement compliqué.
Par conséquent, nous explorons l’autre possibilité :
- Écrire des cmdlets PowerShell en C# et accéder aux fonctionnalités requises dans le code C#. Dans ce cas, nous choisissons toutes les API appropriées pour les entités configurées. Nous utilisons WMI pour accéder au serveur. Par rapport au même code dans PowerShell, C# est beaucoup plus efficace, car il n’est pas nécessaire de l’analyser et de l’interpréter à chaque fois.
Applet de commande PowerShell
Pour commencer à écrire des cmdlets, vous avez besoin d’un ordinateur client sur lequel est installé PowerShell. Vous devez également installer le kit de développement logiciel (SDK) PowerShell ou simplement copier des DLL de référence dans le dossier de travail à l’aide de l’astuce publiée par Jeffrey Snover dans le blog de l’équipe PowerShell. Assurez-vous que vous disposez de la connexion DCOM sur votre serveur. Le moyen le plus simple de le vérifier est de démarrer l’utilitaire wbemtest, disponible sur chaque plateforme Windows, et d’essayer la connexion.
- Démarrez wbemtest.
- Cliquez sur Connecter.
- Entrez les paramètres de connexion :
- Remplacez « root\default » par \<ordinateur>\root\webadministration, où « <ordinateur> » doit correspondre au nom de votre serveur.
- Entrez les informations d’identification du compte disposant de droits d’administrateur sur le serveur.
- Sélectionnez « Confidentialité du paquet » dans le groupe Niveau d’authentification.
- Cliquez sur Connecter. WMI sur votre ordinateur client se connecte au service WMI sur votre serveur. S’il n’est pas accessible, une boîte de dialogue de message d’erreur s’affiche.
- Effectuez une action simple qui engage le fournisseur WMI sur la zone de serveur pour confirmer qu’il fonctionne. Effectuez une énumération de sites :
- Cliquez sur le bouton d’énumération d’instances et entrez le nom de classe « site ».
- Si cela fonctionne, la boîte de dialogue résultante affiche la liste de tous les sites disponibles sur votre serveur.
La cmdlet PowerShell est simplement un assembly de code managé implémenté suivant des règles formelles, qui sont documentées dans le kit de développement logiciel (SDK) PowerShell. Consultez-les en ligne.
Avant d’écrire du code, il est utile d’établir un plan.
Commencez par implémenter une cmdlet qui énumère tous les sites IIS sur le serveur distant. Cette cmdlet retourne un tableau d’objets de site, qui représentent l’élément de configuration IIS avec des propriétés, définies pour le site. Nous allons ajouter des propriétés supplémentaires qui sont utiles dans cet objet.
La cmdlet doit ressembler à ce qui suit :
get-iissite –computer somecomputer –name somesite –credential $(get-credential)
Si nous ne transmettons pas les informations d’identification à la cmdlet par programme, PowerShell affiche une boîte de dialogue demandant le nom d’utilisateur et le mot de passe du serveur distant.
Pour obtenir l’objet de site à partir de l’ordinateur distant, nous devons fournir les paramètres suivants à partir de notre cmdlet :
public string Computer;
public string Name;
public PSCredential Credential;
Tous ces paramètres sont des propriétés publiques dans la classe de cmdlet, décorées par l’attribut Parameter.
Implémentez la première cmdlet. Étant donné que get-iissite n’est pas notre dernière commande, il est préférable d’effectuer deux opérations : séparer le code responsable de la connexion au serveur dans la classe parente RemotingCommand et hériter de la cmdlet de cette classe.
using System;
using System.Net;
using System.Management;
using System.Management.Automation;
using System.ComponentModel;
using System.Security;
namespace Microsoft.Samples.PowerShell.IISCommands
{
public class RemotingCommand : PSCmdlet
{
private string computer = Environment.MachineName;
[Parameter(
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string[] Computer
{
get { return computer; }
set { computer = value; }
}
private PSCredential credential = null;
[Parameter(
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
[CredentialAttribute]
public PSCredential Credential
{
get { return credential; }
set { credential = value; }
}
protected ManagementScope GetScope(string computerName)
{
ConnectionOptions connection = new ConnectionOptions();
connection.Username = Credential.UserName;
connection.Password = Credential.GetNetworkCredential().Password;
connection.Impersonation = ImpersonationLevel.Impersonate;
connection.Authentication = AuthenticationLevel.PacketPrivacy;
ManagementScope scope = new ManagementScope(
"\\\\" + computerName + "\\root\\webadministration", connection);
return scope;
}
protected override void EndProcessing()
{
if (null == credential)
{
// Check variable first
object varCred = GetVariableValue("IISCredential");
if (varCred != null && varCred.GetType() == typeof(PSObject))
{
credential = ((PSObject)varCred).BaseObject as PSCredential;
}
if (null == credential)
{
// use credential of current user or process
SecureString ss = new SecureString();
foreach (char c in CredentialCache.DefaultNetworkCredentials.Password.ToCharArray())
{
ss.AppendChar(c);
}
credential = new PSCredential(
CredentialCache.DefaultNetworkCredentials.UserName, ss);
}
}
}
protected ManagementClass CreateClassObject(
string computerName,
string classPath )
{
return new ManagementClass(
GetScope(computerName),
new ManagementPath(classPath),
new ObjectGetOptions()
);
}
}
La classe RemotingCommand inclut des paramètres et des méthodes requis pour la connexion.
- GetScope() retourne l’objet System.Management qui contient toutes les informations requises pour la connexion à l’espace de noms distant. Examinez la propriété connection.Authentication. Elle est initialisée sur AuthenticationLevel.PacketPrivacy. Il s’agit d’une condition obligatoire. Sinon, WMI refuse la connexion.
- La méthode CreateClassObject() est une méthode utilitaire qui utilise l’étendue de connexion pour créer une classe spécifiée basée sur l’espace de noms WMI distant.
- La méthode EndProcessing() est la méthode standard que chaque classe de cmdlet doit implémenter. Elle est appelée à partir de PowerShell lors du traitement de la cmdlet. L’implémentation de EndProcessing() tente de remplir la propriété credential, si elle est vide. Lors de la première tentative, elle obtient les informations d’identification de la variable externe IISCredential (uniquement pour des raisons pratiques).
Dans une session PowerShell, l’utilisateur peuvent placer le nom d’utilisateur et le mot de passe dans cette variable et l’utiliser plusieurs fois dans plusieurs commandes. Si cette variable n’est pas définie ou contient un type d’objet inapproprié, le code obtient les informations d’identification de l’utilisateur ou du processus actuel. Cela fonctionne lorsque l’utilisateur exécute cette commande localement sur le serveur, à l’aide du compte d’administrateur. Dans ce cas, nous n’avons pas besoin d’entrer d’informations d’identification. Pour chaque boucle de ce code, il existe une astuce connue pour convertir le mot de passe de chaîne en SecureString.
À présent, nous implémentons get-iissite.
[Cmdlet(VerbsCommon.Get, "IISSite")]
public class GetSiteCommand : RemotingCommand
{
private string name = null;
[Parameter(
Position = 0,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
public string Name
{
get { return name; }
set { name = value; }
}
protected override void EndProcessing()
{
base.EndProcessing();
ManagementObjectCollection sites = CreateClassObject(computerName, "Site").GetInstances();
foreach (ManagementObject site in sites)
{
string siteName = site.GetPropertyValue("Name") as string;
if (Name != null)
{
if (siteName.Equals(Name, StringComparison.InvariantCultureIgnoreCase))
{
WriteObject(siteName);
break;
}
}
else
{
WriteObject(siteName);
}
}
}
} //GetSiteCommand
//
// [RunInstaller(true)]
// public class IISDemoCmdSnapIn : PSSnapIn {…}
//
} // Microsoft.Samples.PowerShell.IISCommands
La commande retourne d’abord uniquement les noms de sites. Si l’utilisateur souhaite un site spécifique, il doit fournir le nom de ce site à la commande ; sinon, tous les sites sur cet ordinateur particulier sont retournés.
Pour terminer la commande, vous devez ajouter l’implémentation de la classe, héritée de PSSnapin. Cette classe est utilisée pour inscrire nos commandes. Elle n’a rien de spécifique à IIS. Consultez le code complet dans le fichier source IISDemoCmd.cs.
Générez à présent la cmdlet et examinez son fonctionnement. Pour ce faire, vous pouvez utiliser Visual Studio, mais il est assez facile de la générer à partir de la ligne de commande. Supposons que vous avez placé des DLL de référence PowerShell dans le dossier c:\sdk
. La ligne de commande suivante génère la cmdlet dans IISDemoCmd.dll et la place dans le même dossier, où se trouve le fichier source.
%windir%\Microsoft.NET\Framework\v2.0.50727\csc /t:library /r:c:\sdk\system.management.automation.dll IISDemoCmd.cs
Vous devez à présent inscrire la commande et l’ajouter à PowerShell. Cette procédure est décrite dans la guide de référence de programmation PowerShell. Démarrez PowerShell et exécutez les commandes suivantes à partir du dossier où vous avez créé la DLL de cmdlet.
>set-alias installutil $env:windir\Microsoft.NET\Framework\v2.0.50727\installutil
>installutil iisdemocmd.dll
>add-pssnapin IISDemoCmdSnapIn
Cette procédure ajoute la cmdlet à l’instance en cours d’exécution de PowerShell. Enregistrez ces lignes de commande dans un fichier de script. Vous les utiliserez à nouveau au fur et à mesure que vous travaillerez sur les cmdlets. Ce script se trouve dans le fichier demo_install.ps1.
Vérifiez si la commande est disponible :
>get-command Get-IISSite
CommandType Name Definition
----------- ---- ----------
Cmdlet Get-IISSite Get-IISSite [[-Name] <String...
Essayez-la à présent. Supposons que vous vous connectez à l’ordinateur test_server à l’aide du compte d’administrateur local.
>Get-IISSite -computer test_server -credential $(get-credential administrator)
Default Web Site
Foo
Bar
Cette ligne de commande reçoit l’objet credential de la cmdlet get-credential, qui interagit avec l’utilisateur pour obtenir le mot de passe. Il est également possible de produire les informations d’identification par programme, mais vous devez taper le mot de passe dans le script, ce qui n’est pas du tout sécurisé.
>$global:iiscredential = new-object System.Management.Automation.PsCredential "Administrator",$(convertto-securestring "password" -asplaintext -force)
Cette commande stocke les informations d’identification dans la variable globale $iiscredential, et la cmdlet l’utilise automatiquement. Toutefois, en situation réelle, il est préférable de stocker les informations d’identification dans une variable à l’aide de la commande get-credential : $global :iiscredential = get-credential Administrator.
Réexécutez à présent cette commande.
>Get-IISSite -computer test_server
Default Web Site
Foo
Bar
Toute l’infrastructure est en place. Revenez à présent à la commande pour ajouter le reste des données à partir du site.
Ajout des données de configuration au site
Nous devons convertir l’objet de ManagementBaseObject en PSObject et le renvoyer à PowerShell. PSObject est un conteneur à structure libre qui peut être rempli avec différents types de données. Nous allons utiliser le type PSNoteProperty. Conservez le code de cmdlet tel quel et ajoutez une nouvelle classe chargée de la conversion.
class ObjectConverter
{
public static PSObject ToPSObject(
ManagementBaseObject source
)
{
PSObject obj = new PSObject();
foreach (PropertyData pd in source.Properties)
{
if (pd.Value.GetType() == typeof(System.Management.ManagementBaseObject))
{
obj.Properties.Add(new PSNoteProperty(
pd.Name, ObjectConverter.ToPSObject(pd.Value as ManagementBaseObject)
));
}
else if (pd.Value.GetType() == typeof(ManagementBaseObject[]))
{
ManagementBaseObject[] ar = pd.Value as ManagementBaseObject[];
PSObject[] psar = new PSObject[ar.Length];
for (int i = 0; i < ar.Length; ++i)
{
psar[i] = ObjectConverter.ToPSObject(ar[i]);
}
obj.Properties.Add(new PSNoteProperty(pd.Name, psar));
}
else
{
obj.Properties.Add(new PSNoteProperty(pd.Name, pd.Value));
}
}
return obj;
}
}
Ce code utilise des propriétés complexes et ajoute des propriétés simples telles que PSNoteProperty au conteneur PSObject résultant. Nous ajoutons également une méthode dédiée qui traitera la conversion en cmdlet de sortie. Cette méthode convertit toutes les données WMI et ajoute deux propriétés supplémentaires : le nom de l’ordinateur et les informations d’identification utilisées pour obtenir la connexion à cet ordinateur. Cela permet de distinguer chaque site des autres objets de la session PowerShell.
private PSObject ConstructPSSite(
string computerName,
ManagementObject site)
{
PSObject pssite = ObjectConverter.ToPSObject(site);
pssite.Properties.Add(new PSNoteProperty("Computer", computerName));
pssite.Properties.Add(new PSNoteProperty("Credential", Credential));
return pssite;
}
Remplacez le nom du site que nous avons retourné à PowerShell par un objet entier. La méthode EndProcessing() dans la cmdlet ressemble à présent à ceci :
protected override void EndProcessing()
{
base.EndProcessing();
ManagementObjectCollection sites = CreateClassObject(Computer, "Site").GetInstances();
foreach (ManagementObject site in sites)
{
string siteName = site.GetPropertyValue("Name") as string;
if (Name != null)
{
if (siteName.Equals(Name, StringComparison.InvariantCultureIgnoreCase))
{
WriteObject(ConstructPSSite(Computer, site));
break;
}
}
else
{
WriteObject(ConstructPSSite(Computer, site));
}
}
}
Lorsque nous répétons la génération et l’inscription, puis réexécutons la commande, nous pouvons consulter plus de données sur le site :
> get-iissite -computer test-server "default web site"
ApplicationDefaults : @{ApplicationPool=; EnabledProtocols=http; Path=}
Bindings : {@{BindingInformation=*:80:; Protocol=http}}
Id : 1
Limits : @{ConnectionTimeout=00000000000200.000000:000; Max
Bandwidth=4294967295; MaxConnections=4294967295}
LogFile : @{CustomLogPluginClsid=; Directory=%SystemDrive%\i
netpub\logs\LogFiles; Enabled=True; LocalTimeRollo
ver=False; LogExtFileFlags=2199503; LogFormat=2; P
eriod=1; TruncateSize=20971520}
Name : Default Web Site
ServerAutoStart : True
TraceFailedRequestsLogging : @{Directory=%SystemDrive%\inetpub\logs\FailedReqLo
gFiles; Enabled=False; MaxLogFiles=50}
VirtualDirectoryDefaults : @{AllowSubDirConfig=True; LogonMethod=3; Password=
; Path=; PhysicalPath=; UserName=}
Computer : test-server
Credential : System.Management.Automation.PSCredential
Si vous comparez cela au schéma WMI du site, vous constatez que toutes les données sont désormais disponibles. De plus, nous avons des propriétés supplémentaires que nous avons ajoutées dans la cmdlet. Toutes les propriétés sont accessibles à partir de PowerShell via la notation « point ».
> $sites = get-iissite -computer test-server
>$sites[0]
ApplicationDefaults : @{ApplicationPool=; EnabledProtocols=http; Path=}
Bindings : {@{BindingInformation=*:80:; Protocol=http}}
Id : 1
Limits : @{ConnectionTimeout=00000000000200.000000:000; Max
Bandwidth=4294967295; MaxConnections=4294967295}
LogFile : @{CustomLogPluginClsid=; Directory=%SystemDrive%\i
netpub\logs\LogFiles; Enabled=True; LocalTimeRollo
ver=False; LogExtFileFlags=2199503; LogFormat=2; P
eriod=1; TruncateSize=20971520}
Name : Default Web Site
ServerAutoStart : True
TraceFailedRequestsLogging : @{Directory=%SystemDrive%\inetpub\logs\FailedReqLo
gFiles; Enabled=False; MaxLogFiles=50}
VirtualDirectoryDefaults : @{AllowSubDirConfig=True; LogonMethod=3; Password=
; Path=; PhysicalPath=; UserName=}
Computer : test-server
Credential : System.Management.Automation.PSCredential
>$sites[0].Limits
ConnectionTimeout MaxBandwidth MaxConnections
----------------- ------------ --------------
00000000000200.000000:000 4294967295 4294967295
> $sites[0].Limits.MaxBandwidth
4294967295
Nous avons bien avancé, mais pas encore assez. Le site WMI a également des méthodes. Essayez donc de les ajouter aussi. Cette procédure est simple à réaliser dans PowerShell : vous nommez la méthode et indiquez à PowerShell où se trouve le code. Nous allons ajouter des méthodes du type PSCodeMethod. Pour conserver le code des méthodes, nous ajoutons la classe SiteMethods.
public class SiteMethods
{
static public void Start(PSObject site)
{
InvokeMethod(site, "Start");
}
static public void Stop(PSObject site)
{
InvokeMethod(site, "Stop");
}
static public string GetStatus(PSObject site)
{
uint status = (uint)InvokeMethod(site, "GetState");
string statusName =
status == 0 ? "Starting" :
status == 1 ? "Started" :
status == 2 ? "Stopping" :
status == 3 ? "Stopped" : "Unknown";
return statusName;
}
static private object InvokeMethod(PSObject site, string methodName)
{
string computerName = site.Properties["Computer"].Value as string;
string siteName = site.Properties["Name"].Value as string;
PSCredential credential = site.Properties["Credential"].Value as PSCredential;
ConnectionOptions connection = new ConnectionOptions();
connection.Username = credential.UserName;
connection.Password = credential.GetNetworkCredential().Password;
connection.Impersonation = ImpersonationLevel.Impersonate;
connection.Authentication = AuthenticationLevel.PacketPrivacy;
ManagementScope scope = new ManagementScope(
"\\\\" + computerName + "\\root\\webadministration", connection);
string sitePath = "Site.Name=\"" + siteName + "\"";
ManagementObject wmiSite = new ManagementObject(
scope, new ManagementPath(sitePath), new ObjectGetOptions());
return wmiSite.InvokeMethod(methodName, new object[] { });
}
}
Comme vous le constatez, ce code crée un objet WMI pour le site et appelle des méthodes WMI sur cet objet. Ce code utilise deux propriétés supplémentaires que nous avons ajoutées au site. Avec cette classe, nous pouvons étendre la méthode ConstructPSSite. Nous devons également ajouter une référence à l’espace de noms System.Reflection.
private PSObject ConstructPSSite(
string computerName,
ManagementObject site)
{
PSObject pssite = ObjectConverter.ConvertSiteToPSObject(site);
pssite.Properties.Add(new PSNoteProperty("Computer", computerName));
pssite.Properties.Add(new PSNoteProperty("Credential", Credential));
Type siteMethodsType = typeof(SiteMethods);
foreach (MethodInfo mi in siteMethodsType.GetMethods())
{
if (mi.Name.Equals("Start", StringComparison.InvariantCultureIgnoreCase))
{
pssite.Methods.Add(new PSCodeMethod("Start", mi));
}
if (mi.Name.Equals("Stop", StringComparison.InvariantCultureIgnoreCase))
{
pssite.Methods.Add(new PSCodeMethod("Stop", mi));
}
if (mi.Name.Equals("GetStatus", StringComparison.InvariantCultureIgnoreCase))
{
pssite.Properties.Add(new PSCodeProperty("Status", mi));
}
}
return pssite;
}
Outre les méthodes ajoutées, il existe une propriété dynamique : « Status ». Elle se comporte de la même façon que les propriétés dans les classes C#. Il s’agit d’une fonction appelée lorsque PowerShell a besoin de sa valeur. Le code est très simple, car nous faisons référence à des méthodes provenant du même assembly que notre cmdlet. Rien n’empêche le chargement d’un autre assembly et l’obtention des informations sur les méthodes de ses classes. Si ces méthodes ont la bonne signature, PowerShell l’utilise de la même façon.
L’objet ressemble à présent à ce qui suit :
>$s = get-iissite "Default Web Site" –computer test-server
> $s | get-member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Start CodeMethod static System.Void Start(PSObject site)
Stop CodeMethod static System.Void Stop(PSObject site)
Status CodeProperty System.String Status{get=GetStatus;}
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
ToString Method System.String ToString()
ApplicationDefaults NoteProperty System.Management.Automation.PSObjec...
Bindings NoteProperty System.Management.Automation.PSObjec...
Computer NoteProperty System.String Computer=iissb-101
Credential NoteProperty System.Management.Automation.PSCrede...
Id NoteProperty System.UInt32 Id=1
Limits NoteProperty System.Management.Automation.PSObjec...
LogFile NoteProperty System.Management.Automation.PSObjec...
Name NoteProperty System.String Name=Default Web Site
ServerAutoStart NoteProperty System.Boolean ServerAutoStart=True
TraceFailedRequestsLogging NoteProperty System.Management.Automation.PSObjec...
VirtualDirectoryDefaults NoteProperty System.Management.Automation.PSObjec...
>$s.Status
Started
> $s.Stop()
> $s.Status
Stopped
> $s.Start()
> $s.Status
Started
Avec la possibilité d’ajouter des méthodes et des propriétés dynamiques aux objets, nous pouvons synthétiser ce dont nous avons besoin pour n’importe quelle situation. En plus des méthodes et des propriétés ajoutées dans la cmdlet, nous pouvons en ajouter d’autres dans le script, sans avoir à utiliser du code C#.
Il est également possible de charger la définition de l’objet à partir de XML. Les données exposées à partir d’IIS via des compteurs de performance liés au site (par exemple, le nombre total de requêtes traitées) sont de bons candidats pour des propriétés supplémentaires. Ces données sont facilement accessibles directement à partir du code managé. Il n’est pas nécessaire d’utiliser WMI.
Comment appeler une cmdlet à partir d’une autre cmdlet
L’obtention d’objets de site est importante, mais nous avons besoin de plus, comme l’écriture d’une commande pour ajouter un nouveau site. Pour créer des sites, nous pouvons utiliser la méthode abstraite Create() définie sur la classe Site dans l’espace de noms WMI WebAdministration. La cmdlet ressemble à ceci :
>add-iissite –name <siteName> -computer <serverName> -credential <credential> -bindings <array-of-bindings> –homepath <path> -autostart
Nous avons les mêmes paramètres que ceux définis dans la méthode Create. En outre, la commande doit prendre en charge les commutateurs –whatif et –passthru. Le premier commutateur affiche le résultat de l’exécution de la commande, mais n’apporte aucune modification, tandis que le second commutateur indique à la commande de générer le résultat dans le pipeline. Il est vivement recommandé d’utiliser ces deux commutateurs dans les commandes « destructives ». Pour prendre en charge la cmdlet –whatif, la classe doit être décorée par l’attribut SupportsShouldProcess = true.
Voici une partie du code (vous trouverez l’intégralité du code dans le fichier iisdemocmd.cs).
[Cmdlet(VerbsCommon.Add, "IISSite", SupportsShouldProcess = true)]
public class AddSiteCommand : RemotingCommand
{
//…
private SwitchParameter passThru = new SwitchParameter(false);
[Parameter]
public SwitchParameter PassThru
{
get { return passThru; }
set { passThru = value; }
}
protected override void EndProcessing()
{
base.EndProcessing();
if (ShouldProcess(string.Format("{0} bound to {1} on {2}", name, bindings.ToString(), rootFolder)))
{
object[] args = new object[4];
args[0] = Name;
ManagementBaseObject[] mbarr = new ManagementBaseObject[bindings.Length];
for (int b = 0; b < bindings.Length; ++b)
{
mbarr[b] = ObjectConverter.ToManagementObject(
GetScope(Computer), "BindingElement", bindings[b]);
}
args[1] = mbarr;
args[2] = rootFolder;
args[3] = autoStart;
ManagementClass siteClass = CreateClassObject(Computer, "Site");
try
{
siteClass.InvokeMethod("Create", args);
}
catch (COMException comEx)
{
WriteError(new ErrorRecord(comEx, comEx.Message, ErrorCategory.InvalidArgument, Name));
}
if (PassThru.IsPresent)
{
string getSiteScript = "get-iissite"
+ " -name " + Name
+ " -computer " + Computer
+ " -credential $args[0]";
this.InvokeCommand.InvokeScript(
getSiteScript, false, PipelineResultTypes.Output, null, Credential);
}
}
}
}
Ce code utilise une nouvelle méthode dans la classe ObjectConverter pour produire un tableau de liaisons. La méthode ToManagementObject() convertit les paramètres d’entrée qui peuvent être PSObject ou Hashtable dans l’instance de la classe ManagementBaseObject. Étant donné que l’appel à Create peut échouer avec des paramètres parfaitement corrects si le site avec ces paramètres est déjà disponible, nous appelons cette méthode dans try/catch.
Enfin, si la cmdlet vérifie si l’utilisateur spécifié –passthru est présent, elle appelle PowerShell pour exécuter un élément de script qui retourne ce nouveau site. Dans ce script, nous appelons notre commande « get-iissite » et réutilisons les paramètres transmis à la commande actuelle. InvokeScript place le résultat dans le pipeline tel qu’il est demandé. Il n’est donc pas nécessaire de faire autre chose. Il s’agit d’un exemple de la façon dont nous pouvons effectuer un « rappel » à PowerShell, en transmettant des lignes de commande mises en forme statiquement ou dynamiquement. Bien sûr, il est possible de l’écrire en tant que code C#, mais il nécessite soit de couper et de coller de grandes parties de GetSiteCommand, soit de réorganiser et de refactoriser l’espace de noms.
Au début de la méthode EndProcessing(), nous voyons l’appel à ShouldProcess(). Il s’agit de la façon dont le commutateur –whatif est pris en charge. Lorsque l’utilisateur transmet ce commutateur, cette méthode imprime le texte transmis en tant que paramètre, et le retour a la valeur false. Toutes les actions qui peuvent modifier l’environnement doivent être effectuées lorsque cet appel retourne la valeur true. PowerShell a d’autres commutateurs qui peuvent interagir avec l’utilisateur et demander confirmation avant d’effectuer une action. ShouldProcess() est retourné à la suite de cette confirmation.
Testez la nouvelle commande avec les éléments suivants :
> add-iissite Foo @{Protocol="http"; BindingInformation="*:808"} e:\inetpub\demo -computer test-server -whatif
What if: Performing operation "Add-IISSite" on Target "Foo bound to System.Management.Automation.PSObject[] on e:\inetpub\demo".
Cela montre le fonctionnement de –whatif. Nous obtenons un message plutôt cryptique sur ce qui se passe quand cette commande s’exécute. Pour le rendre plus clair, nous devons le mettre en forme correctement. Le paramètre Bindings est entré dans la ligne de commande sous la forme d’une table de hachage et est transmise à la cmdlet en tant que Hashtable encapsulé dans PSObject. Pour produire du texte explicite, nous devons ajouter du code intelligent ; la méthode ToString() par défaut renvoie simplement le nom de la classe.
Insérez ce bloc de texte à la place de la ligne ShouldProcess() :
StringBuilder bindingText = new StringBuilder("(");
foreach (PSObject b in bindings)
{
Hashtable ht = b.BaseObject as Hashtable;
foreach (object key in ht.Keys)
{
string bstr = String.Format("{0}={1}",
key.ToString(), ht[key].ToString());
bindingText.Append(bstr + ",");
}
bindingText.Remove(bindingText.Length - 1, 1);
bindingText.Append(";");
}
bindingText.Remove(bindingText.Length - 1, 1);
bindingText.Append(")");
if (ShouldProcess(string.Format("{0} bound to {1} on {2}", name, bindingText.ToString(), rootFolder)))
Une fois la cmdlet générée et exécutée, la sortie suivante s’affiche :
> add-iissite Foo @{Protocol="http"; BindingInformation="*:888"} e:\inetpub\demo -computer test-server -whatif
What if: Performing operation "Add-IISSite" on Target "Foo bound to (BindingInformation=*:888,Protocol=http) on e:\inetpub\demo".
Ceci est beaucoup plus compréhensible. Ce nouveau code montre également clairement pourquoi nous devons traiter Hashtable dans la méthode ToManagementObject(). Il s’agit d’un type courant dans PowerShell pour transmettre des paramètres structurés.
Ensuite, exécutez la commande.
> add-iissite Foo @{Protocol="http"; BindingInformation="*:888"} e:\inetpub\demo -computer test-server -passthru | format-table Name,Status
Name Status
---- ------
Foo Stopped
> get-iissite -computer sergeia-a | format-table name,status
Name Status
---- ------
Default Web Site Started
Foo Stopped
La première commande a créé le site sur le serveur distant, puis l’a récupéré et transmis au pipeline. Pour garantir qu’elle a été effectuée correctement, nous avons obtenu une liste des sites, et en effet, le nouveau site est disponible. Par défaut, le serveur tente de démarrer le site, sauf si nous ajoutons le paramètre –AutoStart false. En cas de problème dans les paramètres, par exemple, le serveur ne trouve pas le dossier de base, le site reste arrêté.
Extension des cmdlets pour utiliser une batterie de serveurs
Pour l’instant, nous avons deux commandes : get-iissite et add-iissite. Nous ne disposons pas de cmdlets pour enregistrer le site modifié et supprimer le site. La commande de suppression doit être remove-iissite pour qu’elle soit compatible avec les normes d’affectation de noms PowerShell. La commande Enregistrer porte le nom set-iissite. Pour remove-iissite, nous modifions le code get-iissite et appelons la méthode Delete() sur ManagementObject.
[Cmdlet(VerbsCommon.Remove, "IISSite", SupportsShouldProcess = true)]
public class RemoveSiteCommand : RemotingCommand
{
private string name = null;
[Parameter(
Position = 0,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string Name
{
get { return name; }
set { name = value; }
}
protected override void EndProcessing()
{
base.EndProcessing();
if (ShouldProcess(string.Format("{0} on server {1}", name, Computer)))
{
ManagementObject site = CreateClassInstance(Computer, "Site.Name=\"" + Name + "\"");
site.Delete();
}
}
} //RemoveSiteCommand
Nous avons également ajouté une méthode simple CreateClassInstance() à la cmdlet parente. Cette méthode produit l’instance d’objet liée au chemin d’accès à l’objet. Une autre modification est que le paramètre Name ne peut désormais pas être vide, sinon l’utilisateur peut supprimer tous les sites par erreur. Enfin, nous avons ajouté l’appel ShouldProcess() pour activer les commutateurs –whatif et –confirm.
> Remove-IISSite foo -computer test-server -confirm
Confirm
Are you sure you want to perform this action?
Performing operation "Remove-IISSite" on Target "foo on server sergeia-a".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "Y"): <CR>
> get-iissite -computer test-server | ft name,status
Name Status
---- ------
Default Web Site Started
Nous pouvons implémenter la dernière commande set-iissite en tant qu’exercice, en modifiant la cmdlet add-iissite et en appelant Put() sur ManagementObject.
À présent, mettez à l’échelle les commandes et adaptez-les pour qu’elles fonctionnent avec plusieurs serveurs. C’est très simple :
Modifiez la propriété Computer sur la commande parente pour représenter un tableau de chaînes :
private string[] computer = { Environment.MachineName };
[Parameter(
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string[] Computer
{
get { return computer; }
set { computer = value; }
}
Ensuite, ajoutez une boucle supplémentaire sur ce tableau dans chaque cmdlet pour effectuer la même action sur chaque ordinateur. Voici un exemple de get-iissite :
foreach (string computerName in Computer)
{
ManagementObjectCollection sites = CreateClassObject(computerName, "Site").GetInstances();
foreach (ManagementObject site in sites)
{
if (Name != null)
{
string siteName = site.GetPropertyValue("Name") as string;
if (siteName.Equals(Name, StringComparison.InvariantCultureIgnoreCase))
{
WriteObject(ConstructPSSite(computerName, site));
break;
}
}
else
{
WriteObject(ConstructPSSite(computerName, site));
}
}
}
Nous pouvons à présent manipuler des sites sur l’ensemble de la batterie de serveurs.
> get-iissite -computer test-server,iissb-101,iissb-102 | ft Computer,Name,Status
Computer Name Status
-------- ---- ------
test-server Default Web Site Started
iissb-101 Default Web Site Started
iissb-101 Demo Started
iissb-102 Default Web Site Started
Enregistrez les noms de serveurs de la batterie dans un fichier texte et utilisez-les comme paramètres :
>$("test-server","iissb-101","iissb-102" >farm.txt
>cat farm.txt
test-server
tissb-101
tissb-102
>get-iissite –computer $(cat farm.txt) | ft Computer,Name,Status
Computer Name Status
-------- ---- ------
test-server Default Web Site Started
iissb-101 Default Web Site Started
iissb-101 Demo Started
iissb-102 Default Web Site Started
>get-iissite –computer $(cat farm.txt) | where {$_.Computer –like "iissb*"} | ft Computer,Name,Status
Computer Name Status
-------- ---- ------
iissb-101 Default Web Site Started
iissb-101 Demo Started
iissb-102 Default Web Site Started
Nous pouvons effectuer des actions plus avancées à l’aide de code PowerShell supplémentaire. Le code suivant énumère les sites sur les serveurs avec des noms. En commençant par « iissb », stockez la liste dans la variable, puis arrêtez tous les sites démarrés.
> $sitelist = get-iissite -computer $(cat farm.txt) | where {$_.Computer -like "iissb*"}
> foreach ($site in $sitelist) {
>> if ($site.Status -eq "Started") {$site.Stop()}
>> }
>>
> get-iissite -computer $(cat farm.txt) | ft Computer,Name,Status
Computer Name Status
-------- ---- ------
test-server Default Web Site Started
iissb-101 Default Web Site Stopped
iissb-101 Demo Stopped
iissb-102 Default Web Site Stopped
La variable $sitelist conserve la liste des sites, mais grâce à la nature dynamique de la propriété site.Status, nous pouvons voir l’état réel, non stocké, de chaque objet.
> $sitelist | ft computer,name,status
Computer Name Status
-------- ---- ------
iissb-101 Default Web Site Stopped
iissb-101 Demo Stopped
iissb-102 Default Web Site Stopped
Nous pouvons faire de même sans utiliser de variables. Démarrez un site arrêté sur n’importe quel serveur de la batterie de serveurs.
> get-iissite -computer (cat farm.txt) | foreach { if ($_.Status -eq "Stopped") { $_.Start() }}
> get-iissite -computer $(cat farm.txt) | ft Computer,Name,Status
Computer Name Status
-------- ---- ------
test-server Default Web Site Started
iissb-101 Default Web Site Started
iissb-101 Demo Started
iissb-102 Default Web Site Started
Dans le fichier source associé iisdemocmd.cs, vous trouverez d’autres commandes pour manipuler des répertoires virtuels et certaines propriétés dans les sections de configuration.
Conclusion
Comme nous pouvons le constater, la présence de trois commandes seulement nous permet de couvrir la plupart des besoins en matière d’administration des sites IIS. Combinée à la flexibilité et à la richesse du langage shell, chaque commande ajoute une grande quantité de fonctionnalités. En parallèle, l’écriture d’une nouvelle commande n’est pas beaucoup plus complexe que l’implémentation d’un script similaire dans VBScript ou JScript.
L’équipe IIS prévoit d’ajouter une prise en charge complète de PowerShell dans IIS 7.0 et versions ultérieures. Cela inclut l’implémentation d’un fournisseur de navigation, d’un fournisseur de propriétés et de toutes les autres fonctionnalités requises pour travailler avec tous les aspects de l’administration. Suivez l’avancement de ces améliorations à venir et recherchez l’annonce sur https://www.iis.net/ et sur le site PowerShell.