C#と諸々

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

2008/02/04 00:54

スクリプトコンテスト (応募締め切り:2008/02/10)

先ほど提出しました。
応募方法の Step1 から Step2 までの日数がかなり開いちゃったけど大丈夫かな。。。
タグ: .NET PowerShell
2008/01/08 23:16
Windows PowerShell スクリプトコンテストが開催中だそうです。
応募締め切りは 2008/02/10 です。

僕も参加しようと思ってます^^
応募は一人一回までって書いてあるけど、一度に複数スクリプトをまとめて送るってのもナシってことかな?


[ 情報元 ]
Shigeya Tanabe's blog : Windows PowerShell キャンペーン・スクリプトコンテスト開催中!


タグ: .NET PowerShell
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/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
2007/11/27 21:43
ジェネリック型定義

型名の後に "`" をくっつけて ( エスケープ文字だから 2 回書く ) ジェネリックパラメータの数を書く。

$t0 = [System.Collections.Generic.Dictionary``2];



構築ジェネリック型 ( 1 )

通常、ジェネリック引数は "[アセンブリ修飾名]" という形式で指定する。複数指定する場合は ", " で区切る。ジェネリック引数全体を "[" と "]" で囲む。

$t1 = [System.Collections.Generic.Dictionary``2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089], [System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]];



構築ジェネリック型 ( 2 )

ジェネリック引数に指定する型のアセンブリが、ジェネリック型と同一のアセンブリの場合は、アセンブリ修飾名でなく、完全名でも可。

$t2 = [System.Collections.Generic.Dictionary``2[[System.String], [System.Int32]]];



構築ジェネリック型 ( 3 )

ジェネリック引数に指定する型のアセンブリが、ジェネリック型と同一のアセンブリではなく、更に GAC に登録されていない場合、記述が不可能。
代わりに、Type.MakeGenericType メソッドを使用して構築ジェネリック型を生成する。 ( 下の例では、コピペで簡単に実行できるように、String と Int32 を指定している。 )

$t3 = [System.Collections.Generic.Dictionary``2].MakeGenericType(@([System.String], [System.Int32]));


また、New-Object コマンドレットによるインスタンス生成も不可能。
代わりに、Activator.CreateInstance メソッド等を使用してインスタンスを生成する。

$d0 = [Activator]::CreateInstance($t3);

コンストラクタに渡す引数を指定してActivator.CreateInstance メソッドを使用する場合、コンストラクタに渡す引数が PSObject 型になっていないか注意する必要がある。例えば、New-Object で生成したオブジェクトは PSObject 型である。PSObject 型から生 ( ? ) のオブジェクトを取得するには、PSObject プロパティを参照し、更に BaseObject パラメータを参照する。


$i = [PSObject]100;
$d1 = [Activator]::CreateInstance($t3, @($i.PSObject.BaseObject));
# 下のコードではエラーとなる。
$d2 = [Activator]::CreateInstance($t3, @($i));
2007/11/25 16:46
PowerShell 関数の最終更新日:2008/08/24

自作の PowerShell 関数をまとめました。新しい関数を作成するたびに、これに追加していきます。
PowerShell 上で表示できるヘルプファイルを、各関数毎に用意してありますので、けっこう便利になっていると思います。
例えば、PowerShell 上で "Help New-WCFClient" と実行すれば、New-WCFClient 関数のヘルプが表示されます。

[ ダウンロード ]
YokoKen.PowerShell.Scripts ( YKPSScripts.zip )


[ 使用方法 ]
使用方法は、付属の ReadMe.txt を参照してください。


[ 関数一覧 ]
全部で 19 種類の関数が含まれています。関数名をクリックすると、詳細記事に移動します。

Build-SandcastleProject ( 作成日:2007/07/31 )
Sandcastle Help File Builder のプロジェクト ( 拡張子 .shfb ) をビルドします。

closure ( 作成日:2008/06/09 )
スクリプトブロックにクロージャ機能を付加します。

Compile-CSCode ( 作成日:2007/11/10 )
C# のソースコードをメモリ上にコンパイルします。

Compile-VBCode ( 作成日:2007/11/10 )
Visual Basic のソースコードをメモリ上にコンパイルします。

Create-ZipArchive ( 作成日:2007/10/21 )
指定したフォルダまたはファイルを Zip 形式で圧縮します。

Delete-VssRelation ( 作成日:2007/07/30 )
Visual Studio のソリューションから Visual SourceSafe の関連付けを削除します。

Edit-PSScript ( 作成日:2007/11/05 )
PowerShell スクリプトファイルを PowerGUI Script Editor で編集します。

Get-LatestSource ( 作成日:2007/07/25 )
Visual SourceSafe から最新のソースを取得します。

Get-OperationContract ( 作成日:2007/11/18 )
クライアントオブジェクト、またはクライアントクラス、またはサービスコントラクトから、オペレーションコントラクトの一覧を取得します。

Get-Parameter ( 作成日:2008/06/08 )
スクリプトブロックのパラメータ情報を取得します。

Invoke-NUnit ( 作成日:2008/04/03  )
NUnit を使用して記述されたユニットテストを実行します。

Invoke-Process ( 作成日:2008/04/03 )
プロセスを実行します。

New-WCFChannelFactory ( 作成日:2007/11/18 )
指定したサービスコントラクトのためのチャネルファクトリのインスタンスを生成します。

