IIS 7.0 向けの PowerShell コマンドレットの作成

作成者 : Sergei Antonov
発行日 : 2007 年 11 月 23 日 (作業者 : saad(英語))
更新日 : 2008 年 5 月 13 日 (作業者 : saad(英語))

はじめに 

PowerShell の登場により、IIS 管理者が使用できる新しいツールが増えました。この記事では、IIS 7.0 の管理タスクを主に取り上げていますが、PowerShell は既存の IIS 6.0 サーバーでも使用可能です。

この記事では、クライアント マシンで PowerShell を使用したリモート IIS 7.0 サーバーの管理を中心に説明します。この記事の執筆時点では、IIS チームが提供する WMI プロバイダーを Vista および Windows Server® 2008 と共に使用する場合にのみ、この記事の内容を実行することができます。この場合、クライアント コンピューターには IIS 関連のコンポーネントは一切インストールする必要はありません。WMI が、リモート サーバー上で使用可能な実際の構成への接続を提供します。

: 管理機能を実行する際に、PowerShell 内で Microsoft.Web.Administration も使用することがあります。ただし、この記事ではその技術について特に取り上げません。

PowerShell チームは、WMI オブジェクトにアクセスするための特別なコマンド (get-wmiobject) を作成しました。通常このコマンドは、PowerShell 内で作成され、WMI のプロパティとメソッドを公開するためのオブジェクトを返します。このオブジェクトは、通常のクラスで返される普通のマネージ コード オブジェクトではありません。

この合成オブジェクトは、ベースとして使用される System.Management.ManagementBaseObject からのメタデータではなく、WMI 名前空間で定義されるメタデータを公開します。これにより、ユーザーは WMI プロバイダーによって公開された、構成されたエンティティの管理モデルを反映する名前空間のビューを使用できます。

残念ながら、このコマンドは IIS 名前空間では動作しません。PowerShell バージョン 1.0 では、get-wmiobject はリモートの DCOM 接続に対して既定の認証レベルのみをサポートします。これは、IIS 7.0 または IIS 6.0 では十分ではありません。ユーザーが IIS を構成する際には、パスワードおよびその他の機密データを構成内で編集および保存するために、それらのデータをネットワーク接続上で送信することが必要になる場合があります。これをサポートするために、IIS 6.0 および IIS 7.0 の IIS プロバイダーでは、認証レベル "パケットのプライバシー" が必要となります。get-wmiobject でこの要件を満たす方法はありません。

この制限に対処するためには、次の 2 つのオプションがあります。

  • PowerShell を System.Management 名前空間に対する汎用スクリプト インターフェイスとして使用します。また、リモート WMI 名前空間への接続を構成するスクリプト コードを記述するため、および System.Management オブジェクトを使用して (PowerShell 経由でアクセス) 管理データを取得および保存するためにも、PowerShell を使用します。これは C# プログラミングと似ています。言語を PowerShell に置き換えただけのことです。通常はこの方法でうまくいきますが、WMI に対して PowerShell は特殊なアダプターを適用します。このアダプターは、すべての System.Management オブジェクトを、WMI 名前空間をプライマリ API として公開する合成 PowerShell オブジェクトに自動的に変換します。このため、"ネイティブ" の System.Management サブストラクチャにアクセスする追加のコードを作成してアダプターの変更に対処する必要があります。これによって、このプログラミングは不必要に複雑な作業になってしまいます。

 そのため、次に示す別のオプションを検討します。

  • C# で PowerShell コマンドレットを記述し、C# コードで必要な機能にアクセスします。この場合、構成したエンティティに対して適切な API を選択します。WMI を使用してサーバーにアクセスします。PowerShell による同じコードと比較すると、C# のコードは毎回解析および解釈する必要がないので、より効率的です。

この記事には次のような内容が含まれています。

  • PowerShell コマンドレット

  • サイトへの構成データの追加

  • コマンドレットを別のコマンドレットから呼び出す方法

  • サーバー ファームで動作するためのコマンドレット拡張

PowerShell コマンドレット

