.NETダイアログが表示されずスクリプトが止まる問題の対策
公開日 更新日 2020/09/16
PowerShell入門中です。
先日のこちらの記事(PowerShellでダイアログを使う)にて、PowerShellスクリプトから.NETの各種ダイアログを表示させる方法を学びました。
しかしながら、どうもこの.NET Frameworkのダイアログを扱える条件があるということがわかりましたので、整理したいと思います。
Contents
問題点
まずこの問題の対象ですが、Windows 7などで提供されているPowerShell 2.0が対象となります。
※未確認ですが、PowerShell 3.0では問題が起きないような気がします。
この問題は、関連記事の手順で、.NET Frameworkのダイアログを表示させる処理を含むPowerShellスクリプトを作った場合、次の動作になるというものです。
- WindowsのスタートメニューからPowerShellを実行し、その後プロンプトから対象のPS1ファイルを実行した場合
スクリプトが.NET Frameworkのダイアログを表示する直前で停止、powershellの画面を閉じないといけない。 - 対象のPS1ファイルの右クリックメニューから[実行]を選択した場合
スクリプトが.NET Frameworkのダイアログを表示する直前で停止、powershellの画面を閉じないといけない。 - 対象のPS1ファイルの右クリックメニューから[編集]を選択すると起動するpowershell_ise.exeから実行した場合
.NET Frameworkのダイアログを表示でき、スクリプトは問題なく動作する
3番にあるpowershell_ise.exeから実行した場合だけ.NET Frameworkのダイアログを表示できるということがわかりました。
.NET Frameworkのダイアログを表示できない問題の原因
WEBを検索すると、PowerShellにはSTAモードとMTAモードという2つの実行モードがあって、どうも.NET Frameworkのダイアログを使用するには、STAモードにして実行しないといけないということがわかりました。
STAモードとMTAモードについて調べてみました
まずはPowerShell.exeのマニュアルより抜粋。
PowerShell.exe Command-Line Help
-Mta
Starts Windows PowerShell using a multi-threaded apartment. This parameter is introduced in Windows PowerShell 3.0. In Windows PowerShell 3.0, single-threaded apartment (STA) is the default. In Windows PowerShell 2.0, multi-threaded apartment (MTA) is the default.-Sta
Starts Windows PowerShell using a single-threaded apartment. In Windows PowerShell 3.0, single-threaded apartment (STA) is the default. In Windows PowerShell 2.0, multi-threaded apartment (MTA) is the default.
問題点でも触れましたが、PowerShell 2.0のデフォルトでは、MTAモードとして実行されるようです。
そのため、明示的に-STAオプションを指定しなかった場合は、本件の問題点につながります。
なお未確認ですが、PowerShell 3.0ではデフォルトが変更され、明示的に-MTAオプションを指定しなかった場合、STAモードとしてPowerShellが実行されるため、本件の問題は起きないようです。
なぜSTAモードじゃないといけないのかは、残念ながら適切なリソースが見つけられてませんが、フォーラムだと以下あたりでしょうか。
Could you explain STA and MTA?
The COM threading model is called an “apartment” model, where the execution context of initialized COM objects is associated with either a single thread (Single Thread Apartment) or many threads (Multi Thread Apartment). In this model, a COM object, once initialized in an apartment, is part of that apartment for the duration of it’s runtime.
The STA model is used for COM objects that are not thread safe. That means they do not handle their own synchronization. A common use of this is a UI component. So if another thread needs to interact with the object (such as pushing a button in a form) then the message is marshalled onto the STA thread. The windows forms message pumping system is an example of this.
If the COM object can handle its own synchronization then the MTA model can be used where multiple threads are allowed to interact with the object without marshalled calls.
STAは、シングルスレッド用でスレッドセーフじゃなくUIコンポーネント用という位置づけらしい。
ということで、とりあえずここでは、STAモードにしないといけないというレベルで事を進めたいと思います。
スクリプト実行時の値の比較
実行モードは、スクリプト中で変数「$host.Runspace.ApartmentState」にて確認可能とのこと。
ここでPowerShell実行時に指定された値に応じた変数の動作は以下のようになりました。
PowerShell起動時の指定 | $host.Runspace.ApartmentStateの値 | |
---|---|---|
(PowerShell 2.0の場合) | (PowerShell 3.0以降の場合) | |
なし(デフォルト) | Unknown (=MTA) | ※未確認 Unknown (=STA) |
-STA | STA | STA |
-MTA | MTA | MTA |
回避方法
問題点にあるそれぞれの動作ケースについて、.NET Frameworkのダイアログを表示するための回避方法を整理したいと思います。
なおpowershell_ise.exeについては、もともとSTAモードで実行しているため、問題なく実行できているという動きだったようです。
PowerShellプロンプトから対象スクリプトを実行させる方法
前述したようにPowerShell.exe実行時のコマンドライン引数を与えることで回避できそうです。
1 |
> PowerShell.exe -STA 実行するスクリプト.ps1 |
対象スクリプトの右クリックメニューから[実行]を選択することで実行させる方法
もしかしたら右クリックメニューで実行するPowerShellのコマンドライン引数をカスタマイズできるのかもしれませんがWindowsのOS設定を変えるのはあまり気が進まないので、スクリプト中で変更する方法がないか検索。
検索してみると、意外とたくさんの方が様々な方法で実現していたので参考にさせていただきました。
とは言え、ちょっと豪華な作りのスクリプトが多くて、もう少しシンプルな方法を考えてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#ApartmentStateにてSTAモードのチェック(.NET Frameworkオブジェクトを利用する前提) $reRun = $false; Switch($host.Runspace.ApartmentState){ 'STA' { #何もしない } 'MTA' { #MTAモードの場合は、STAモードを指定して再実行 $reRun = $true; } default { #STAモードでもMTAモードでもない場合はUnknown(デフォルト) If($PSVersionTable.PSVersion.Major -eq "2"){ #PowerShell2.0の場合、デフォルトがMTAモードなのでSTAモードを指定して再実行 $reRun = $true; } } } If($reRun){ $ScriptPath = $MyInvocation.MyCommand.Path; Start-Process PowerShell.exe -ArgumentList "-sta $ScriptPath"; Exit; } |
ポイントを以下に補足。
- 3行目「Switch($host.Runspace.ApartmentState){」
$host.Runspace.ApartmentStateは、実行モードを返却するプロパティ。STA,MTA,Unknownのいずれか(らしい)。
デフォルト値で動作する場合は、今のところUnknownが帰ってくるようです。 - 11行目「default」
PowerShellのSwitch構文でいう「その他」枠。
動作確認した限りではUnknownを指定すれば良いのですが、今回はSTAモードと確認が取れなかった場合にすべて再実行に倒したかったので、このような指定にしました。 - 13行目「If($PSVersionTable.PSVersion.Major -eq “2”){」
PowerShellのバージョン情報のうち、メジャーバージョン部分で2の場合のみ再実行するようにしてます。
大前提としてWindows7以降としておりますので、多分こういう分岐で良いと思ってます。 - 21行目「Start-Process PowerShell.exe -ArgumentList “-sta $ScriptPath”;」
STAモードとして再実行が必要な場合に、子プロセスとして引数に-staを追加した状態でPowerShell.exeを実行します。
再実行時に同じスクリプトの実行となるよう、引数最後に$ScriptPathとして、自身を示す$MyInvocation.MyCommand.Pathを与えています。
動かしてみると、一瞬プロンプト画面がチラついて余り綺麗じゃないです。
ここは気にならないレベルなのか、やりたいことから考えないといけなさそうな気がします。
個人的には、右クリックメニューから選択するという手作業を伴い動作させるのであれば、速度を求めているわけではないし、結果が出てくれば良いと思われるので、さほど気にならないと思います。
レスポンシブ広告
関連記事
-
PowerShellの開発速度の改善を考える
私はどちらかというとWindowよりもUNIXの方がなれてますが、仕事ではWin …
-
MiniDV形式ビデオテープを撮影日時つきファイルでバックアップ[PowerShell版]
去年からちょいちょいPowerShellを使い出しました。 勉強の一環で、ちょっ …
-
PowerShellスクリプトを動かすための設定
PowerShellは最近のWindowsで追加されたスクリプト言語で、今のWi …
-
PowerShellでダイアログを表示し入力結果を取得
PowerShellの入門中です。 今回はPowerShellスクリプトからのW …
- PREV
- PowerShellでダイアログを表示し入力結果を取得
- NEXT
- My Humanity