Поделиться через


Написание командлетов PowerShell для IIS 7.0

Автор: Сергей Антонов

Введение

С помощью PowerShell администраторы IIS получают новое средство для использования. В следующей статье рассматриваются задачи администрирования для IIS 7.0 и более поздних версий. Однако PowerShell можно использовать для существующих серверов IIS 6.0.

В этой статье рассматривается администрирование удаленного сервера IIS с помощью PowerShell на клиентском компьютере. На момент написания этой статьи это возможно, только если вы используете поставщик WMI, поставляемый командой IIS с Vista и Windows Server® 2008. В этом случае на клиентском компьютере не требуется ничего, связанного с IIS. WMI обеспечивает подключение к фактической конфигурации, доступной на удаленном сервере.

Примечание

Вы также можете использовать Microsoft.Web.Administration в PowerShell для выполнения функций администрирования. Однако эта статья не посвящена этому методу.

Команда PowerShell создала специальную команду для доступа к объектам WMI — get-wmiobject. Обычно он возвращает объект, созданный в PowerShell, который предоставляет свойства и методы WMI, а не обычный управляемый объект кода, возвращаемый для обычных классов.

Этот искусственный объект предоставляет метаданные, определенные в пространстве имен WMI, а не метаданные из System.Management.ManagementBaseObject, который используется в качестве основы. Это дает пользователю представление пространства имен, которое было предоставлено поставщиком WMI и которое отражает модель администрирования настроенной сущности.

К сожалению, эта команда не работает с пространствами имен IIS. Для PowerShell версии 1.0 get-wmiobject поддерживает только уровень проверки подлинности по умолчанию для удаленного подключения DCOM. Этого недостаточно ни для IIS 6.0, ни для IIS 7.0 и более поздних версий. При настройке служб IIS может потребоваться отправить пароли и другие конфиденциальные данные через сетевое подключение, чтобы изменить и сохранить их в конфигурации. Для этой поддержки поставщикам IIS требуется уровень проверки подлинности "Конфиденциальность пакетов". Невозможно предоставить это требование командлету get-wmiobject.

С этим ограничением у нас есть два варианта:

  • Используйте PowerShell в качестве универсального интерфейса скриптов для пространства имен System.Management. Используйте PowerShell также для выполнения следующих действий: написание кода скрипта, который настраивает подключение к удаленному пространству имен WMI; и для получения и сохранения данных администрирования с помощью объектов System.Management, доступных через PowerShell. Это похоже на программирование на C#, только на языке PowerShell. Обычно это работает хорошо, но в случае WMI PowerShell применяет специальный адаптер, который автоматически преобразует все объекты System.Management в искусственные объекты PowerShell, предоставляющие пространство имен WMI в качестве основного API. По этой причине необходимо преодолеть изменения адаптера и написать дополнительный код для доступа к "собственной" подструктуре System.Management, которая быстро превращает это программирование в неоправданно сложное упражнение.

Поэтому мы рассмотрим другой вариант:

  • Напишите командлеты PowerShell на C# и получите доступ к необходимым функциям в коде C#. В этом случае мы выбираем любые подходящие API для настроенных сущностей. Для доступа к серверу используется WMI. По сравнению с тем же кодом в PowerShell, C# гораздо эффективнее, так как его не нужно анализировать и интерпретировать каждый раз.

Командлет PowerShell

Чтобы приступить к написанию командлетов, вам потребуется клиентский компьютер, установленный с PowerShell. Кроме того, необходимо установить пакет SDK для PowerShell или просто скопировать ссылочные библиотеки DLL в рабочую папку, используя инструкции, опубликованные Джеффри Snover в блоге группы разработчиков PowerShell. Убедитесь, что на сервере установлено подключение DCOM. Самый простой способ подтвердить это — запустить служебную программу wbemtest, которая доступна на каждой платформе Windows, и попробовать подключение.

  1. Запустите wbemtest.
  2. Нажмите кнопку "Подключить".
  3. Введите параметры подключения:
    • Замените "root\default" на \<computer>\root\webadministration, где "<computer>" должно быть именем вашего сервера.
    • Введите учетные данные учетной записи с правами администратора на сервере.
    • Выберите "Конфиденциальность пакетов" в группе уровня проверки подлинности.
  4. Нажмите кнопку "Подключить". WMI на клиентском компьютере подключается к службе WMI на серверном компьютере. Если он недоступен, появится диалоговое окно с сообщением об ошибке.
  5. Выполните простое действие, которое использует поставщик WMI на сервере, чтобы убедиться, что он работает. Выполните перечисление сайтов:
    • Нажмите кнопку "Экземпляры перечисления" и введите "site" в поле имя класса.
    • Когда это работает, в результирующем диалоговом окне отображается список всех сайтов, доступных на сервере.