コマンドレットの作成を開始するには、クライアント コンピューターに PowerShell をインストールしておく必要があります。また、PowerShell SDK をインストールするか、または Jeffrey Snover 氏によって PowerShell チーム ブログに投稿された手法 (英語)を使用して、参照 DLL を作業フォルダーにコピーする必要があります。サーバーに DCOM 接続が用意されていることを確認してください。これを確認する最も簡単な方法は、各 Windows プラットフォームで利用可能な wbemtest ユーティリティを起動して、接続を試行することです。

  1.  wbemtest を起動します。
  2.  [接続] をクリックします。
  3.  次のように接続パラメーターを入力します。

        a. "root\default" を "\\<computer>\root\webadministration" に置き換えます。<computer> には使用するサーバーの名前を指定する必要があります。
        b. サーバーで管理者権限を持つアカウントの資格情報を入力します。
        c. 認証レベル グループで [パケットのプライバシー] を選択します。

  4.  [接続] をクリックします。クライアント コンピューター上の WMI が、サーバー コンピューター上の WMI サービスに接続します。アクセスできなかった場合、エラー メッセージを示すダイアログ ボックスが表示されます。
  5.  サーバー コンピューター上の WMI プロバイダーに関連する簡単な操作を実行し、それが機能することを確認します。たとえば、サイトの列挙を実行します。 

        a.  [インスタンスの列挙] ボタンをクリックし、クラス名として「site」と入力します。
        b.  正常に実行されると、結果のダイアログ ボックスに、サーバー上で利用可能なすべてのサイトの一覧が表示されます。

PowerShell コマンドレットは、形式的なルールによって実装された単なるマネージ コードのアセンブリです。それらのルールは PowerShell SDK で文書化されており、オンライン (英語)で参照できます。

コードを記述する前に、計画を立てるのは有効です。

最初に、リモート サーバー上のすべての IIS 7.0 サイトを列挙するコマンドレットを実装します。このコマンドレットはサイト オブジェクトの配列を返します。このオブジェクトは、サイトに対して定義された、IIS 7.0 構成要素とプロパティを表します。このオブジェクト内に、有用なプロパティをいくつか追加していきます。

次のようなコマンドレットが作成されるようにします。

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

プログラムによって資格情報をコマンドレットに渡さない場合、PowerShell は、リモート サーバーに対するユーザー名とパスワードを要求するダイアログ ボックスを生成します。

リモート コンピューターからサイト オブジェクトを取得するには、コマンドレットから次のパラメーターを指定する必要があります。

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

これらすべてのパラメーターは、Parameter 属性によって修飾された、cmdlet クラス内のパブリック プロパティです。

最初のコマンドレットを実装します。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 オブジェクトを返します。connection.Authentication プロパティに注目してください。このプロパティは AuthenticationLevel.PacketPrivacy に初期化されています。これは必須の要件です。このように初期化しないと、WMI によって接続が拒否されます。
  • メソッド CreateClassObject() は、接続スコープを使用して、リモート WMI 名前空間に基づいて指定されたクラスを作成するユーティリティ メソッドです。
  • EndProcessing() メソッドは、各コマンドレット クラスによって実装される必要のある標準メソッドです。このメソッドは、コマンドレットの処理時に、PowerShell から呼び出されます。ここでの EndProcessing() の実装では、credential プロパティが空の場合に、そのプロパティにデータを設定します。最初は、外部変数 IISCredential から資格情報が取得されます (利便性のため)。

PowerShell セッションでは、ユーザーはユーザー名とパスワードをこの変数に設定して、複数のコマンドで何度も使用できます。この変数が定義されていないか、または不適切なオブジェクト型を含む場合、コードは現在のユーザーまたはプロセスの資格情報を取得します。この処理は、ユーザーが管理者アカウントを利用して、サーバー上でローカルにこのコマンドを実行している場合に機能します。この場合、資格情報を入力する必要はありません。このコード内のループで、パスワードを string から 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 から構築することもできますが、ここではシンプルにコマンド ラインから構築します。PowerShell 参照 DLL をフォルダー 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;
        }
    }

