C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2007/12/09 16:35
SOAP メッセージの検証・変更を行うための PowerShell 関数を作成しました。

【 Set-MessageInspector 関数 】
WCF クライアントが要求メッセージを送信する前、応答メッセージを受信した後の 2 箇所に独自の処理を追加します。
追加した処理からは、要求メッセージまたは応答メッセージの検査・変更を行うことができます。
これにより、例えば、SOAP メッセージをログに記録することができます。

[ パラメータ ]
・target
SvcUtil.exe が生成したクライアントクラス ( ClientBase ジェネリッククラスの派生クラス ) のインスタンスを指定します。
または、チャネルファクトリのインスタンスを指定します。

・beforeSendRequest
WCF クライアントが要求メッセージを送信する前に行う処理を指定します。
beforeSendRequest に指定する関数内では、要求メッセージ (System.ServiceModel.Channels.Message) を第一引数から取得できます。
また、WCF クライアント オブジェクト チャネル (System.ServiceModel.IClientChannel) を第二引数から取得できます。
関数が戻り値を返した場合、戻り値は afterReceiveReply に指定した関数の第二引数に、相関状態データとして渡されます。

・afterReceiveReply
WCF クライアントが応答メッセージを受信した後に行う処理を指定します。
afterReceiveReply に指定する関数内では、応答メッセージ (System.ServiceModel.Channels.Message) を第一引数から取得できます。
また、相関状態データ (System.Object) を第二引数から取得できます。


[ 戻り値 ]
なし


[ コード ]
function Set-MessageInspector
{
    param ($target, [ScriptBlock]$beforeSendRequest, [ScriptBlock]$afterReceiveReply)
    trap { break; }
   
    function Compile-MessageInspectorCode
    {
        $source =
@"
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Runspaces;

internal sealed class PSClientMessageInspector : IClientMessageInspector
{
    private static object InvokeScript(ScriptBlock target, params object[] args)
    {
        StringBuilder argsTextBuilder = new StringBuilder();
        if (args != null)
        {
            for (int i = 0; i <= args.Length; i++)
            {
                argsTextBuilder.AppendFormat("`$invokeArgs[{0}] ", i);
            }
        }
        string invokeCommandText = string.Format("`$invokeArgs = `$Input | % {{ `$_; }}; &{{{0}}} {1}", target, argsTextBuilder);
        Command invokeCommand = new Command(invokeCommandText, true, true);
        using (Pipeline invokePipeline = Runspace.DefaultRunspace.CreateNestedPipeline())
        {
            invokePipeline.Commands.Add(invokeCommand);
            Collection<PSObject> invokeResult = invokePipeline.Invoke(args);
            object result = null;
            if ((invokeResult != null) && (invokeResult.Count != 0))
            {
                result = (invokeResult.Count == 1) ? (object)invokeResult[0] : (object)invokeResult;
            }
            return result;
        }
    }
   
    private readonly ScriptBlock _beforeSendRequestScriptBlock;
   
    private readonly ScriptBlock _afterReceiveReplyScriptBlock;
   
    public PSClientMessageInspector(ScriptBlock beforeSendRequestScriptBlock, ScriptBlock afterReceiveReplyScriptBlock)
    {
        this._beforeSendRequestScriptBlock = beforeSendRequestScriptBlock;
        this._afterReceiveReplyScriptBlock = afterReceiveReplyScriptBlock;
    }
   
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        if (this._beforeSendRequestScriptBlock == null)
        {
            return null;
        }
        return PSClientMessageInspector.InvokeScript(this._beforeSendRequestScriptBlock, request, channel);
    }

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        if (this._afterReceiveReplyScriptBlock == null)
        {
            return;
        }
        PSClientMessageInspector.InvokeScript(this._afterReceiveReplyScriptBlock, reply, correlationState);
    }
}

public sealed class PSClientMessageInspectorBehavior : IEndpointBehavior
{
    private readonly PSClientMessageInspector _inspector;
   