Командлет PowerShell — это просто управляемая сборка кода, реализованная в соответствии с формальными правилами, которые описаны в пакете SDK для PowerShell. Найдите их в интернете.

Перед написанием любого кода полезно иметь план.

Сначала реализуйте командлет, который перечисляет все сайты IIS на удаленном сервере. Этот командлет возвращает массив объектов сайта, представляющих элемент конфигурации IIS со свойствами, определенными для сайта. Мы добавим некоторые дополнительные свойства, которые удобно иметь в этом объекте.

Мы хотим, чтобы командлет выглядел следующим образом:

get-iissite –computer somecomputer –name somesite –credential $(get-credential)

Если мы не передадим учетные данные командлету программным способом, PowerShell создает диалоговое окно с запросом имени пользователя и пароля для удаленного сервера.

Чтобы получить объект сайта с удаленного компьютера, необходимо указать следующие параметры из нашего командлета:

public string Computer;
public string Name;
public PSCredential Credential;

Все эти параметры являются общедоступными свойствами в классе командлета, украшенными атрибутом Parameter.

Реализуйте первый командлет. Так как get-iissite не является нашей последней командой, лучше выполнить две задачи: отдельный код, отвечающий за подключение к серверу в родительском классе RemotingCommand; и наследуют командлет от этого класса.

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()
             );
        }
    }

Класс RemotingCommand включает параметры и методы, необходимые для подключения.

  • GetScope() возвращает объект System.Management, который содержит все сведения, необходимые для подключения к удаленному пространству имен. Посмотрите на подключение. Свойство authentication. Он инициализируется в AuthenticationLevel.PacketPrivacy. Это условие является обязательным. В противном случае WMI откажет подключение.
  • Метод CreateClassObject() — это служебный метод, который использует область подключения для создания указанного класса на основе удаленного пространства имен WMI.
  • Метод EndProcessing() — это стандартный метод, который должен реализовывать каждый класс командлета. Он вызывается из PowerShell при обработке командлета. Реализация EndProcessing() пытается заполнить свойство учетных данных, если оно пустое. С первой попытки он получает учетные данные из внешней переменной IISCredential (только для удобства).

В сеансе PowerShell пользователь может захотеть поместить имя пользователя и пароль в эту переменную и использовать их несколько раз в нескольких командах. Если эта переменная не определена или содержит неподходящий тип объекта, код получает учетные данные текущего пользователя или процесса. Она работает, когда пользователь выполняет эту команду локально на сервере, используя учетную запись администратора. В этом случае нам вообще не нужно вводить учетные данные. Для каждого цикла в этом коде существует известный способ преобразования пароля из строки в SecureString.

Теперь мы реализуем 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

В первом случае команда возвращает только имена сайтов. Если пользователь хочет какой-то определенный сайт, он должен указать имя этого сайта в команде ; в противном случае будут возвращены все сайты на этом конкретном компьютере.

Чтобы завершить команду, необходимо добавить реализацию класса , наследуемого от PSSnapin. Этот класс используется для регистрации наших команд. Он не имеет ничего конкретного для IIS; См. полный код в исходном файле IISDemoCmd.cs.

Создайте командлет и посмотрите, как он работает. Это можно сделать из Visual Studio, но это достаточно просто, чтобы создать его из командной строки. Предположим, что вы поместили ссылочные библиотеки DLL PowerShell в папку c:\sdk. Следующая командная строка создает командлет в IISDemoCmd.dll и помещает его в ту же папку, где находится исходный файл.