このコードは、複雑なプロパティを受け取り、生成した PSObject に PSNoteProperty のような単純なプロパティとして追加します。また、この変換を処理する専用メソッドをコマンドレットに追加します。このメソッドではすべての WMI データを変換します。また、コンピューター名、およびこのコンピューターへの接続に使用された資格情報の 2 つのプロパティをさらに追加します。これは、各サイトを 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 メソッドを呼び出します。このコードは、サイトに対して追加した 2 つのプロパティを使用します。このクラスを使用して、メソッド 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 7.0 から公開されるサイト関連のデータがあります。たとえば、処理された要求の合計カウントなどです。これらのデータには、マネージ コードから直接簡単にアクセスできます。WMI を使用する必要はありません。

コマンドレットを別のコマンドレットから呼び出す方法

サイト オブジェクトの取得は重要なことですが、新しいサイトを追加するコマンドを作成することなど、さらに多くのことが必要です。サイトを作成するには、WMI の WebAdministration 名前空間の Site クラスに定義されている抽象メソッド Create() を使用できます。コマンドレットは次のようになります。

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

Create メソッドで定義されているのと同じパラメーターを使用します。加えて、このコマンドでは -whatif スイッチおよび -passthru スイッチをサポートする必要があります。前者は、コマンド実行の結果を示しますが、変更は行いません。後者は、結果をパイプラインに出力するようにコマンドに指示します。これら 2 つのスイッチは、誤って使用すると破壊的な結果をもたらすコマンドで使用することを強くお勧めします。-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 の呼び出しは失敗します。よって、Create メソッドは try/catch の内部で呼び出します。

最後に、コマンドレットは、ユーザーが -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 の動作方法を示しています。このコマンドの実行時に発生した事柄について、やや暗号的なメッセージが表示されます。よりわかりやすくするには、それを適切に書式設定する必要があります。パラメーター Bindings がハッシュ テーブルとしてコマンド ラインに入力され、PSObject にラップされた Hashtable としてコマンドレットに渡されます。そこから意味を持つテキストを生成するには、よりスマートなコードを追加する必要があります。既定の 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".

この方がより明快です。この新しいコードからは、ToManagementObject() メソッドで Hashtable を処理しなければならない理由も明確にわかります。これは、構造化されたパラメーターを渡す際に 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 の 2 つのコマンドを作成しました。しかし、変更したサイトを保存するためのコマンドレットと、サイトを削除するためのコマンドレットがまだありません。削除コマンドは、PowerShell の名前付け標準に準拠させるため、remove-iissite にする必要があります。保存コマンドの名前は、set-iissite となります。remove-iissite を作成するには、get-iissite コードを変更して、ManagementObject でメソッド Delete() を呼び出します。



    [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 コマンドレットを変更して、ManagementObject で Put() を呼び出すようにします。この実装は、読者の演習課題とします。

ここで、コマンドをスケール アウトして、複数のサーバーで機能するように調整します。これは簡単です。

親コマンドのプロパティ 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 に保持されますが、プロパティ site.Status の動的な性質により、各オブジェクトの保存されたステータスではなく、実際のステータスが表示されます。

> $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 には、仮想ディレクトリや構成セクション内のプロパティを操作するための、さらに多くのコマンドが含まれています。

まとめ

今まで見てきたように、3 つのコマンドを使用するだけで、IIS 7.0 サイトの管理で必要な大半の作業を実行することができます。シェル言語の柔軟性と豊富な機能と組み合わせることにより、各コマンドはより多くの機能を備えることができます。同時に、新しいコマンドを作成することは、VBScript や Jscript で同様のスクリプトを実装することに比べても、それほど複雑な作業ではありません。

IIS チームは、IIS 7.0 に PowerShell の完全なサポートを追加することを計画しています。これには、ナビゲーション プロバイダー、プロパティ プロバイダー、および管理のすべての側面における作業に必要な他のすべての機能を実装することが含まれます。今後の機能向上の進捗状況については、https://www.iis.net/(英語) および PowerShell サイトでの告知を参照してください。