New-WCFClient ( 作成日:2007/11/18 )
SvcUtil.exe が生成したクライアントクラス ( ClientBase ジェネリッククラスの派生クラス ) をインスタンス化します。

New-WCFClientContent ( 作成日:2007/11/18 )
WSDL を元に、クライアントコンテンツを生成します。

New-WCFClientOrFactory ( 作成日:2007/11/18 )
New-WCFClient 関数、New-WCFChannelFactory 関数が内部で使用する関数です。
つまり、New-WCFClient 関数、New-WCFChannelFactory 関数を使用するにはこの関数が必要となりますが、この関数を利用者が直接呼び出すことはありません。

Rebuild-Solution ( 作成日:2007/07/26 )
Visual Studio 2005 のソリューションをリビルドします。

Set-MessageInspector ( 作成日:2007/12/09 )
WCF クライアントが要求メッセージを送信する前、応答メッセージを受信した後の 2 箇所に独自の処理を追加します。

try ( 作成日:2007/11/07 )
C# の try - catch - finally のような形式で、構造化例外処理を行うことが可能になります。
2007/11/21 22:24
プロファイル内に関数を直書きしてると、だんだん行数がかさんできて、メンテナンスがしづらくなってきます。
そこで僕は、関数は別のスクリプトファイル ( 1 関数につき 1 ファイル ) に記述しています。プロファイルでは、それらのスクリプトファイルを実行することで関数をインポートしています。

以下はプロファイルに記述しているコードです。

Microsoft.PowerShell_profile.ps1
Set-Variable -Scope "Global" -Option "Constant" -Name "GlobalScripts" -Value "$PSHome\Scripts";
Set-Variable -Scope "Global" -Option "Constant" -Name "Scripts" -Value ("{0}\WindowsPowerShell\Scripts" -f [Environment]::GetFolderPath("MyDocuments"));


function Execute-Scripts
{
    param ([String]$scriptsFolderPath)
   
    if (Test-Path $scriptsFolderPath)
    {
        Get-ChildItem $scriptsFolderPath -Include "*.ps1" -Recurse -Force | % { &($_.FullName); };
    }
}

Execute-Scripts $GlobalScripts;
Execute-Scripts $Scripts;


あとは、PowerShell のインストールフォルダ ( もしくは My Documents フォルダ内の WindowsPowerShell フォルダ ) 内に "Scripts" というフォルダを作成して、その中にスクリプトファイルを置いておけば、関数が自動でインポートされます。

なお、ファイルの中に書く関数は、function ブロックもちゃんと記述する必要があります。また、スコープを global に指定する必要があります。
例えば、"Hoge" と出力する Hoge 関数ならば以下のようになります。

Hoge.ps1
function global:Hoge
{
    return "Hoge";
}



関数をプロファイル内にいくつも書いている人には、かなりお勧めです。是非試してみてください。 (  既にやっている人もいそうだけど )


[ 関連記事 ]

Windows PowerShell プロファイル
タグ: .NET PowerShell
2007/11/20 01:01
実は最近まで全く使っていませんでした。
これは使わなきゃ損です。僕は損してました ^^;


プロファイルは、PowerShell の起動時に実行されるスクリプトファイルです。スナップイン、関数、変数、エイリアスの追加などに使用できます。
プロファイルは 4 種類あります。この辺の説明はこの記事では省略します。PowerShell 付属のドキュメント「Windows PowerShell™ ファースト ステップ ガイド」を参照してください。

プロファイルはスクリプトファイルですので、実行ポリシーの設定が絡んできます。実行ポリシーの既定の設定では、スクリプトファイルの実行はできません。プロファイルやそれ以外のスクリプトファイルを利用する場合は、実行ポリシーの設定を変更する必要があります。

実行ポリシーの変更は Set-ExecutionPolicy コマンドレットで行います。また、Get-ExecutionPolicy コマンドレットにて実行ポリシーの現在の設定を確認できます。
実行ポリシーに設定できる値は以下の 4 種類です。

Restricted
スクリプトファイルの実行を許可しません。

AllSigned
全てのスクリプトファイルは信頼された発行元によって署名されている必要があります。

RemoteSigned
インターネットからダウンロードしたスクリプトファイルは、信頼された発行元によって署名されている必要があります。

Unrestricted
全てのスクリプトファイルの実行を許可します。


実行ポリシーの設定変更はセキュリティの低下を意味します。上記 4 項目では、下に行くほどセキュリティが低くなっています。
PowerShell はとても強力です。もし、悪意のあるスクリプトを実行してしまった場合、大変なことになるかもしれません。実行ポリシーを変更する場合、そのことを充分承知してください。

AllSigned に設定した場合、自分で作成したスクリプトファイルにも署名を行う必要があります。また、署名後にスクリプトファイルを書き換えた場合、そのファイルに対して署名をしなおす必要があります。ちょっと面倒ではありますが、証明書が他者の手に渡らない限りは改ざんを防止できるという大きなメリットが得られます。

RemoteSigned に設定した場合、インターネットからダウンロードしたスクリプトファイル以外は、署名なしで実行できます。ただし、改ざんも検出しません。お手軽になりますが、インターネットからダウンロードしたスクリプトファイル以外には無防備です。

Unrestricted に設定した場合、全てのスクリプトファイルを署名なしで実行できます。さすがにこの設定は避けた方がいいと思います。


PowerShell にて以下のコマンドを実行すると、詳しい解説を読むことができます。実行ポリシーの変更方法や署名方法も載っています。

Help about_signing
タグ: .NET PowerShell