%windir%\Microsoft.NET\Framework\v2.0.50727\csc /t:library /r:c:\sdk\system.management.automation.dll IISDemoCmd.cs

Теперь необходимо зарегистрировать команду и добавить ее в PowerShell. Эта процедура описана в справочнике по программированию в PowerShell. Запустите PowerShell и выполните следующие команды из той же папки, в которой вы создали библиотеку DLL командлета.

>set-alias installutil $env:windir\Microsoft.NET\Framework\v2.0.50727\installutil
>installutil iisdemocmd.dll
>add-pssnapin IISDemoCmdSnapIn

При этом командлет добавляется в работающий экземпляр PowerShell. Сохраните эти командные строки в файл скрипта. Вы будете использовать их снова по мере продолжения работы с командлетами. Этот скрипт можно найти в demo_install.ps1 файла.

Убедитесь, что команда доступна:

>get-command Get-IISSite
 
CommandType     Name                            Definition
-----------     ----                            ----------
Cmdlet          Get-IISSite                     Get-IISSite [[-Name] <String...

Теперь попробуйте. Предположим, что вы подключаетесь к компьютеру test_server с помощью учетной записи локального администратора.

>Get-IISSite -computer test_server -credential $(get-credential administrator)
 
Default Web Site
Foo
Bar

Эта командная строка получает объект учетных данных из командлета get-credential, который будет взаимодействовать с пользователем для получения пароля. Учетные данные также можно создать программным способом, но в скрипте необходимо ввести пароль, который не является безопасным.

>$global:iiscredential = new-object System.Management.Automation.PsCredential "Administrator",$(convertto-securestring "password" -asplaintext -force)

Эта команда сохраняет учетные данные в глобальной переменной $iiscredential, и командлет будет использовать их автоматически. Однако в реальных сценариях учетные данные лучше хранить в переменной с помощью команды get-credential: $global:iiscredential = get-credential Administrator.

Теперь эта команда снова.

>Get-IISSite -computer test_server
Default Web Site
Foo
Bar

Вся инфраструктура на месте. Теперь вернитесь к команде и добавьте остальные данные с сайта.

Добавление данных конфигурации на сайт

Необходимо преобразовать объект из ManagementBaseObject в PSObject и вернуть его в PowerShell. PSObject — это контейнер свободной формы, который может быть заполнен различными типами данных. Мы будем использовать тип PSNoteProperty. Сохраните код командлета в чистоте и добавьте новый класс, отвечающий за преобразование.

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;
    }
}

Этот код использует сложные свойства и добавляет простые свойства, такие как PSNoteProperty, в полученный объект PSObject. Мы также добавим выделенный метод, который будет работать с преобразованием в командлет out. Этот метод преобразует все данные WMI и добавляет еще два свойства: имя компьютера и учетные данные, которые использовались для подключения к этому компьютеру. Это помогает отличить каждый сайт от других объектов в сеансе 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;
}

Замените имя сайта, возвращенное в PowerShell, целым объектом. Метод EndProcessing() в командлете теперь выглядит следующим образом:

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));
        }
    }
}

Когда мы повторим сборку и регистрацию и снова запустите команду, мы увидим дополнительные данные о сайте:

> 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

Если сравнить это со схемой WMI для сайта, вы увидите, что все данные теперь доступны. кроме того, у нас есть дополнительные свойства, добавленные в командлет . Все свойства доступны из PowerShell с помощью точечной нотации.

> $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

Мы добились хорошего прогресса, но недостаточно. На сайте WMI также есть методы, поэтому попробуйте добавить их. Это легко сделать в PowerShell: вы присвоите методу имя и сообщите PowerShell, где находится код. Мы добавим методы типа PSCodeMethod. Чтобы сохранить код для методов, мы добавим класс 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[] { });
    }
}

Как видите, этот код создает объект WMI для сайта и вызывает методы WMI для этого объекта. В этом коде используются два дополнительных свойства, которые мы добавили на сайт. С помощью этого класса можно расширить метод ConstructPSSite. Необходимо также добавить ссылку на пространство имен 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;
}