    public PSClientMessageInspectorBehavior(ScriptBlock beforeSendRequestScriptBlock, ScriptBlock afterReceiveReplyScriptBlock)
    {
        this._inspector = new PSClientMessageInspector(beforeSendRequestScriptBlock, afterReceiveReplyScriptBlock);
    }
   
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(this._inspector);
    }
   
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }
   
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }
   
    public void Validate(ServiceEndpoint endpoint)
    {
    }
}
"@;
        $references =
            @(
                [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel").Location,
                [System.Reflection.Assembly]::LoadWithPartialName("System.Management.Automation").Location
            );
        Compile-CSCode $source $references | Out-Null;
    }
   
    $behaviors = $target.Endpoint.Behaviors;
    if ($() -eq $behaviors)
    {
        throw "引数 client が、 System.ServiceModel.ClientBase``1 の派生型、または System.ServiceModel.ChannelFactory``1 の派生型ではありません。";
    }
   
    if ($() -eq ("PSClientMessageInspectorBehavior" -as [Type]))
    {
        Compile-MessageInspectorCode;
    }
    $inspectorBehavior = New-Object "PSClientMessageInspectorBehavior" @($beforeSendRequest, $afterReceiveReply);
    $behaviors.Add($inspectorBehavior);
}



[ 注意 ]
・この関数は、内部で Compile-CSCode 関数 を使用しています。
Compile-CSCode 関数が使用できない場合、この関数は実行に失敗します。

・この関数で取得できる SOAP メッセージは、実際にネットワーク上で通信される SOAP メッセージとは異なる場合がありますので注意してください。
例えば、バインディングにWS-Security が適用されているとネットワーク上で通信される SOAP メッセージは暗号化されますが、この関数で取得した SOAP メッセージは暗号化前もしくは復号後のものになります。
ネットワーク上で実際に通信される SOAP メッセージを調べるには、Microsoft Network Monitor 等のツールを使用してください。


[ 使用例 ]

$client = New-WCFClient Sample.WCF.ServiceClient "C:\Work\WCF\Client.dll.config";
$beforeSendRequest =
    {
        param ($request, $channel)
        return [DateTime]::Now;
    };
$afterReceiveReply =
    {
        param ($reply, $correlationState)
        "requested time: {0}" -f $correlationState | Write-Host;
        $reply | Write-Host;
    };
Set-MessageInspector $client $beforeSendRequest $afterReceiveReply;


このコマンドは、New-WCFClient 関数 を使用して生成されたWCF クライアントのインスタンスに、応答の SOAP メッセージをコンソール出力するための独自の処理を追加します。
応答の SOAP メッセージの出力時には、要求メッセージを送信した日時も出力します。


【 ダウンロード 】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。

YokoKen.PowerShell.Scripts
スポンサーサイト



2007/12/09 04:00
ScriptBlock クラス (System.Management.Automation)Invoke メソッドInvokeReturnAsIs メソッド を使ってスクリプトブロックを実行すると、スクリプトブロック内の param ステートメントによって宣言されたパラメータには、引数が格納されない。 ( $args 自動変数には格納されるけど。 )

で、とりあえず Pipeline クラス (System.Management.Automation.Runspaces) を使って実現してみた。

public static object InvokeScript(ScriptBlock target, params object[] args)
{
    StringBuilder argsTextBuilder = new StringBuilder();
    if (args != null)
    {
        for (int i = 0; i <= args.Length; i++)
        {
            argsTextBuilder.AppendFormat("`$invokeArgs[{0}] ", i);
        }
    }
    string invokeCommandText = string.Format("`$invokeArgs = `$Input | % {{ `$_; }}; &{{{0}}} {1}", target, argsTextBuilder);
    Command invokeCommand = new Command(invokeCommandText, true, true);
    using (Pipeline invokePipeline = Runspace.DefaultRunspace.CreateNestedPipeline())
    {
        invokePipeline.Commands.Add(invokeCommand);
        Collection<PSObject> invokeResult = invokePipeline.Invoke(args);
        object result = null;
        if ((invokeResult != null) && (invokeResult.Count != 0))
        {
            result = (invokeResult.Count == 1) ? (object)invokeResult[0] : (object)invokeResult;
        }
        return result;
    }
}


