型名の後に "`" をくっつけて ( エスケープ文字だから 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));
IDispatchMessageInspector インターフェイス (System.ServiceModel.Dispatcher) が WCF サービス側、IClientMessageInspector インターフェイス (System.ServiceModel.Dispatcher) が WCF クライアント側。
で、メッセージは Message クラス (System.ServiceModel.Channels) として取得できると。
・・・まだ何も試してないけどw
これを利用して、SOAP メッセージを取得する PowerShell 関数が作れそう (・∀・)ニヤニヤ
自作の 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 のような形式で、構造化例外処理を行うことが可能になります。
Speedtest - how fast are you?
やってみた。

タイピングもそんなに得意じゃない上に英語もダメ。これ以上得点上がらなさそう。
仕方が無いので、プログラマらしいやり方で高得点を目指すことにした。
結果画面でアドレスバーに
javascript:(function() { function f(d, t){if (d.innerText != undefined) { d.innerText = t; } else { d.textContent = t; } }; var v = document.getElementById("auswertung").childNodes; f(v[1], "999 points"); f(v[3], "position 0"); f(v[9], "999 characters per minute"); f(v[12], "999 correct"); f(v[16], "0 wrong"); })();
って入力して Enter !

すごい、全然嬉しくない。
そこで僕は、関数は別のスクリプトファイル ( 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 プロファイル
これは使わなきゃ損です。僕は損してました ^^;
プロファイルは、PowerShell の起動時に実行されるスクリプトファイルです。スナップイン、関数、変数、エイリアスの追加などに使用できます。
プロファイルは 4 種類あります。この辺の説明はこの記事では省略します。PowerShell 付属のドキュメント「Windows PowerShell™ ファースト ステップ ガイド」を参照してください。
プロファイルはスクリプトファイルですので、実行ポリシーの設定が絡んできます。実行ポリシーの既定の設定では、スクリプトファイルの実行はできません。プロファイルやそれ以外のスクリプトファイルを利用する場合は、実行ポリシーの設定を変更する必要があります。
実行ポリシーの変更は Set-ExecutionPolicy コマンドレットで行います。また、Get-ExecutionPolicy コマンドレットにて実行ポリシーの現在の設定を確認できます。
実行ポリシーに設定できる値は以下の 4 種類です。
Restricted
スクリプトファイルの実行を許可しません。
AllSigned
全てのスクリプトファイルは信頼された発行元によって署名されている必要があります。
RemoteSigned
インターネットからダウンロードしたスクリプトファイルは、信頼された発行元によって署名されている必要があります。
Unrestricted
全てのスクリプトファイルの実行を許可します。
実行ポリシーの設定変更はセキュリティの低下を意味します。上記 4 項目では、下に行くほどセキュリティが低くなっています。
PowerShell はとても強力です。もし、悪意のあるスクリプトを実行してしまった場合、大変なことになるかもしれません。実行ポリシーを変更する場合、そのことを充分承知してください。
AllSigned に設定した場合、自分で作成したスクリプトファイルにも署名を行う必要があります。また、署名後にスクリプトファイルを書き換えた場合、そのファイルに対して署名をしなおす必要があります。ちょっと面倒ではありますが、証明書が他者の手に渡らない限りは改ざんを防止できるという大きなメリットが得られます。
RemoteSigned に設定した場合、インターネットからダウンロードしたスクリプトファイル以外は、署名なしで実行できます。ただし、改ざんも検出しません。お手軽になりますが、インターネットからダウンロードしたスクリプトファイル以外には無防備です。
Unrestricted に設定した場合、全てのスクリプトファイルを署名なしで実行できます。さすがにこの設定は避けた方がいいと思います。
PowerShell にて以下のコマンドを実行すると、詳しい解説を読むことができます。実行ポリシーの変更方法や署名方法も載っています。
Help about_signing
そこで、そんなことをせずとも任意のコンフィギュレーションを使用して WCF クライアントを簡単に利用するための関数を作成しました。また、他にも WCF クライアントを利用する上で役立つ関数を作成しました。
作成した関数は全部で 4 つです。
New-WCFClientContents 関数
WSDL を元に、クライアントコンテンツを生成します。クライアントコンテンツには、WCF クライアントを含むアセンブリと WCF クライアントのコンフィギュレーションが含まれます。
内部的には、SvcUtil.exe を使用してコードを生成し、その後コンパイルを行っています。
New-WCFClient 関数
SvcUtil.exe が生成したクライアントクラス ( ClientBase ジェネリッククラスの派生クラス ) をインスタンス化します。エンドポイントやバインディングの設定には任意のコンフィギュレーションを指定できます。
New-WCFChannelFactory 関数
指定したサービスコントラクトのためのチャネルファクトリのインスタンスを生成します。エンドポイントやバインディングの設定には任意のコンフィギュレーションを指定できます。
この関数は主に、SvcUtil.exe を使用せず ChannelFactory ジェネリック クラスを使用して WCF クライアントを利用したい、という時に役立ちます。
Get-OperationContract 関数
クライアントオブジェクト、またはクライアントクラス、またはサービスコントラクトから、オペレーションコントラクトの一覧 ( String 配列 ) を取得します。
基本は、New-WCFClientContents 関数でクライアントコンテンツを作成し、New-WCFClient 関数でクライアントオブジェクトを生成、その後オペレーションコントラクトを呼び出したり、Get-OperationContract でどんなオペレーションコントラクトがあるかを確認したり、といった使い方になります。
しかし、それぞれの関数は独立しています。例えば、New-WCFClientContents 関数を使わずに生成したクライアントコンテンツに対しても、New-WCFClient 関数を利用することができます。
なお、New-WCFClient 関数と New-WCFChannelFactory 関数は、両方とも、後述の New-WCFClientOrFactory 関数を内部で使用しています。
【 New-WCFClientContents 関数 】
WSDL を元に、クライアントコンテンツを生成します。クライアントコンテンツには、WCF クライアントを含むアセンブリと WCF クライアントのコンフィギュレーションが含まれます。
内部的には、SvcUtil.exe を使用してコードを生成し、その後コンパイルを行っています。
[ パラメータ ]
・metadataPaths
WSDL の URL またはローカルパスを指定します。ローカルパスで指定する場合、WSDL 以外のメタデータドキュメントのパスも全て指定する必要があります。
・outputDirectory
コンテンツの出力先ディレクトリのパスを指定します。
・assemblyName
生成されるアセンブリ名 ( 拡張子は含めない ) を指定します。
省略した場合は、"Client" という名前が付けられます。
・referenceAssemblyNames
アセンブリの参照設定を指定します。
基本的なアセンブリは既定で追加されますので、通常このパラメータは省略します。
[ 戻り値 ]
作成されたコンテンツに関する情報を格納するハッシュテーブルを返します。各キーの説明を次に記述します。
・Assembly
アセンブリオブジェクト。
・ClientClasses
WCF クライアントクラスの配列。
・ServiceContractInterfaces
サービスコントラクトインターフェイスの配列。
・AssemblyPath
アセンブリのローカルパス。
・ConfigurationPath
コンフィギュレーションのローカルパス。
・EndpointConfigurationNames
コンフィギュレーション内に存在するエンドポイント設定の名前の配列。
[ コード ]
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;
}
【 New-WCFClient 関数 】
SvcUtil.exe が生成したクライアントクラス ( ClientBase ジェネリッククラスの派生クラス ) をインスタンス化します。エンドポイントやバインディングの設定には任意のコンフィギュレーションを指定できます。
[ パラメータ ]
・clientClass
SvcUtil.exe が生成したクライアントクラス ( ClientBase ジェネリッククラスの派生クラス ) を指定します。
・configurationPath
コンフィギュレーションのローカルパスを指定します。
省略した場合、引数 clientClass が定義されているアセンブリに対応付けられているコンフィギュレーションが使用されます。例えば、引数 clientClass が定義されているアセンブリが "Client.dll" の場合、 そのアセンブリと同じディレクトリ内にある "Client.dll.config" が使用されます。
・endpointConfigurationName
コンフィギュレーション内のエンドポイント要素を示す名前を指定します。
コンフィギュレーション内にエンドポイント要素が一つしかない場合はこのパラメータを省略することができます。
[ 戻り値 ]
クライアントクラスのインスタンスを返します。
[ コード ]
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;
}
※この関数は、後述の New-WCFClientOrFactory 関数を内部で使用しています。
【 New-WCFChannelFactory 関数 】
指定したサービスコントラクトのためのチャネルファクトリのインスタンスを生成します。エンドポイントやバインディングの設定には任意のコンフィギュレーションを指定できます。
この関数は主に、SvcUtil.exe を使用せず ChannelFactory ジェネリック クラスを使用して WCF クライアントを利用したい、という時に役立ちます。
[ パラメータ ]
・serviceContractInterface
サービスコントラクトインターフェイスを指定します。
・configurationPath
コンフィギュレーションのローカルパスを指定します。
省略した場合、引数 clientClass が定義されているアセンブリに対応付けられているコンフィギュレーションが使用されます。例えば、引数 clientClass が定義されているアセンブリが "Client.dll" の場合、 そのアセンブリと同じディレクトリ内にある "Client.dll.config" が使用されます。
・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;
}
※この関数は、後述の New-WCFClientOrFactory 関数を内部で使用しています。
【 New-WCFClientOrFactory 関数 】
New-WCFClient 関数、New-WCFChannelFactory 関数が内部で使用する関数です。つまり、New-WCFClient 関数、New-WCFChannelFactory 関数を使用するにはこの関数が必要となりますが、この関数を利用者が直接呼び出すことはありません。
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;
}
【 Get-OperationContract 関数 】
クライアントオブジェクト、またはクライアントクラス、またはサービスコントラクトから、オペレーションコントラクトの一覧を取得します。
[ パラメータ ]
・target
クライアントオブジェクト、またはクライアントクラス、またはサービスコントラクトを指定します。
[ 戻り値 ]
オペレーションコントラクトの一覧を返します。
[ コード ]
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;
}
【 関連記事 】
WCF サービス開発には PowerShell を活用しよう
【 ダウンロード 】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。
YokoKen.PowerShell.Scripts
PowerGUI 及び PowerGUI Script Editor は、C# の region ディレクティブによるアウトライン機能と同じ機能を備えています。
つまり、
#region function Hoge
function Hoge
{
return "Hoge";
}
#endregion
って書けば
function Hoge
って感じに折りたたむことができます。
あと、単に { と } によるブロックにもアウトライン機能が働きます。
PowerShell の例外処理
PowerShell で try - catch - finally を実現
以前、この 2 つの記事で PowerShell の例外処理に関することを書きました。
で、一つ重大なことを見落としていました。Write-Error コマンドレットの存在です。
実は、Write-Error コマンドレットで出力した例外は trap ブロックで捕捉することができないようです。だから、try - catch -finally を実現する関数も、Write-Error コマンドレットの前では無力です。
ErrorActionPreference 変数 ( あるいは -errorAction 共通パラメータ ) の設定は反映されます。というより、trap ブロックが無力であるため完全にこれに従って動作します。
ErrorActionPreference 変数に "Stop" が設定されている場合、 Write-Error コマンドレットによって発生した例外を trap ステートメントにて捕捉でき ( るように見え ) ます。でもこれは、厳密には Write-Error コマンドレットによって発生した例外ではありません。ActionPreferenceStopException という別の例外です。残念ながら、この例外から本来発生した例外を取得することはできないようです。
Write-Error コマンドレットのヘルプを見ると、 「オブジェクトをエラー パイプラインに書き込みます。」 と書いてあります。
私なりに調べてみたところ、この記述は半分正しく半分間違っています。
Write-Error コマンドレットに限らず、 例外に ErrorActionPreference 変数の "Continue" が適用されると、最終的には例外が通知されるもののそれ以降の処理が続行されます。これを実現するための仕組みがエラーパイプラインです。
先ほど書いたように、Write-Error コマンドレットは ErrorActionPreference 変数の設定に完全に従います。Write-Error コマンドレットによって発生した例外がエラーパイプライン渡されるのは、あくまでも ErrorActionPreference 変数が "Continue" に設定されている場合のみです。
( なお、コマンドレット内で発生した例外の一部も、Write-Error と同様の動作をします。具体的には、エラー通知の実装が、例外のスローではなく Cmdlet クラスの WriteError メソッドを使用している場合です。 )
エラーパイプラインは、エラー情報を蓄積しながら処理を続行させるための仕組みです。ここからは、エラーパイプラインについて見て行きましょう。
エラーパイプラインは通常のパイプラインとは別モノです。例えば以下のコードを実行すると、"Hoge" だけがパイプラインで渡されていることが確認できます。 ( $ErrorActionPreference 変数は "Continue" ( 既定 ) で試してください。 )
&{ Write-Error "Error1"; "Hoge1"; } | %{ "$_ がパイプラインで渡されました。"; };
出力は以下の通りです。
Write-Error "Error1"; "Hoge1"; : Error1
発生場所 行:1 文字:2
+ &{ <<<< Write-Error "Error1"; "Hoge1"; } | %{ "$_ がパイプラインで渡されました。"; };
Hoge1 がパイプラインで渡されました。
Error1 の情報が出力された後、 "Hoge1 がパイプラインで渡されました。" と出力されていることから、エラーパイプラインと通常のパイプラインが別モノだということがわかります。
では、以下のコードを実行するとどうなるでしょうか。
&{ Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; } | %{ "$_ がパイプラインで渡されました。"; };
このコードを実行すると、以下のような出力が得られます。
Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; : Error1
発生場所 行:1 文字:2
+ &{ <<<< Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; } | %{ "$_ がパイプラインで渡されました。"; };
Hoge1 がパイプラインで渡されました。
Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; : Error2
発生場所 行:1 文字:2
+ &{ <<<< Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; } | %{ "$_ がパイプラインで渡されました。"; };
Hoge2 がパイプラインで渡されました。
どうも、エラーパイプラインと通常のパイプラインは別モノではあるものの、一連の順序は保持されているようです。
もしかすると、エラーパイプラインを渡っている例外もパイプラインを渡っているオブジェクトも、どこかで識別用のマークが付けられているだけで、一つのパイプラインを渡っているのかもしれません。まぁ、実際どうなっているのかまでは私にはわかりません。
エラーパイプラインにある例外を通常のパイプラインに移動させることもできます。それには、2>&1 という演算子を使います。
以下のコードを実行すると、エラーパイプラインにある例外が通常のパイプラインに移動することが確認できます。
(&{ Write-Error "Error1"; "Hoge1"; Write-Error "Error2"; "Hoge2"; } | %{ "$_ ok1"; }) 2>&1 | %{ "$_ ok2";};
出力は以下のようになります。
Error1 ok2
Hoge1 ok1 ok2
Error2 ok2
Hoge2 ok1 ok2
Error1 と Error2 には "ok2" だけが付加されています。つまり、2>&1 演算子によって通常のパイプラインに移動されたわけです。
ここでは結果をわかりやすくするために例外を文字列に変換しましたので、例外情報が赤文字で書き出されてはいません。通常のパイプラインに移動されても、文字列に変換したりせず例外がそのまま出力された場合は、赤文字で出力されます。
( 余談ですが、PS オブジェクトに対して、Add-Member コマンドレットを使って "writeErrorStream" というノートプロパティを追加し値を true に設定すると、そのオブジェクトの出力時に出力が赤文字になります。例外が赤文字で表示されるのはこの為です。 )
エラーパイプラインはこんなところでしょうか。ちなみに通常のパイプラインからエラーパイプラインに移動させる 1>&2 演算子というものもあるようですが、使おうとすると「まだサポートされていない」と言われます。次期バージョンである PowerShell 2.0 ではサポートされるのかもしれません。
長さは 64 文字です
1..100|%{@("Fizz")[$_%3]+@("Buzz")[$_%5]+@($_)[!($_%3-and$_%5)]}
# 追記(2007/11/13)
57 文字まで短縮しました。
1..100|%{$s=@("Fizz")[$_%3]+@("Buzz")[$_%5];($s,$_)[!$s]}
# 追記ここまで
ネタ元 : FizzBuzz 問題 を PowerShell で ( 囚人のジレンマな日々 )
で、この関数、実は一つ欠点がある。生成されたアセンブリは現在のアプリケーションドメインにロードされるため即座に利用が可能なわけだが、アプリケーションドメインにロードされたアセンブリはアプリケーションドメインがアンロードされない限りアンロードできない。つまり、コンパイルするたびにアセンブリがロードされメモリを消費していくことになる。
アセンブリの動的なロードは、アプリケーションのプラグイン機能で良く使われる。その際、この問題を回避する方法として用いられるのが、プラグイン用のアプリケーションドメインを別に用意するという方法。異なるアプリケーションドメイン間で通信を行うために、プラグインクラスは MarshalByRefObject クラス (System) を継承する。そうすれば、AppDomain.CreateInstanceAndUnwrap メソッドで生成したプロキシを通して通信が行える。
前回の記事を書いた時は、この問題を考慮したバージョンの関数を次のステップとして作ろうと思ってた。でも、一々 MarshalByRefObject を継承しないといけなくなるし、シリアライズ不可能なオブジェクトは扱えなくなる。これはさすがに PowerShell での実用性がないんじゃないか。おまけにAppDomain.CreateInstanceAndUnwrap メソッドで生成したプロキシが PowerShell から正常に操作できないみたいだし。
そこまでせずとも、今の関数を注意して使えばいいじゃないか。ということで、やめた。
ちなみに、この関数は元々、PowerShell のリリース前 ( 僕がPowerShell を知る前 ) に、コンソール上から C# のコードを動的に実行できるアプリを C# で作ろうとした時に書いたコードの一部を使いまわしたもの。
といっても、そのアプリは CSharpCodeProvider クラスを利用しても実現できないと気が付き ( 一応、単純なコードなら実行できた ) 、諦めたんだけど・・・。
たぶん、System.Reflection.Emit 名前空間のクラスを利用して 構文解釈 → MSIL に変換 という処理を実装しなきゃダメ。PowerShell や IronPython を作るのに等しい。
あと、PowerShell 上で C# のコードをコンパイルして使うという試み、すでにやっている方がいたことが判明。
僕の場合、「定義する」ことに重点を置いていたが、こちらは「実行する」ことに重点を置いている。別アプリケーションドメインでの実行も考慮されている。
炎の作品 ― flamework.net ―: PowerShell で C# の実行
炎の作品 ― flamework.net ―: メモリリークのない C# の実行
アセンブリ名を省略した場合、ランダムな文字列が設定される。
当然、コンパイルされたクラスは PowerShell から即利用可能。
# 追記
(2007/11/10)
アセンブリ名を指定する場合は、アセンブリがファイルとして "%windir%\System32 " フォルダ内に保存されてしまう模様。そのため、一時ディレクトリに保存するよう修正。
(2007/11/11)
一時ディレクトリの後処理を忘れてたので追加。
# 追記ここまで
Compile-CSCode 関数
function global: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;
}
使用例
$HogeCode =
@"
namespace Sample
{
public class Hoge
{
public string Hello()
{
return "Hello World !";
}
}
}
"@;
$FugaCode =
@"
namespace Sample
{
public class Fuga
{
public string GoodBye()
{
return "Goodbye World !";
}
}
}
"@;
$sources = @($HogeCode, $FugaCode);
$sampleAssembly = Compile-CSCode $sources;
$h = New-Object "Sample.Hoge";
$f = New-Object "Sample.Fuga";
$h.Hello();
$f.Goodbye();
続く・・・?
C# コードを PowerShell 上で使用する関数の注意点
【 ダウンロード 】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。
YokoKen.PowerShell.Scripts
今回は、以下のように C# の try - catch - finally に近い書き方ができます。
try {
# 処理
} catch ([例外の型]) {
param($ex)
# 例外処理
} finally {
# 後処理
}
各ブロックの開始の "{" の前と各ブロックの終了の "}" の後ろは、上記のように、改行せずに記述する必要があります。また例外の型は、上記のように、必ず "()" で囲む必要があります。
catch または finally は、省略可能です。例外の型も省略可能です。
# 追記 ( 2007/11/12 )
catch ブロック内では、break ステートメントを使用して例外を再スローすることができます。当然、任意の例外を throw ステートメントでスローすることもできます。
# 追記ここまで
try {
# 処理
} catch {
# 例外処理
}
で、これらを実現するための関数がこちらです。
function global:try
{
$currentArgIndex = 0;
$tryBlock = $args[$currentArgIndex];
$currentArgIndex++;
if ($tryBlock -isnot [System.Management.Automation.ScriptBlock])
{
throw New-Object "ArgumentException" @("try ブロックの指定が不正です。");
}
if ("catch" -eq $args[$currentArgIndex])
{
$currentArgIndex++;
if ($args[$currentArgIndex] -is [Type])
{
$targetExceptionType = $args[$currentArgIndex];
$currentArgIndex++;
}
$catchBlock = $args[$currentArgIndex];
$currentArgIndex++;
if ($catchBlock -isnot [System.Management.Automation.ScriptBlock])
{
throw New-Object "ArgumentException" @("catch ブロックの指定が不正です。");
}
}
if ("finally" -eq $args[$currentArgIndex])
{
$currentArgIndex++;
$finallyBlock = $args[$currentArgIndex];
$currentArgIndex++;
if ($finallyBlock -isnot [System.Management.Automation.ScriptBlock])
{
throw New-Object "ArgumentException" @("finally ブロックの指定が不正です。");;
}
}
if (($() -eq $catchBlock) -and ($() -eq $finallyBlock))
{
throw New-Object "ArgumentException" @("catch ブロックまたは finally ブロックを指定してください。");
}
&{
$requireFinally = ($() -ne $finallyBlock);
&{
&$tryBlock;
trap
{
if ($() -eq $catchBlock)
{
break;
}
$ex = $_.Exception;
if (($() -ne $targetExceptionType) -and (!$targetExceptionType.IsAssignableFrom($ex.GetType())))
{
break;
}
&$catchBlock $ex;
continue;
}
};
if ($requireFinally)
{
$requireFinally = $False;
&$finallyBlock;
}
trap
{
if ($requireFinally)
{
$requireFinally = $False;
&$finallyBlock;
}
break;
}
};
}
以下のスクリプトを実行すると、動作が確認できます。
try {
"try ブロック実行";
throw New-Object "ArgumentException";
"この文は出力されない";
} catch ([ArgumentException]) {
param ($ex)
"{0} がスローされたから catch ブロック実行" -f $ex.GetType().Name;
} finally {
"finally ブロック実行";
}
出力は以下のようになります。
try ブロック実行
ArgumentException がスローされたから catch ブロック実行
finally ブロック実行
【 ダウンロード 】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。
YokoKen.PowerShell.Scripts
PowerGUI ( 囚人のジレンマな日々 )
PowerGUI Community
PowerShell の GUI 環境だそうです。素晴らしい。
まだちょっとしかいじってませんが、簡単に紹介してみます。
PowerGUI には、PowerGUI と PowerGUI Script Editor の二つのツールが含まれています。
PowerGUI Script Editor は、名前の通り PowerShell スクリプトのエディタです。コードの装飾、インテリセンス等がサポートされています。書いたスクリプトは、保存しなくても PowerShell のスクリプトファイルとして実行することができます。 ( 一時ファイルとしてどこかに保存されているかもしれませんが。 )
ただ、変数に格納したオブジェクトのメンバーに対してはインテリセンスがサポートされていないので、そこは少し残念ですが、まぁしょうがないかなと。 ( PowerShell のコンソール上での Tab キーによる補完機能ではサポートされていますが、あれは動的にリフレクションで取得していそうですね。 )
あと、[ File ] - [ PowerShell Libraries... ] メニューにて自作のスナップインを簡単に利用できます。
PowerGUI は、表形式のデータを一つのノードとし、そのノードをツリー形式で展開します。ノードは PowerShell スクリプトで作成されており、自作も簡単にできます。もちろん PowerGUI 上で直接自作できます。その際は PowerGUI Script Editor と同じくコードの装飾やインテリセンス等のサポートが得られます。
また、ノード上のデータを XML ファイルに出力するといった操作も PowerShell スクリプトで作成されており、これも自作することができます。
PowerShell で簡単に拡張可能とは中々面白いですね。
更に、こういった PowerGUI 用のスクリプトが、PowerGUI Library という形で公開されています。拡張子 .snapin のファイルとして作成されており、PowerGUI にて、コンテキストメニューの [ Import... ] から簡単に追加することができます。
PowerGUI、いじり甲斐がありそうですね~