Помимо добавленных методов, существует одно динамическое свойство — Status. Он ведет себя так же, как свойства в классах C#; Это функция, которая вызывается, когда в PowerShell требуется ее значение. Код очень прост, так как мы ссылаемся на методы из той же сборки, что и наш командлет. Ничто не препятствует загрузке любой другой сборки и получению сведений о методах ее классов. Если эти методы имеют правильную сигнатуру, PowerShell использует ее таким же образом.

Объект теперь выглядит следующим образом:

>$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

С помощью возможности добавлять методы и динамические свойства к объектам, мы можем синтезировать то, что нам нужно для любой ситуации. Помимо методов и свойств, добавленных в командлете, мы можем добавить дополнительные сведения в скрипт без необходимости использовать код C#.

Также можно загрузить определение объекта из XML. Хорошим кандидатом для дополнительных свойств являются данные, предоставляемые из IIS через счетчики производительности, связанные с сайтом, например общее количество обработанных запросов. Эти данные легко доступны непосредственно из управляемого кода— нет необходимости использовать WMI.

Вызов одного командлета из другого командлета

Получение объектов сайта важно, но нам нужно больше, например написание команды для добавления нового сайта. Для создания сайтов можно использовать абстрактный метод Create(), определенный в классе Site в пространстве имен WMI WebAdministration. Командлет выглядит следующим образом:

>add-iissite –name <siteName> -computer <serverName> -credential <credential> -bindings <array-of-bindings> –homepath <path> -autostart

У нас есть те же параметры, что и в методе Create. Кроме того, команда должна поддерживать параметры –whatif и –passthru. Первый показывает результат выполнения команды, но не вносит никаких изменений; второй указывает команде выводить результат в конвейер. Эти два параметра настоятельно рекомендуется использовать в командах destcructive. Для поддержки командлета –whatif класс должен быть декорирован атрибутом SupportsShouldProcess = true.

Ниже приведена часть кода (найдите весь код в файле 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);
            }
        }
    }
}

Этот код использует новый метод в классе ObjectConverter для создания массива привязок. Метод ToManagementObject() преобразует входные параметры, которые могут быть PSObject или Hashtable, в экземпляр класса ManagementBaseObject. Так как вызов Create может завершиться ошибкой с совершенно правильными параметрами, если сайт с этими параметрами уже доступен, мы вызываем этот метод в try/cach.

Наконец, если командлет проверяет, присутствует ли указанный пользователем параметр –passthru, он вызывает PowerShell для выполнения части скрипта, возвращающего этот новый сайт. В этом сценарии мы вызываем команду get-iissite и повторно будем использовать параметры, переданные в текущую команду. InvokeScript помещает результат в конвейер в соответствии с запросом, поэтому больше ничего делать не нужно. Это пример того, как можно выполнить "обратный вызов" в PowerShell, передавая отформатированные командные строки статически или динамически. Конечно, его можно написать как код C#, но для этого требуется либо вырезать и вставить большие части GetSiteCommand, либо реорганизовать и рефакторинг пространства имен.

В начале метода EndProcessing() мы видим вызов ShouldProcess(). Вот как поддерживается параметр –whatif. Когда пользователь передает этот параметр, этот метод выводит текст, переданный ему в качестве параметра, и возвращает значение false. Все действия, которые могут изменить среду, должны выполняться, когда этот вызов возвращает значение true. В PowerShell есть другие параметры, которые могут взаимодействовать с пользователем и запрашивать подтверждение перед выполнением какого-либо действия. Функция ShouldProcess() возвращает результат этого подтверждения.

Протестируйте новую команду, выполнив следующую команду:

> 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".

Вот как работает –whatif. Мы получаем довольно загадочное сообщение о том, что происходит при выполнении этой команды. Чтобы сделать это более понятным, мы должны правильно отформатировать его. Привязки параметров вводятся в командной строке в виде хэш-таблицы и передаются в командлет как хэш-таблица, заключенная в PSObject. Чтобы создать осмысленный текст из него, необходимо добавить дополнительный интеллектуальный код. По умолчанию ToString() просто возвращает имя класса.