$Input が Enumrator であるのと、関数の引数の渡し方の関係で、ちょっとゴニョゴニョやってる。
もうちょいエレガントな方法はないものか・・・。
タグ: .NET C# PowerShell
2007/12/08 16:55
例えば Hoge 関数を取得するには、

$hoge = $Function:Hoge;


と書きますが、
同じように Fuga-Piyo 関数を取得しようとすると失敗してしまいます。
これは、関数名に含まれているハイフンが、演算子として解釈されてしまうためです。
では、どうやって Fuga-Piyo 関数を取得すれば良いかというと、${} という形式で変数を使用します。

$fugaPiyo = ${Function:Fuga-Piyo};


これで、Fuga-Piyo 関数を取得できます。

ちなみに、${} 形式で変数を宣言すれば、変数名に空白を含めることもできたりします。
タグ: .NET PowerShell
2007/12/04 22:10
2007/12/02 01:42
開発した WCF サービスのちょっとしたテストを行うのに、一々 Visual Studio でプロジェクト作ってサービス参照追加して C# でコード書いてビルドして・・・なんて面倒すぎです。
でも PowerShell を使えば、そんな面倒なことをせずとも、WCF サービスを思いのままに呼び出せます。

論より証拠と言いますし、まずは実感してみてください。


[ 必要なもの ]
WCF 及び PowerShell を利用するためには、以下の 3 つが必要です。

.NET Framework ( バージョン 3.0 以降であれば OK です。 )
Windows SDK for Windows Vista
Windows PowerShell 1.0 ( 適切なのを選んでください。 )


[ WCF サービス ]
本来は、C# で WCF サービスを実装して IIS ホスティングなりセルフホスティングなりするんですが、この記事ではどうせなので PowerShell 上で作成 & ホスティングします。通信プロトコルには net.tcp プロトコルを使用することとします。また、WSDL を公開するために Http プロトコルも使用します。

まず、PowerShell を管理者権限で起動してください。管理者権限で起動する理由は、ホスティングを行う際に Http プロトコルを利用するためです。管理者権限が無いとエラーになってしまいます。
PowerShell を起動したら、なんも考えず、以下のコードを全てコピーして PowerShell にペーストしてください。

CounterService のホスティング
function Compile-CSCode
{
    param([String[]]$sources, [string[]]$referenceAssemblyNames, [String]$assemblyName)
    trap { break; }
   
    $tempDirectory = "$Env:Temp\PSCompile";
    if (!(Test-Path $tempDirectory))
    {
        New-Item $tempDirectory -ItemType "Directory" | Out-Null;
    }
    Remove-Item "$tempDirectory\*" -Recurse -Force;
   
    $codeProvider = New-Object "Microsoft.CSharp.CSharpCodeProvider";
    $parameters = New-Object "System.CodeDom.Compiler.CompilerParameters";
    $parameters.GenerateExecutable = $False;
    $parameters.GenerateInMemory = $True;
    $parameters.TreatWarningsAsErrors = $False;
    if (($() -ne $assemblyName) -and (0 -lt $assemblyName.Length))
    {
        $parameters.OutputAssembly = "$tempDirectory\$assemblyName";
    }
    if ($() -ne $referenceAssemblyNames)
    {
        $parameters.ReferencedAssemblies.AddRange($referenceAssemblyNames);
    }
   
    $result = $codeProvider.CompileAssemblyFromSource($parameters, $sources);
    if ($result.Errors.Count -ne 0)
    {
        $errorMessage = "コンパイルエラーが発生しました。`n";
        foreach ($err in $result.Errors)
        {
            $errorMessage += "{0}:{1}`n  Row:{2} Column:{3}`n" -f @($err.ErrorNumber, $err.ErrorText, $err.Line, $err.Column);
        }
        throw New-Object "ArgumentException" @($errorMessage);
    }
    return $result.CompiledAssembly;
}

