Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
von Sergei Antonow
Einführung
Da PowerShell ausgeliefert wurde, erhalten IIS-Administratoren ein neues Tool für die Verwendung. Der folgende Artikel konzentriert sich auf Verwaltungsaufgaben für IIS 7.0 und höher; PowerShell kann jedoch für vorhandene IIS 6.0-Server verwendet werden.
Dieser Artikel konzentriert sich auf die Verwaltung des IIS-Remoteservers mithilfe von PowerShell auf dem Clientcomputer. Ab diesem Schreiben ist dies nur möglich, wenn Sie den WMI-Anbieter verwenden, den das IIS-Team mit Vista und Windows Server® 2008 ausgeliefert hat. In diesem Fall müssen Sie nichts mit IIS auf Ihrem Clientcomputer in Verbindung haben – WMI stellt die Verbindung zur tatsächlichen Konfiguration bereit, die auf dem Remoteserver verfügbar ist.
Hinweis
Sie können auch Microsoft.Web.Administration in PowerShell verwenden, um Verwaltungsfunktionen auszuführen. Dieser Artikel konzentriert sich jedoch nicht auf diese Technik.
Das PowerShell-Team hat einen speziellen Befehl für den Zugriff auf WMI-Objekte – get-wmiobject – erstellt. Normalerweise wird ein Objekt zurückgegeben, das in PowerShell erstellt wird, das WMI-Eigenschaften und -Methoden verfügbar macht, anstelle des üblichen verwalteten Codeobjekts, das für normale Klassen zurückgegeben wird.
Dieses synthetische Objekt macht Metadaten verfügbar, die im WMI-Namespace definiert sind, nicht Metadaten von System.Management.ManagementBaseObject, das als Basis verwendet wird. Dadurch erhält der Benutzer die Namespaceansicht, die vom WMI-Anbieter verfügbar gemacht wurde und das Verwaltungsmodell der konfigurierten Entität widerspiegelt.
Leider funktioniert dieser Befehl nicht mit IIS-Namespaces. Für PowerShell Version 1.0 unterstützt das get-wmiobject nur die Standardauthentifizierungsebene für die Remote-DCOM-Verbindung. Dies reicht weder für IIS 6.0 noch für IIS 7.0 und höher aus. Wenn Benutzer IIS konfigurieren, kann es erforderlich sein, Kennwörter und andere vertrauliche Daten über die Netzwerkverbindung zu senden, um sie zu bearbeiten und in der Konfiguration zu speichern. Um dies zu unterstützen, benötigen IIS-Anbieter eine Authentifizierungsstufe "Paketdatenschutz". Es gibt keine Möglichkeit, diese Anforderung für das Cmdlet get-wmiobject zu liefern.
Mit dieser Einschränkung haben wir zwei Optionen:
- Verwenden Sie PowerShell als generische Skriptschnittstelle für den System.Management-Namespace. Verwenden Sie PowerShell auch, um Folgendes auszuführen: Schreiben Sie Skriptcode, der die Verbindung mit dem Remote-WMI-Namespace konfiguriert. und zum Abrufen und Speichern von Verwaltungsdaten mithilfe von System.Management-Objekten, auf die über PowerShell zugegriffen wird. Es ist wie die C#-Programmierung, nur in PowerShell-Sprache. In der Regel funktioniert dies gut, aber für den WMI-Fall wendet PowerShell einen speziellen Adapter an, der automatisch alle System.Management-Objekte in synthetische PowerShell-Objekte konvertiert, die WMI-Namespace als primäre API verfügbar machen. Aus diesem Grund müssen wir Adapteränderungen überwinden und zusätzlichen Code schreiben, um auf die "systemeigene" System.Management-Unterstruktur zuzugreifen, wodurch diese Programmierung schnell zu einer unnötig komplizierten Übung wird.
Daher erkunden wir die andere Option:
- Schreiben Sie PowerShell-Cmdlets in C# und greifen Sie auf die erforderliche Funktionalität im C#-Code zu. In diesem Fall wählen wir alle geeigneten APIs für konfigurierte Entitäten aus. Wir verwenden WMI für den Zugriff auf den Server. Im Vergleich zum gleichen Code in PowerShell ist C# viel effektiver, da es nicht jedes Mal analysiert und interpretiert werden muss.
PowerShell-Cmdlet
Um mit dem Schreiben von Cmdlets zu beginnen, benötigen Sie einen Clientcomputer, der mit PowerShell installiert ist. Sie müssen auch powerShell SDK installieren oder einfach Referenz-DLLs in den Arbeitsordner kopieren, indem Sie den Trick verwenden, der von Jeffrey Snover im PowerShell-Teamblog veröffentlicht wurde . Stellen Sie sicher, dass Sie über die DCOM-Verbindung auf Ihrem Server verfügen. Die einfachste Möglichkeit, dies zu bestätigen, besteht darin, das Hilfsprogramm wbemtest zu starten, das auf jeder Windows-Plattform verfügbar ist, und versuchen Sie die Verbindung.
- Starten Sie wbemtest.
- Klicken Sie auf Verbinden.
- Verbindungsparameter eingeben:
- Ersetzen Sie "root\default" durch \<computer>\root\webadministration, wobei "<computer>" der Name Ihres Servers sein muss.
- Geben Sie die Anmeldeinformationen des Kontos ein, das Über Administratorrechte auf dem Server verfügt.
- Wählen Sie "Paketdatenschutz" in der Gruppe "Authentifizierungsebene" aus.
- Klicken Sie auf Verbinden. WMI auf Ihrem Clientcomputer stellt eine Verbindung mit dem WMI-Dienst auf Ihrem Servercomputer her. Wenn nicht darauf zugegriffen werden kann, wird ein Dialogfeld mit einer Fehlermeldung angezeigt.
- Führen Sie eine einfache Aktion aus, die den WMI-Anbieter auf dem Server einbezieht, um zu bestätigen, dass er funktioniert. Führen Sie eine Aufzählung von Websites aus:
- Klicken Sie auf die Schaltfläche "Enum-Instanzen" und geben Sie "site" für den Klassennamen ein.
- Wenn dies funktioniert, zeigt das resultierende Dialogfeld eine Liste aller Websites an, die auf Ihrem Server verfügbar sind.
Das PowerShell-Cmdlet ist einfach eine assembly mit verwaltetem Code, die nach formalen Regeln implementiert ist, die im PowerShell SDK dokumentiert sind. Suchen Sie sie online.
Bevor Sie Code schreiben, ist es nützlich, einen Plan zu haben.
Implementieren Sie zunächst ein Cmdlet, das alle IIS-Websites auf dem Remoteserver aufzählt. Dieses Cmdlet gibt ein Array von Websiteobjekten zurück, das das IIS-Konfigurationselement mit Eigenschaften darstellt, die für die Website definiert sind. Wir fügen einige zusätzliche Eigenschaften hinzu, die für dieses Objekt nützlich sind.
Wir möchten, dass das Cmdlet wie folgt aussieht:
get-iissite –computer somecomputer –name somesite –credential $(get-credential)
Wenn die Anmeldeinformationen nicht programmgesteuert an das Cmdlet übergeben werden, erstellt PowerShell ein Dialogfeld, in dem der Benutzername und das Kennwort für den Remoteserver angefordert werden.
Um das Websiteobjekt vom Remotecomputer abzurufen, müssen wir die folgenden Parameter aus unserem Cmdlet angeben:
public string Computer;
public string Name;
public PSCredential Credential;
Alle diese Parameter sind öffentliche Eigenschaften in der Cmdlet-Klasse, die durch das Parameter-Attribut versehen sind.
Implementieren Sie das erste Cmdlet. Da get-iissite nicht unser letzter Befehl ist, ist es besser, zwei Dinge auszuführen: den Code, der für die Verbindung mit dem Server verantwortlich ist, in die übergeordnete Klasse RemotingCommand auslagern, und das Cmdlet von dieser Klasse erben.
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()
);
}
}
Class RemotingCommand enthält Parameter und Methoden, die für die Verbindung erforderlich sind.
- GetScope() gibt das System.Management-Objekt zurück, das alle Informationen enthält, die für die Verbindung mit dem Remotenamespace erforderlich sind. Schauen Sie sich die Verbindungseigenschaft "Authentifizierung" an. Sie wird in AuthenticationLevel.PacketPrivacy initialisiert. Dies ist eine obligatorische Anforderung. Andernfalls lehnt WMI die Verbindung ab.
- Methode CreateClassObject() ist eine Hilfsmethode, die den Verbindungskontext verwendet, um eine angegebene Klasse basierend auf dem Remote-WMI-Namespace zu erstellen.
- EndProcessing() -Methode ist die Standardmethode, die jede Cmdlet-Klasse implementieren soll. Sie wird von PowerShell aufgerufen, wenn unser Cmdlet verarbeitet wird. Die Implementierung von EndProcessing() versucht, die Credential-Eigenschaft auszufüllen, wenn sie leer ist. Beim ersten Versuch werden die Anmeldeinformationen aus der externen Variablen IISCredential (nur zur Benutzerfreundlichkeit) abgerufen.
Wenn Sie sich in einer PowerShell-Sitzung befinden, kann der Benutzer den Benutzernamen und das Kennwort in dieser Variablen platzieren und mehrmals in verschiedenen Befehlen verwenden. Wenn diese Variable nicht definiert ist oder einen ungeeigneten Objekttyp enthält, ruft der Code die Anmeldeinformationen des aktuellen Benutzers oder Prozesses ab. Es funktioniert, wenn der Benutzer diesen Befehl lokal auf dem Server unter Verwendung des Administratorkontos ausführt. In diesem Fall müssen wir überhaupt keine Anmeldeinformationen eingeben. Für jede Schleife in diesem Code gibt es einen bekannten Trick zum Konvertieren des Kennworts aus einer Zeichenfolge in SecureString.
Jetzt implementieren wir 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
In der ersten Durchlauf liefert der Befehl nur Seitennamen zurück. Wenn der Benutzer eine bestimmte Website möchte, muss er dem Befehl den Namen dieser Website angeben. andernfalls werden alle Websites auf diesem bestimmten Computer zurückgegeben.
Um den Befehl abzuschließen, müssen Sie die Implementierung der Klasse hinzufügen, die von PSSnapin geerbt wurde. Diese Klasse wird verwendet, um unsere Befehle zu registrieren. Es gibt nichts Spezifisches zu IIS; siehe den vollständigen Code in der Quelldatei IISDemoCmd.cs.
Erstellen Sie das Cmdlet jetzt, und sehen Sie, wie es funktioniert. Sie können dies in Visual Studio tun, aber es ist einfach genug, sie über die Befehlszeile zu erstellen. Angenommen, Sie haben PowerShell-Referenz-DLLs in einen Ordner c:\sdkeingefügt. Die folgende Befehlszeile erstellt das Cmdlet in IISDemoCmd.dll und platziert es in demselben Ordner, in dem sich die Quelldatei befindet.
%windir%\Microsoft.NET\Framework\v2.0.50727\csc /t:library /r:c:\sdk\system.management.automation.dll IISDemoCmd.cs
Jetzt müssen Sie den Befehl registrieren und ihn in PowerShell hinzufügen. Dieses Verfahren wird in der PowerShell-Programmierreferenz beschrieben. Starten Sie PowerShell, und führen Sie die folgenden Befehle aus demselben Ordner aus, in dem Sie die Cmdlet-DLL erstellt haben.
>set-alias installutil $env:windir\Microsoft.NET\Framework\v2.0.50727\installutil
>installutil iisdemocmd.dll
>add-pssnapin IISDemoCmdSnapIn
Dadurch wird das Cmdlet zur ausgeführten Instanz von PowerShell hinzugefügt. Speichern Sie diese Befehlszeilen in einer Skriptdatei. Sie werden sie erneut verwenden, während Sie mit der Arbeit an den Cmdlets fortfahren. Sie finden dieses Skript in der Datei demo_install.ps1.
Überprüfen Sie, ob der Befehl verfügbar ist:
>get-command Get-IISSite
CommandType Name Definition
----------- ---- ----------
Cmdlet Get-IISSite Get-IISSite [[-Name] <String...
Versuchen Sie es jetzt. Angenommen, Sie stellen mithilfe des lokalen Administratorkontos eine Verbindung mit dem Computer test_server her.
>Get-IISSite -computer test_server -credential $(get-credential administrator)
Default Web Site
Foo
Bar
Diese Befehlszeile empfängt das Anmeldeinformationsobjekt vom Cmdlet get-credential, das mit dem Benutzer interagiert, um das Kennwort abzurufen. Es ist auch möglich, die Anmeldeinformationen programmgesteuert zu erstellen, aber Sie müssen das Kennwort in das Skript eingeben, das überhaupt nicht sicher ist.
>$global:iiscredential = new-object System.Management.Automation.PsCredential "Administrator",$(convertto-securestring "password" -asplaintext -force)
Mit diesem Befehl werden die Anmeldeinformationen in der globalen Variablen $iiscredential gespeichert, und das Cmdlet verwendet sie automatisch. In echten Sitautions ist es jedoch besser, die Anmeldeinformationen mithilfe des Befehls "get-credential" in einer Variablen zu speichern: $global:iiscredential = get-credential Administrator.
Dieser Befehl wird nun erneut ausgeführt.
>Get-IISSite -computer test_server
Default Web Site
Foo
Bar
Alle Infrastruktur ist vorhanden. Kehren Sie nun zum Befehl zurück, und fügen Sie die restlichen Daten von der Website hinzu.
Hinzufügen von Konfigurationsdaten zur Website
Wir müssen das Objekt von ManagementBaseObject in PSObject konvertieren und es an PowerShell zurückgeben. PSObject ist ein Freihandformcontainer, der mit verschiedenen Arten von Daten gefüllt werden kann. Wir werden den PSNoteProperty-Typ verwenden. Lassen Sie den Cmdlet-Code sauber, und fügen Sie eine neue Klasse hinzu, die für die Konvertierung verantwortlich ist.
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;
}
}
Dieser Code greift auf komplexe Eigenschaften zurück und fügt dem resultierenden PSObject einfache Eigenschaften wie PSNoteProperty hinzu. Außerdem fügen wir eine dedizierte Methode hinzu, mit der die Konvertierung in das Cmdlet behandelt wird. Diese Methode konvertiert alle WMI-Daten und fügt zwei weitere Eigenschaften hinzu: den Computernamen und die Anmeldeinformationen, die zum Abrufen der Verbindung mit diesem Computer verwendet wurden. Dies hilft, jede Website von anderen Objekten in der PowerShell-Sitzung zu unterscheiden.
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;
}
Ersetzen Sie den Websitenamen, den wir an PowerShell zurückgegeben haben, durch ein ganzes Objekt. Die Methode EndProcessing() im Cmdlet sieht nun wie folgt aus:
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));
}
}
}
Wenn wir den Build und die Registrierung wiederholen und den Befehl erneut ausführen, werden weitere Daten zur Website angezeigt:
> 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
Wenn Sie dies mit dem WMI-Schema für die Website vergleichen, sehen Sie, dass alle Daten jetzt verfügbar sind. darüber hinaus verfügen wir über zusätzliche Eigenschaften, die wir im Cmdlet hinzugefügt haben. Auf alle Eigenschaften kann über PowerShell über die Punktnotation zugegriffen werden.
> $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
Wir haben gute Fortschritte gemacht, aber nicht gut genug. Die WMI-Website verfügt auch über Methoden. Versuchen Sie daher, sie ebenfalls hinzuzufügen. Dies ist einfach in PowerShell– Sie nennen die Methode und teilen PowerShell mit, wo sich der Code befindet. Wir fügen Methoden des PSCodeMethod-Typs hinzu. Um den Code für die Methoden beizubehalten, fügen wir die Klasse SiteMethods hinzu.
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[] { });
}
}
Wie Sie sehen, erstellt dieser Code ein WMI-Objekt für die Website und ruft WMI-Methoden für dieses Objekt auf. Dieser Code verwendet zwei zusätzliche Eigenschaften, die wir der Website hinzugefügt haben. Mit dieser Klasse können wir die Methode ConstructPSSite erweitern. Außerdem müssen wir einen Verweis auf den System.Reflection-Namespace hinzufügen.
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;
}
Zusätzlich zu den hinzugefügten Methoden gibt es eine dynamische Eigenschaft - "Status". Es verhält sich auf die gleiche Weise wie Eigenschaften in C#-Klassen; es ist eine Funktion, die aufgerufen wird, wenn PowerShell ihren Wert benötigt. Der Code ist sehr einfach, da wir Methoden aus derselben Assembly wie unser Cmdlet referenzieren. Es gibt nichts, was das Laden einer anderen Assembly und das Abrufen der Informationen zu den Methoden deren Klassen verhindert. Wenn diese Methoden über die richtige Signatur verfügen, verwendet PowerShell sie auf die gleiche Weise.
Das Objekt sieht nun wie folgt aus:
>$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
Mit der Möglichkeit, Den Objekten Methoden und dynamische Eigenschaften hinzuzufügen, können wir das synthetisieren, was wir für jede Situation benötigen. Zusätzlich zu den Methoden und Eigenschaften, die im Cmdlet hinzugefügt wurden, können wir dem Skript weitere Hinzufügen, ohne dass C#-Code verwendet werden muss.
Es ist auch möglich, die Definition des Objekts aus XML zu laden. Ein guter Kandidat für zusätzliche Eigenschaften sind Daten, die von IIS über Leistungsindikatoren im Zusammenhang mit Websites verfügbar gemacht werden, z. B. die Gesamtanzahl der verarbeiteten Anforderungen. Auf diese Daten kann problemlos direkt über den verwalteten Code zugegriffen werden– es ist nicht erforderlich, WMI zu verwenden.
Aufrufen eines Cmdlets aus einem anderen Cmdlet
Das Abrufen von Websiteobjekten ist wichtig, aber wir benötigen mehr, z. B. das Schreiben eines Befehls zum Hinzufügen einer neuen Website. Zum Erstellen von Websites können wir die abstrakte Methode Create() verwenden, die in der Site-Klasse im WMI WebAdministration-Namespace definiert ist. Das Cmdlet sieht wie folgt aus:
>add-iissite –name <siteName> -computer <serverName> -credential <credential> -bindings <array-of-bindings> –homepath <path> -autostart
Wir haben dieselben Parameter wie in der Create-Methode definiert. Darüber hinaus sollte der Befehl die Schalter "Whatif" und "–passthru" unterstützen. Das erste zeigt das Ergebnis der Befehlsausführung, nimmt jedoch keine Änderungen vor; der zweite weist den Befehl an, das Ergebnis in der Pipeline auszugeben. Diese beiden Schalter sind bei destruktiven Befehlen sehr empfehlenswert zu verwenden. Zur Unterstützung des Cmdlets "–whatif" muss die Klasse durch das Attribut SupportsShouldProcess = true versehen werden.
Dies ist Teil des Codes (finden Sie den gesamten Code im 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);
}
}
}
}
Dieser Code verwendet eine neue Methode in der ObjectConverter-Klasse, um ein Bindungenarray zu erzeugen. Die Methode ToManagementObject() konvertiert Eingabeparameter, die PSObject oder Hashtable in die Instanz der ManagementBaseObject-Klasse sein können. Da der Aufruf von Create mit perfekt korrekten Parametern fehlschlägt, wenn die Website mit diesen Parametern bereits verfügbar ist, rufen wir diese Methode in try/cach auf.
Wenn das Cmdlet überprüft, ob der vom Benutzer angegebene "–passthru" vorhanden ist, ruft es PowerShell auf, um ein Skript auszuführen, das diese neue Website zurückgibt. In diesem Skript rufen wir unseren Befehl "get-iissite" auf und verwenden die parameter, die an den aktuellen Befehl übergeben werden. InvokeScript fügt das Ergebnis wie angefordert in die Pipeline ein, sodass keine weiteren Schritte erforderlich sind. Dies ist ein Beispiel dafür, wie wir "callback" zu PowerShell ausführen können, indem formatierte Befehlszeilen statisch oder dynamisch übergeben werden. Natürlich ist es möglich, sie als C#-Code zu schreiben, erfordert jedoch entweder das Ausschneiden und Einfügen großer Teile von GetSiteCommand oder das Neuorganisieren und Umgestalten des Namespaces.
Am Anfang der Methode EndProcessing() wird der Aufruf von ShouldProcess() angezeigt. So wird der Switch "–whatif" unterstützt. Wenn der Benutzer diesen Schalter übergibt, gibt diese Methode den Text aus, der als Parameter an sie übergeben wird, und es wird false zurückgegeben. Alle Aktionen, die die Umgebung ändern können, müssen ausgeführt werden, wenn dieser Aufruf "true" zurückgibt. PowerShell verfügt über andere Schalter, die mit dem Benutzer interagieren und um Bestätigung bitten, bevor eine Aktion ausgeführt wird. SollteProcess() wird als Ergebnis dieser Bestätigung zurückgegeben.
Testen Sie den neuen Befehl mit folgendem Befehl:
> 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".
So funktioniert –whatif. Wir erhalten eine ziemlich kryptische Nachricht darüber, was passiert, wenn dieser Befehl ausgeführt wird. Um es klarer zu machen, müssen wir es richtig formatieren. Parameterbindungen werden in der Befehlszeile als Hashtabelle eingegeben und als in PSObject eingebettete Hashtable an das Cmdlet übergeben. Um aussagekräftigen Text daraus zu erzeugen, müssen wir intelligenteren Code hinzufügen – der Standardmäßige ToString() gibt einfach den Klassennamen zurück.
Fügen Sie diesen Textblock anstelle der Zeile "ShouldProcess()" ein:
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)))
Nachdem das Cmdlet erstellt und ausgeführt wurde, wird die folgende Ausgabe angezeigt:
> 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".
Das ist viel verständlicher. Aus diesem neuen Code ist auch klar, warum wir Hashtable in der Methode ToManagementObject() verarbeiten müssen – dies ist ein gängiger Typ in PowerShell zum Übergeben strukturierter Parameter.
Führen Sie nun den Befehl aus.
> 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
Der erste Befehl hat die Website auf dem Remoteserver erstellt und anschließend abgerufen und an die Pipeline übergeben. Um sicherzustellen, dass sie ordnungsgemäß durchgeführt wurde, haben wir eine Liste der Websites erhalten, und tatsächlich ist die neue Website verfügbar. Standardmäßig versucht der Server, die Website zu starten, es sei denn, wir fügen den Parameter "–AutoStart false" hinzu. Wenn es ein Problem in den Parametern gibt , z. B. kann der Server den Startordner nicht finden - dann bleibt die Website angehalten.
Cmdlets erweitern, um mit einer Serverfarm zu arbeiten
Derzeit haben wir zwei Befehle: Get-IISSite und Add-IISSite. Es fehlen Cmdlets zum Speichern der geänderten Website und zum Löschen der Website. Der Löschbefehl sollte "remove-iissite" sein, um ihn mit den PowerShell-Benennungsstandards kompatibel zu halten. Der Befehl wird den Namen "set-iissite" tragen. Für remove-iissite ändern wir den get-iissite-Code und rufen die Methode Delete() für ManagementObject auf.
[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
Außerdem wurde dem übergeordneten Cmdlet eine einfache Methode CreateClassInstance() hinzugefügt. Diese Methode erzeugt die Objektinstanz, die an den Objektpfad gebunden ist. Eine weitere Änderung besteht darin, dass der Parameter Name jetzt nicht leer sein kann. Andernfalls kann der Benutzer versehentlich alle Websites löschen. Schließlich wurde der Aufruf "ShouldProcess()" hinzugefügt, um die Schalter "–whatif" und "-confirm" zu aktivieren.
> 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
Wir können den letzten Befehlssatz set-iissite zur Übung implementieren, das add-iissite-Cmdlet ändern und Put() für ManagementObject aufrufen.
Skalieren Sie nun die Befehle heraus, und passen Sie sie an die Arbeit mit mehreren Servern an. Dies ist einfach:
Ändern Sie die Eigenschaft Computer im übergeordneten Befehl, um ein Array von Zeichenfolgen darzustellen:
private string[] computer = { Environment.MachineName };
[Parameter(
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string[] Computer
{
get { return computer; }
set { computer = value; }
}
Fügen Sie dann in jedem Cmdlet eine zusätzliche Schleife über dieses Array hinzu, um dieselbe Aktion auf jedem Computer auszuführen. Hier ist ein Beispiel von 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));
}
}
}
Jetzt können wir Websites in der gesamten Serverfarm bearbeiten.
> 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
Speichern Sie die Servernamen aus der Farm in einer Textdatei, und verwenden Sie sie als Parameter:
>$("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
Wir können komplexere Aufgaben mit mehr PowerShell-Sprache erledigen. Der folgende Code listet Websites auf Servern mit Namen auf. Ab "iissb" speichern Sie die Liste in der Variablen, und beenden Sie dann alle Websites, die gestartet werden.
> $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
Die Variable $sitelist hält die Seitenliste, aber dank der dynamischen Natur der Eigenschaft site.Status sehen wir den tatsächlichen Status, der nicht gespeichert ist, jedes Objekts.
> $sitelist | ft computer,name,status
Computer Name Status
-------- ---- ------
iissb-101 Default Web Site Stopped
iissb-101 Demo Stopped
iissb-102 Default Web Site Stopped
Wir können dasselbe tun, ohne Variablen zu verwenden. Starten Sie alle beendeten Websites auf einem beliebigen Server in der Serverfarm.
> 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
In der zugehörigen Quelldatei iisdemocmd.cs finden Sie weitere Befehle zum Bearbeiten virtueller Verzeichnisse und einiger Eigenschaften in den Konfigurationsabschnitten.
Fazit
Wie wir sehen können, können wir mit nur drei Befehlen die meisten Anforderungen in der Verwaltung von IIS-Websites abdecken. In Kombination mit der Flexibilität und Fülle der Shellsprache fügt jeder Befehl eine vielzahl von Funktionen hinzu. Gleichzeitig ist das Schreiben eines neuen Befehls nicht viel komplizierter als die Implementierung ähnlicher Skripts in VBScript oder Jscript.
Das IIS-Team plant die vollständige Unterstützung von PowerShell in IIS 7.0 und höher hinzuzufügen. Dazu gehört die Implementierung eines Navigationsanbieters, eines Eigenschaftsanbieters und aller anderen Funktionalitäten, die erforderlich sind, um mit allen Aspekten der Verwaltung zu arbeiten. Folgen Sie dem Fortschritt dieser bevorstehenden Verbesserungen; suchen Sie nach der Ankündigung auf https://www.iis.net/ und auf der PowerShell-Seite.