Вставьте этот блок текста вместо строки 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)))

После сборки и выполнения командлета мы видим следующие выходные данные:

> 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".

Это гораздо более понятно. Из этого нового кода также ясно, почему необходимо обрабатывать Hashtable в методе ToManagementObject() — это распространенный тип в PowerShell для передачи структурированных параметров.

Теперь выполните следующую команду:

> 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

Первая команда создала сайт на удаленном сервере, а затем извлекла его и передала в конвейер. Чтобы убедиться, что это было сделано правильно, мы получили список сайтов, и действительно, новый сайт доступен. По умолчанию сервер попытается запустить сайт, если мы не добавим параметр –AutoStart false. Если в параметрах возникла проблема, например, сервер не может найти домашнюю папку, сайт останется остановленным.

Расширение командлетов для работы с фермой серверов

Сейчас у нас есть две команды: get-iissite и add-iissite. Отсутствуют командлеты для сохранения измененного сайта и удаления сайта. Команда удаления должна быть remove-iissite, чтобы обеспечить совместимость со стандартами именования PowerShell. Команда Сохранить будет иметь имя set-iissite. Для remove-iissite мы изменим код get-iissite и вызываем метод Delete() в 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

Мы также добавили простой метод CreateClassInstance() в родительский командлет. Этот метод создает экземпляр объекта, привязанный к пути к объекту. Еще одно изменение заключается в том, что параметр Name теперь не может быть пустым. В противном случае пользователь может удалить все сайты по ошибке. Наконец, мы добавили вызов ShouldProcess() для включения параметров –whatif и –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

Последнюю команду set-iissite можно реализовать как упражнение, изменив командлет add-iissite и вызвав Метод Put() в ManagementObject.

Теперь масштабируйте команды и адаптируйте их для работы с несколькими серверами. Это просто:

Измените свойство Computer в родительской команде, чтобы представить массив строк:

private string[] computer = { Environment.MachineName };
[Parameter(
   ValueFromPipeline = true,
   ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string[] Computer
{
    get { return computer; }
    set { computer = value; }
}

Затем добавьте дополнительный цикл по этому массиву в каждый командлет, чтобы выполнить одно и то же действие на каждом компьютере. Ниже приведен пример из 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));
                }
            }
        }

Теперь мы можем управлять сайтами во всей ферме серверов.

> 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

Сохраните имена серверов из фермы в текстовый файл и используйте их в качестве параметра:

>$("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

Мы можем выполнять более сложные задачи, используя больше языка PowerShell. Следующий код перечисляет сайты на серверах с именами. Начиная с "iissb", сохраните список в переменной и остановите все запущенные сайты.

> $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

Переменная $sitelist сохраняет список сайтов, но благодаря динамическому характеру сайта свойств. Состояние, мы видим фактическое, не сохранено состояние каждого объекта.

> $sitelist | ft computer,name,status

Computer                   Name                       Status
--------                   ----                       ------
iissb-101                  Default Web Site           Stopped
iissb-101                  Demo                       Stopped
iissb-102                  Default Web Site           Stopped

Мы можем сделать то же самое без использования каких-либо переменных. Запустите любой остановленный сайт на любом сервере в ферме серверов.

> 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

В сопроводительном исходном файле iisdemocmd.cs вы найдете дополнительные команды для управления виртуальными каталогами и некоторыми свойствами в разделах конфигурации.

Заключение

Как мы видим, наличие только трех команд позволяет нам удовлетворить большинство потребностей в администрировании сайтов IIS. В сочетании с гибкостью и богатством языка оболочки каждая команда добавляет множество функциональных возможностей. В то же время создание новой команды не намного сложнее, чем реализация аналогичного скрипта в VBScript или Jscript.

Команда IIS планирует добавить полную поддержку PowerShell в IIS 7.0 и более поздних версий. Сюда входит реализация поставщика навигации, поставщика свойств и всех других функций, необходимых для работы со всеми аспектами администрирования. Следите за ходом выполнения этих предстоящих улучшений и найдите объявление на https://www.iis.net/ сайте PowerShell и на сайте PowerShell.