しかし (毎度のことながら)、PowerShell の柔軟さを持ってすれば、クロージャを実現することだって可能です。
今回は closure という名前の関数を作りました。この関数の引数に、クロージャとして機能させたいスクリプトブロックを渡せば、スクリプトブロックをクロージャ化できます。
例えば次のような使い方ができます。 (Wikipedia に掲載されている JavaScript のクロージャサンプルを移植)
function NewCounter
{
$i = 0;
return closure {
$i++;
return $i;
};
}
$counter = NewCounter;
&$counter; # 1
&$counter; # 2
&$counter; # 3
次のような使い方もできます。
function NewDecorator
{
param ([string]$decoration)
return closure {
param ([string]$text)
return $decoration + $text + $decoration;
};
}
$sharpDecorator = NewDecorator '#';
&$sharpDecorator "hoge"; # #hoge#
&$sharpDecorator "fuga"; # #fuga#
&$sharpDecorator "piyo"; # #piyo#
[ コード ]
クロージャを実現する closure 関数は次のようになっています。
この関数は更に InvokeClosureScript という関数を使用します。(正確には closure 関数が生成するスクリプトブロックの内部で使用します。)
closure 関数 + InvokeClosureScript 関数
function global:closure
{
param ([ScriptBlock]$private:script)
trap { break; }
# 引数の妥当性検証
if ($() -eq $script) { throw '引数 script が null です。' }
# 全てのクロージャを保存するクロージャストアを作成 (ハッシュテーブル)
if ($() -eq $global:ClosureStore)
{
Set-Variable 'ClosureStore' @{} -Scope 'global' -Option 'Constant, AllScope';
}
# GC に回収されているクロージャ (を格納しているハッシュテーブル要素) は、クロージャストアから削除
($ClosureStore.GetEnumerator() | ? { !$_.Value.IsAlive; }) | ? { $() -ne $_; } | % { $ClosureStore.Remove($_.Key); };
# 子スコープで環境 (自動変数を除く全ての変数) を取得し保存
$autoVariableNames =
@(
'$', '?', '^', '_', 'args', 'ConfirmPreference', 'ConsoleFileName', 'DebugPreference', 'Error', 'ErrorActionPreference',
'ErrorView', 'ExecutionContext', 'false', 'FormatEnumerationLimit', 'foreach', 'HOME', 'Host', 'input', 'LASTEXITCODE', 'lastWord',
'line', 'Matches', 'MaximumAliasCount', 'MaximumDriveCount', 'MaximumErrorCount', 'MaximumFunctionCount', 'MaximumHistoryCount', 'MaximumVariableCount', 'MyInvocation', 'NestedPromptLevel',
'null', 'OutputEncoding', 'PID', 'PROFILE', 'ProgressPreference', 'PSHOME', 'PWD', 'ReportErrorShowExceptionClass', 'ReportErrorShowInnerException', 'ReportErrorShowSource',
'ReportErrorShowStackTrace', 'ShellId', 'StackTrace', 'switch', 'true', 'VerbosePreference', 'WarningPreference', 'WhatIfPreference'
);
$private:environment = & { return Get-Variable | ? { $autoVariableNames -notcontains $_.Name }; };
# スクリプトと環境を組み合わせてクロージャを表す。
$private:closure =
New-Object 'PSObject' |
Add-Member 'Script' $script -MemberType 'NoteProperty' -PassThru |
Add-Member 'Environment' $environment -MemberType 'NoteProperty' -PassThru;
# GUID をキー、クロージャの弱参照を値とし、クロージャストアに保存
$private:closureId = [Guid]::NewGuid();
$ClosureStore.Add($closureId, [WeakReference]$closure);
# クロージャを実行するスクリプトを動的な文字列操作で構築 (スクリプトに GUID を埋め込むため)
$private:invokerText = "InvokeClosureScript `"$closureId`" `$Args;";
# テキストからスクリプトへ変換し、更に PSObject 化する
$private:invoker = [PSObject](Invoke-Expression "{ $invokerText }");
# 環境をスクリプトに結びつけることでスクリプトと環境の寿命を同一化する
Add-Member -InputObject $invoker -Name 'Closure' -Value $closure -MemberType 'NoteProperty';
return $invoker;
}
function global:InvokeClosureScript
{
param ([Guid]$private:closureId, [Array]$private:Args_)
# 指定した GUID に関連付いているクロージャをクロージャストアから取得 (クロージャは弱参照を使用して格納されている)
$private:closure = $ClosureStore[$closureId].Target;
# Null なら例外
if ($() -eq $closure) { throw '指定した ID に関連付けられたクロージャは存在しません。'; }
# 関数呼び出しを動的な文字列操作で構築 (param キーワードによる引数の受け取りが正常に機能するように)
$private:invokerText = 'param ([ScriptBlock]$private:script, [Array]$private:Args_) .$script';
for ($private:i = 0; $i -lt $Args_.Length; $i++) { $invokerText += " `$Args_[$i]"; }
# テキストからスクリプトへ変換
$private:invoker = Invoke-Expression "{ $invokerText }";
# 環境のロードとクロージャの実行は変数宣言を最小限にした子スコープで
$private:result =
&{
# 環境をロード
$Args[1].Environment | % { trap { continue; } $ExecutionContext.SessionState.PSVariable.Set($_); };
# クロージャを実行
return .$Args[0] $Args[1].Script $Args[2];
} $invoker $closure $Args_;
return $result;
}
[ closure 関数について ]
closure 関数は、受け取ったスクリプトブロックと環境 (自動変数を除く全ての変数) の組み合わせを一つのクロージャとして、クロージャストア (グローバルなハッシュテーブル) に保存します。クロージャストアのキーには、ランダムに生成された GUID を使用します。そして、この GUID を引数として InvokeClosureScript 関数を呼び出すスクリプト (インボーカー) を生成します。closure 関数はこのインボーカーを呼び出し元へと返します。
[ InvokeClosureScript 関数について ]
InvokeClosureScript 関数は、受け取った GUID を元にクロージャストアからクロージャ (スクリプトブロックと環境) を取得します。そして、環境をロードしてスクリプトブロックを実行するのですが、スクリプトブロックは環境がロードされるスコープと同一スコープで実行されます。これにより、本来親スコープで宣言されたはずの変数でもスクリプトブロックから変更することが可能となります。例えば冒頭の一つ目のサンプルコードでは、クロージャ (となるスクリプトブロック) の親スコープで $i が宣言されていますが、クロージャ内から $i を変更 (インクリメント) しています。
(なお、環境のロードとスクリプトブロックの実行は子スコープで行われますが、これは単に、InvokeClosureScript 関数内の変数がスクリプトブロックの実行に影響を与えないようにするためです。)
[ クロージャの寿命について ]
クロージャ (スクリプトブロックと環境) をクロージャストアに保存する際には、弱い参照を保存しています。これにより、クロージャは不要になると GC の対象になることができます。クロージャが GC に回収されただけでは、まだクロージャストアにエントリが残っていますが、次に closure 関数が呼び出された際にエントリも削除されます。
またクロージャは、インボーカーのノートプロパティとしても保存されています。これにより、インボーカーがどこからか参照されている限りは、クロージャは GC の対象になりません。
ただし、一つ注意点があります。インボーカーを変数に保持する代わりに次のように関数として保持してしまうと、(PSObject ではなく生のオブジェクトが保持されるため) ノートプロパティが失われてしまうのです。
$function:func1 = closure {}; # クロージャが GC の対象になってしまう
ノートプロパティが失われてしまうということは、クロージャが GC の対象になってしまうということです。一度変数に保持してから更に関数として保持すればノートプロパティは失われませんが、これも管理が複雑になるのでお勧めしません。
[ インボーカーのノートプロパティについて ]
インボーカーのノートプロパティにクロージャが保存されている理由はクロージャの寿命の制御のためですが、これは嬉しい副作用をもたらします。次のように、インボーカーのノートプロパティを通してスクリプトブロックと環境を確認することができるのです。
$closure1 = closure {};
$closure1.Closure.Script; # スクリプトブロックを確認
$closure1.Closure.Environment; # 環境を確認
【ダウンロード】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。
YokoKen.PowerShell.Scripts
[ パラメータ ]
target
対象のスクリプトブロック。
paramName
取得するパラメータの名前。
省略した場合は全てのパラメータを取得。
[ 戻り値 ]
スクリプトブロックのパラメータ情報。
[ Get-Parameter ]
function Get-Parameter
{
param ([ScriptBlock]$target, [string]$paramName)
trap { break; }
if ($() -eq $target) { throw New-Object "ArgumentException" @("target"); }
$parameterMetadataProperty = [ScriptBlock].GetProperty("ParameterMetadata", [System.Reflection.BindingFlags]"NonPublic, Instance");
$parameterMetadata = $parameterMetadataProperty.GetValue($target, $());
$bindableParametersProperty = $parameterMetadata.GetType().GetProperty("BindableParameters", [System.Reflection.BindingFlags]"NonPublic, Instance");
$bindableParameters = $bindableParametersProperty.GetValue($parameterMetadata, $());
$compiledCommandParameterType = [Type]::GetType("System.Management.Automation.CompiledCommandParameter");
$typeProperty = $compiledCommandParameterType.GetProperty("Type", [System.Reflection.BindingFlags]"NonPublic, Instance");
$result =
$bindableParameters.GetEnumerator() |
? { (("$paramName" -eq "") -or ($_.Key -eq $paramName)); } |
% {
$paramInfo = New-Object "PSObject";
Add-Member -InputObject $paramInfo -Name "Name" -Value $_.Key -MemberType "NoteProperty";
Add-Member -InputObject $paramInfo -Name "Type" -Value $typeProperty.GetValue($_.Value, $()) -MemberType "NoteProperty";
return $paramInfo;
};
return $result;
}
[ 使用例 ]
Get-Parameter ${function:Get-Parameter};
Get-Parameter ${function:Get-Parameter} "paramName";
【 ダウンロード 】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。
YokoKen.PowerShell.Scripts
Windows PowerShell Get-Enjoy コンテスト結果発表
で、僕の作品が選考委員特別賞を頂きました!
プロトタイプチェーンというのが僕の作品です。マニアックな作品と、お褒めの言葉を頂きました (笑)
ダウンロードはこちらからです。
Prototype.zip
実は、コンテスト向けの作品は当初全く違う作品を作ろうとしていました。
スクリプトを書いている内にクラスが使いたくなってきて、Add-Member コマンドレットを使って疑似的にクラスのようなものを書き始めました。
気付いたら、疑似的なクラスを作るための機構に凝りだしてしまいました。
で、作品の路線を変更することにしました。
プロトタイプチェーンこそ備わっていませんが、オブジェクトへ動的にメンバーを追加したりできる PowerShell は、プロトタイプベースオブジェクト指向の流れを汲んでいるのだと思います。
ならば、プロトタイプチェーンを実装しよう。PowerShell にできないことは (あまり) ないはずだ!とその時考えたわけです。
そういえば、僕が初めて PowerShell に触った時、真っ先にスクリプトブロックに Prototype プロパティが備わっているかどうかを試しました。
JavaScript でプロトタイプベースオブジェクト指向を知った僕は、PowerShell にも当然のようにプロトタイプチェーンが備わっているだろうと (勝手に) 思い込んでいたので、備わっていないとわかった時には結構テンションが下がりました。
あの時の落胆をバネに、この作品を完成させることができたような気がしないでもないです。
NUnit を使用して記述されたユニットテストを実行する関数。
この関数は内部で Invoke-Process 関数を使用する。
[ パラメータ ]
testAssemblyPaths
ユニットテストアセンブリのパスの配列。
xmlOutputDirectoryPath
XML 出力ファイルの出力先ディレクトリのパス。
version
NUnit のバージョン。
timeoutMilliseconds
プロセスの実行時間に対するタイムアウト値 (単位:ミリ秒)。
無制限に待機する場合、-1 または Int32.MaxValue を指定。
省略した場合は -1。
[ 戻り値 ]
戻り値については Invoke-Process 関数を参照。
[ Invoke-NUnit ]
function global:Invoke-NUnit
{
param ([string[]] $testAssemblyPaths, [string]$xmlOutputDirectoryPath, [string]$version, [int]$timeoutMilliseconds = [System.Threading.Timeout]::Infinite)
trap { break; }
if ("$testAssemblyPaths".Length -eq 0) { throw "引数 testAssemblyPaths が null または空の配列です。"; }
if ([string]::IsNullOrEmpty($xmlOutputDirectoryPath)) { throw "引数 xmlOutputDirectoryPath が null または空の文字列です。"; }
if ([string]::IsNullOrEmpty($version)) { throw "引数 version が null または空の文字列です。"; }
$testAssemblyPathsText = [string]::Empty;
$testAssemblyPaths | % { $testAssemblyPathsText += "`"$_`" "; };
$nunitConsolePath = "$Env:ProgramFiles\NUnit $version\bin\nunit-console.exe";
$nunitConsoleArgs = "$testAssemblyPathsText /xml=`"$xmlOutputDirectoryPath\TestResult.xml`" /nologo /nodots";
return Invoke-Process $nunitConsolePath $nunitConsoleArgs $timeoutMilliseconds;
}
[ 使用例 ]
$testAssemblyPaths =
@(
"C:\work\Hoge\Project1.Test\bin\Release\Project1.Test.dll",
"C:\work\Hoge\Project2.Test\bin\Release\Project2.Test.dll"
);
$xmlOutputDirectoryPath = "C:\work\Hoge";
$result = Invoke-NUnit $testAssemblyPaths $xmlOutputDirectoryPath "2.4.6";
if (!$result.IsSuccess)
{
Write-Host "ユニットテストが失敗しました。" -ForegroundColor "Red";
}
【 ダウンロード 】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。
YokoKen.PowerShell.Scripts
PowerShell でプロセスを実行する関数。
[ パラメータ ]
processPath
プロセス (実行可能ファイル) のパス。
processArgs
プロセスを実行する際に渡す引数。
省略可能。
timeoutMilliseconds
プロセスの実行時間に対するタイムアウト値 (単位:ミリ秒)。
無制限に待機する場合、-1 または Int32.MaxValue を指定。
省略した場合は -1。
[ 戻り値 ]
プロセスの実行結果に関する情報を格納するハッシュテーブル。
各キーの説明を以下に記述する。
IsSuccess
プロセスが正常終了した場合は true、それ以外の場合は false。
プロセスが正常終了したかどうかは、プロセスの終了コードを元に判断される。
IsComplete
プロセスがタイムアウトせずに完了した場合は true、それ以外の場合は false。
Message
プロセスが実行中に出力したメッセージ。
ErrorMessage
プロセスが実行中に出力したエラーメッセージ。
[ Invoke-Process 関数 ]
function global:Invoke-Process
{
param ([string] $processPath, [string]$processArgs, [int]$timeoutMilliseconds = [System.Threading.Timeout]::Infinite)
trap
{
if ($() -ne $process)
{
$process.Dispose();
}
break;
}
if ([String]::IsNullOrEmpty($processPath)) { throw "引数 processPath が null または空の文字列です。"; }
$process = New-Object "System.Diagnostics.Process";
$process.StartInfo = New-Object "System.Diagnostics.ProcessStartInfo" @($processPath, $processArgs);
$process.StartInfo.WorkingDirectory = (Get-Location).Path;
$process.StartInfo.RedirectStandardOutput = $True;
$process.StartInfo.RedirectStandardError = $True;
$process.StartInfo.UseShellExecute = $False;
$process.Start() | Out-Null;
$message = $process.StandardOutput.ReadToEnd();
$errorMessage = $process.StandardError.ReadToEnd();
$complete = $process.WaitForExit($timeoutMilliseconds);
if (!$complete)
{
$process.Kill();
}
$result =
@{
"IsSuccess" = ($process.ExitCode -eq 0);
"IsComplete" = $complete;
"Message" = $message;
"ErrorMessage" = $errorMessage;
};
$process.Dispose();
return $result;
}
[ 使用例 ]
$result = Invoke-Process "$Env:ProgramFiles\Microsoft Visual Studio 9.0\Common7\IDE\devenv.com" "/rebuild Release `"C:\work\Hoge\Hoge.sln`"";
Write-Host $result.Message;
if (!$result.IsSuccess)
{
Write-Host "ソリューションのリビルドに失敗しました。" -ForegroundColor "Red";
}
【 ダウンロード 】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。
YokoKen.PowerShell.Scripts
【 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
自作の 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 のような形式で、構造化例外処理を行うことが可能になります。
そこで、そんなことをせずとも任意のコンフィギュレーションを使用して 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
アセンブリ名を省略した場合、ランダムな文字列が設定される。
当然、コンパイルされたクラスは 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