function Compile-CounterService
{
    $iCounterServiceCode =
@"
    using System;
    using System.ServiceModel;
   
    namespace Sample.WCFServices
    {
        [ServiceContract(Namespace = "http://schemas.sample.net/WCFServices", SessionMode = SessionMode.Allowed)]
        public interface ICounterService
        {
            [OperationContract]
            void Access();
   
            [OperationContract]
            void Release();
   
            [OperationContract]
            int GetCount();
        }
    }
"@;
    $counterServiceCode =
@"
    using System;
    using System.ServiceModel;
   
    namespace Sample.WCFServices
    {
        [ServiceBehavior(Namespace = "http://schemas.sample.net/WCFServices", InstanceContextMode = InstanceContextMode.PerSession)]
        public class CounterService : ICounterService
        {
            private int _count;
           
            public CounterService()
            {
                Console.WriteLine("インスタンスが生成されました。");
            }
           
            public void Access()
            {
                Console.WriteLine("Access メソッドが呼び出されました。");
                if (10 < this._count)
                {
                    FaultReasonText overflowReasonText = new FaultReasonText("もう数えられない。。。");
                    FaultReason overflowReason = new FaultReason(overflowReasonText);
                    FaultCode overflowCode = FaultCode.CreateReceiverFaultCode("overflow", "http://schemas.sample.net/WCFServices/Fault");
                    throw new FaultException(overflowReason, overflowCode);
                }
                this._count++;
            }
   
            [OperationBehavior(ReleaseInstanceMode = ReleaseInstanceMode.AfterCall)]
            public void Release()
            {
                Console.WriteLine("Release メソッドが呼び出されました。インスタンスは破棄されます。");
            }
   
            public int GetCount()
            {
                Console.WriteLine("GetCount メソッドが呼び出されました。");
                return this._count;
            }
        }
    }
"@;
    $referenceAssemblyNames =
        @(
            [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel").Location,
            [System.Reflection.Assembly]::LoadWithPartialName("System.Runtime.Serialization").Location
        );
    Compile-CSCode @($iCounterServiceCode, $counterServiceCode) $referenceAssemblyNames | Out-Null;
}

function Execute-CounterServiceHosting
{
    trap
    {
        if ($() -ne $counterServiceHost)
        {
            $counterServiceHost.Abort();
            $counterServiceHost.Close();
        }
        break;
    }
   
    if (("Sample.WCFServices.CounterService" -as [Type]) -eq $())
    {
        Compile-CounterService;
    }
    $counterServiceType = [Sample.WCFServices.CounterService];
    $counterServiceBinding = New-Object "System.ServiceModel.NetTcpBinding" @([System.ServiceModel.SecurityMode]::None);
    $counterServiceBinding.Namespace = "http://schemas.sample.net/WCFServices";
    $counterServiceContractType = [Sample.WCFServices.ICounterService];
    $metadataBehavior = New-Object "System.ServiceModel.Description.ServiceMetadataBehavior";
    $metadataBehavior.HttpGetEnabled = $True;
    $metadataBehavior.HttpGetUrl = "http://localhost/Sample/WCFServices/CounterService.wsdl";
    $counterServiceAddress = [Uri]"net.tcp://localhost:10000/Sample/WCFServices/CounterService.svc";
    $counterServiceHost = New-Object "System.ServiceModel.ServiceHost" @($counterServiceType, $counterServiceAddress);
    $counterServiceHost.AddServiceEndpoint($counterServiceContractType, $counterServiceBinding, [System.String]::Empty) | Out-Null;
    $counterServiceHost.Description.Behaviors.Add($metadataBehavior);
   
    $counterServiceHost.Open();
    "WCF サービス の ホスティングを開始しました。";
    foreach($dispatcher in $counterServiceHost.ChannelDispatchers)
    {
        "Listening uri: $($dispatcher.Listener.Uri)";
    }
    "Enter キーを押すと終了します。";
    Read-Host | Out-Null;
    $counterServiceHost.Close();
}

Execute-CounterServiceHosting;


以下のメッセージが出力されれば成功です。

WCF サービス の ホスティングを開始しました。
Listening uri: net.tcp://localhost:10000/Sample/WCFServices/CounterService.svc
Listening uri: http://localhost/Sample/WCFServices/CounterService.wsdl
Enter キーを押すと終了します。

これで、「CounterService」 という WCF サービスがホスティングされました。
「CounterService」 は、以下の 3 つのオペレーションコントラクトを提供します。

void Access() ・・・ 呼び出すたびにカウントが +1 されます。
int GetCount() ・・・ 現在のカウントを取得します。
void Release() ・・・ サービスインスタンスを解放します。 ( カウントが 0 にリセットされます。 )

Enter キーを押してしまうとホスティングが終了してしまうので、そのまま何もせずに PowerShell 立ち上げておいてください。


[ WCF クライアント ]

ここからが本題の WCF クライアントです。
まず、準備として PowerShell 上でWCF クライアントを扱うための関数 ( 後述 ) を定義します。
既に立ち上がっている PowerShell とは別に、もう一つ PowerShell を起動してください。こちらは管理者権限で起動する必要はありません。
以下のコードを全てコピーし、今起動した方の PowerShell にペーストしてください。

WCF クライアントを扱うための 4 つの関数
function global:New-WCFClientContents
{
    param ([String[]]$metadataPaths, [String]$outputDirectory, [String]$assemblyName = "Client", [string[]]$referenceAssemblyNames)
  
    trap { break; }
    [System.Reflection.Assembly]::LoadWithPartialName("System.Configuration") | Out-Null;
    [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null;
  
    if ("$metadataPaths".Trim().Length -eq 0) { throw "引数 metadataPaths が不正です。"; }
    if ("$outputDirectory".Trim().Length -eq 0) { throw "引数 outputDirectory が不正です。"; }
    if ("$assemblyName".Trim().Length -eq 0) { throw "引数 assemblyName が不正です。"; }
  
    # SvcUtil の実行
    $svcUtilExe = "$Env:ProgramFiles\Microsoft SDKs\Windows\v6.0\Bin\SvcUtil.exe";
    $workDirectory = "$outputDirectory\$([Guid]::NewGuid())"
    $configFileName = "$assemblyName.dll.config";
    &$svcUtilExe /target:"Code" "$metadataPaths" /directory:"$workDirectory" /config:"$configFileName" /noLogo | Out-Null;
    if (!(Test-Path $workDirectory)) { throw "WCF クライアントの作成に失敗しました。;" }
  
    # クライアントコードのコンパイル
    $sourceFiles = Get-ChildItem $workDirectory -Include "*.cs" -recurse;
    $sourcePaths = ($sourceFiles | % { $_.FullName; });
    $codeProvider = New-Object "Microsoft.CSharp.CSharpCodeProvider";
    $parameters = New-Object "System.CodeDom.Compiler.CompilerParameters";
    $parameters.GenerateExecutable = $False;
    $parameters.GenerateInMemory = $False;
    $parameters.OutputAssembly = "$outputDirectory\$assemblyName.dll";
    $referenceAssemblyNames +=
        @(
            [System.Reflection.Assembly]::LoadWithPartialName("System").Location,
            [System.Reflection.Assembly]::LoadWithPartialName("System.Data").Location,
            [System.Reflection.Assembly]::LoadWithPartialName("System.Xml").Location,
            [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel").Location,
            [System.Reflection.Assembly]::LoadWithPartialName("System.IdentityModel").Location,
            [System.Reflection.Assembly]::LoadWithPartialName("System.Runtime.Serialization").Location
        );
    $parameters.ReferencedAssemblies.AddRange($referenceAssemblyNames);
    $compilerResult = $codeProvider.CompileAssemblyFromFile($parameters, $sourcePaths);
    if ($compilerResult.Errors.Count -ne 0)
    {
        $errorMessage = "コンパイルエラーが発生しました。`n";
        foreach ($err in $compilerResult.Errors)
        {
            $errorMessage += "{0}:{1}`n  Row:{2} Column:{3}`n" -f @($err.ErrorNumber, $err.ErrorText, $err.Line, $err.Column);
        }
        throw New-Object "ArgumentException" @($errorMessage);
    }
  
    # コンテンツの配置
    Copy-Item -path "$workDirectory\$configFileName" -Destination "$outputDirectory\$configFileName";
    Remove-Item $workDirectory -Recurse -Force;
  
    # 戻り値の設定
    $assembly = $compilerResult.CompiledAssembly;
    $types = $assembly.GetTypes();
    [Type[]]$clientClasses = $types | ? { ($_.BaseType.IsGenericType) -and ($_.BaseType.GetGenericTypeDefinition() -eq [System.ServiceModel.ClientBase``1]); };
    [Type[]]$serviceContractInterfaces = $types | ? { $_.IsDefined([System.ServiceModel.ServiceContractAttribute], $False); };
    $assemblyPath = "$outputDirectory\$assemblyName.dll";
    $configurationPath = "$outputDirectory\$configFileName";
    $configuration = [System.Configuration.ConfigurationManager]::OpenExeConfiguration($assemblyPath);
    $serviceModelSectionGroup = [System.ServiceModel.Configuration.ServiceModelSectionGroup]::GetSectionGroup($configuration);
    [String[]]$endpointConfigurationNames = ($serviceModelSectionGroup.Client.Endpoints | % { $_.Name });
    $result =
        @{
            Assembly = $assembly;
            ClientClasses = $clientClasses;
            ServiceContractInterfaces = $serviceContractInterfaces;
            AssemblyPath = $assemblyPath;
            ConfigurationPath = $configurationPath;
            EndpointConfigurationNames = $endpointConfigurationNames;
        };
    return $result;
}

function global:New-WCFClient
{
    param ([Type]$clientClass, [String] $configurationPath, [String]$endpointConfigurationName)
  
    trap { break; }
    [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null;
  
    if (($() -eq $clientClass.BaseType) -or (!$clientClass.BaseType.IsGenericType) -or ($clientClass.BaseType.GetGenericTypeDefinition() -ne [System.ServiceModel.ClientBase``1]))
    {
        throw "$clientClass は、System.ServiceModel.ClientBase ジェネリッククラスの派生クラスではありません。";
    }
    if ([String]::IsNullOrEmpty($configurationPath))
    {
        $configurationPath = $clientClass.Assembly.Location + ".config";
    }
    return New-WCFClientOrFactory $clientClass $configurationPath $endpointConfigurationName;
}

function global:New-WCFChannelFactory
{
    param ([Type]$serviceContractInterface, [String] $configurationPath, [String]$endpointConfigurationName)
  
    trap { break; }
    [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null;
   
    if ((!$serviceContractInterface.IsInterface) -or (!$serviceContractInterface.IsDefined([System.ServiceModel.ServiceContractAttribute], $False)))
    {
        throw "$serviceContractInterface は、サービスコントラクトではありません。";
    }
    if ([String]::IsNullOrEmpty($configurationPath))
    {
        $configurationPath = $serviceContractInterface.Assembly.Location + ".config";
    }
    $factoryType = [System.ServiceModel.ChannelFactory``1].MakeGenericType($serviceContractInterface);
    return New-WCFClientOrFactory $factoryType $configurationPath $endpointConfigurationName;
}

function global:New-WCFClientOrFactory
{
    param ([Type]$clientOrFactoryType, [String] $configurationPath, [String]$endpointConfigurationName)
  
    trap { break; }
    [System.Reflection.Assembly]::LoadWithPartialName("System.Configuration") | Out-Null;
    $serviceModelAssembly = [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel");
  
    # コンフィギュレーションの取得
    $clientConfigurationMap = New-Object "System.Configuration.ExeConfigurationFileMap";
    $clientConfigurationMap.ExeConfigFilename = $configurationPath;
    $clientConfiguration = [System.Configuration.ConfigurationManager]::OpenMappedExeConfiguration($clientConfigurationMap, "None");
    $clientServiceModelSectionGroup = [System.ServiceModel.Configuration.ServiceModelSectionGroup]::GetSectionGroup($clientConfiguration);
  
    # エンドポイント要素の取得
    if ($clientServiceModelSectionGroup.Client.Endpoints.Count -eq 0) { throw "コンフィギュレーションにエンドポイント要素が存在しません。"; }
    if ([String]::IsNullOrEmpty($endpointConfigurationName))
    {
        if (1 -lt $clientServiceModelSectionGroup.Client.Endpoints.Count) { throw "コンフィギュレーションにエンドポイント要素が複数存在します。"; }
        $endpointElement = $clientServiceModelSectionGroup.Client.Endpoints[0];
    }
    else
    {
        $endpointElement = $clientServiceModelSectionGroup.Client.Endpoints | ? { $_.Name -eq $endpointConfigurationName; };
        if ($() -eq $endpointElement) { throw "コンフィギュレーションに名前が `"$endpointConfigurationName`" のエンドポイント要素が存在しません。。" }
    }
  
    # エンドポイントアドレスの取得
    $configLoaderType = $serviceModelAssembly.GetType("System.ServiceModel.Description.ConfigLoader", $False, $True);
    $flagsByLoadIdentityMethod = [System.Reflection.BindingFlags]([System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Static);
    $loadIdentityMethod = $configLoaderType.GetMethod("LoadIdentity", $flagsByLoadIdentityMethod);
    &{
        trap [System.Reflection.TargetInvocationException] { throw "コンフィギュレーションのエンドポイント ID の指定が不正です。`n$($_.Exception.InnerException)"; }
        $identity = $loadIdentityMethod.Invoke($(), @($endpointElement.Identity));
    };
    $endpointAddress = New-Object "System.ServiceModel.EndpointAddress" @($endpointElement.Address, $identity, $endpointElement.Headers.Headers);
  
    # バインディング設定の取得
    if ([String]::IsNullOrEmpty($endpointElement.Binding)) { throw "コンフィギュレーションのエンドポイント要素の binding 属性が不正です。"; }
    $bindingSetElement = $clientServiceModelSectionGroup.Bindings[$endpointElement.Binding];
    $bindingType = $bindingSetElement.BindingType;
    $binding = New-Object $bindingType;
    if (![String]::IsNullOrEmpty($endpointElement.BindingConfiguration))
    {
        $bindingConfigurationElement = $bindingSetElement.ConfiguredBindings | ? { $_.Name -eq $endpointElement.BindingConfiguration; };
        if ($() -eq $bindingConfigurationElement) { throw "コンフィギュレーションのエンドポイント要素の bindingConfiguration 属性が不正です。"; }
        $bindingConfigurationElement.ApplyConfiguration($binding)
    }
  
    # クライアントオブジェクトまたはファクトリオブジェクトの生成
    $clientOrFactory = [Activator]::CreateInstance($clientOrFactoryType, @($binding.PSObject.BaseObject, $endpointAddress.PSObject.BaseObject));
    if ($clientOrFactory.Endpoint.Contract.Name -ne $endpointElement.Contract)
    {
        throw "`"$endpointConfigurationName`" のエンドポイント要素はコントラクトが `"$($client.Endpoint.Contract.Name)`" ではありません。";
    }
  
    # エンドポイントビヘイビアの適用
    if (![String]::IsNullOrEmpty($endpointElement.BehaviorConfiguration))
    {
        $endpointBehaviorElement = $clientServiceModelSectionGroup.Behaviors.EndpointBehaviors[$endpointElement.BehaviorConfiguration];
        $flagsByCreateBehaviorMethod = [System.Reflection.BindingFlags]([System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic);
        $createBehaviorMethod = [System.ServiceModel.Configuration.BehaviorExtensionElement].GetMethod("CreateBehavior", $flagsByCreateBehaviorMethod);
        $endpointBehaviors = $endpointBehaviorElement | % { $createBehaviorMethod.Invoke($_, $()); };
        $endpointBehaviors | % { $clientOrFactory.Endpoint.Behaviors.Add($_); };
    }
  
    return $clientOrFactory;
}

function global:Get-OperationContract
{
    param ($target)
  
    trap { break; }
    [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null;
  
    if ($target -isnot [Type])
    {
        if ($() -ne $target.GetType)
        {
            $targetType = $target.GetType();
        }
        else
        {
            $targetType = [Type]"$target";
        }
    }
    else
    {
        $targetType = $target;
    }
  
    $serviceContractAttributeClass = [System.ServiceModel.ServiceContractAttribute];
    if (($targetType.IsInterface) -and ($targetType.IsDefined($serviceContractAttributeClass, $False)))
    {
        $contractInterface = $targetType;
    }
    else
    {
        $contractInterface = $targetType.GetInterfaces() | ? {$_.IsDefined($serviceContractAttributeClass, $False);}
        if ($() -eq $contractInterface) { return; }
    }
  
    $operationContractAttributeClass = [System.ServiceModel.OperationContractAttribute];
    $operationContractMethods = $contractInterface.GetMethods() | ? { $_.IsDefined($operationContractAttributeClass, $False); };
    if ($() -ne $operationContractMethods)
    {
        $operationContractTexts = $operationContractMethods | % { $_.ToString() };
    }
    return $operationContractTexts;
}



これでクライアント側の準備は完了です。

ここからは、最初に起動した方の PowerShell でホスティングされている 「CounterService」 を利用するサンプルコードになります。
まず、以下のコマンドを実行すると、New-WCFClientContents 関数によって、( 「CounterService」 が公開している WSDL を元に ) クライアントクラスがコンパイルされて "C:\work\wcf" というフォルダ内に作成されます。また、コンフィギュレーションも一緒に作成されます。また、この関数の戻り値から、作成されたコンテンツの情報も取得できます。

CounterService のクライアントコンテンツを作成
$clientContents = New-WCFClientContents "http://localhost/Sample/WCFServices/CounterService.wsdl" "C:\work\wcf" "CounterServiceClient";



以下のコマンドを実行すると、今コンパイルされた クライアントクラスのインスタンスが、 New-WCFClient 関数によって、 PowerShell 上に生成されます。

CounterService のクライアントのインスタンス生成
$client = New-WCFClient $clientContents.ClientClasses[0] $clientContents.ConfigurationPath $clientContents.EndpointConfigurationNames[0];


これで、$client 変数を使用して WCF サービスを利用することができるようになりました。
以下のコマンドを実行すれば、「CounterService」 を利用できていることがわかります。

CounterService にアクセス
$client.Open();

$client.GetCount();
$client.Access();
$client.GetCount();
$client.Release();
$client.GetCount();

$client.Close();




New-WCFChannelFactory 関数を使用すれば、サービスコントラクトインターフェイスから ChannelFactory<T> クラスを生成することもできます。

CounterService 用のチャネルファクトリの生成
$channelFactory = New-WCFChannelFactory ([ICounterService]) "C:\work\wcf\CounterServiceClient.dll.config";


チャネルファクトリにてオープン・クローズ及びクライアントの生成を行えば、先ほどのクライアントオブジェクトと同様に WCF サービスが利用できます。

チャネルファクトリの使用
$channelFactory.Open();
$client = $channelFactory.CreateChannel();

$client.GetCount();
$client.Access();
$client.GetCount();
$client.Release();
$client.GetCount();

$channelFactory.Close();




[ 使用した関数について ]
先ほど、クライアント側の「準備」として、PowerShell 上でWCF クライアントを扱うための関数を定義しました。そして、その関数を使用して WCF クライアントの作成等を行ってきました。
これらの関数は、私が自作した関数で、こちらの記事で紹介しています。詳細な説明を載せていますので、ご覧ください。
また、私が自作した全ての関数 ( WCF 関連以外のものも含む ) は、こちらの記事からダウンロードすることができます。

今回は、説明を簡単にするために、「準備」にコピペという手段を取りましたが、PowerShell の「プロファイル」という機能 ( ここここを参照 ) を利用すれば、一々コピペする必要もなくなります。つまり、最初から New-WCFClientContents 関数や New-Client 関数を使用できるわけです。

PowerShell を使えば、Visual Studio を立ち上げる必要もビルドする必要もありません。PowerShell にやりたいことを記述して Enter を押すだけで即実行されます。WCF 開発には是非 PowerShell を活用してください。
タグ: .NET C# PowerShell WCF