はじめに
Windows7環境の置き換えを進める過程で、C#からPowerShellスクリプトを呼び出すプログラムが、Windows10に移行後に動作しなくなった。PowerShell実行ポリシーの設定をしたにもかかわらず、スクリプトファイルが実行されていないようだった。
その解決のために調べた内容を、整理してメモしておく。
- 64bit版Windowsには、32bit版と64bit版のPowerShellがインストールされている
- PowerShell実行ポリシーの一部は、32bit用と64bit用で別に管理されている
- 32bit版アプリケーションからは、原則として32bit版PowerShellが呼び出される
そのため、起動するバージョンのPowerShellに合わせた実行ポリシーを設定しないと、スクリプトが想定どおりに動作しないことがある。
PowerShellスクリプト実行ポリシーのスコープ
PowerShellスクリプトを実行するためには、まず実行ポリシーを設定して、スクリプトの実行を許可する必要がある。
●About Execution Policies (Microsoft Docs)
スコープごとの設定
PowerShellスクリプトの実行ポリシーには、スコープごとに設定が存在する。
スコープごとの設定は、-Listオプションで一覧できる。
PS> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser RemoteSigned
LocalMachine AllSigned
スコープを指定してポリシーを設定・確認する場合は、-Scopeオプションを使用する。
PS> Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope CurrentUser
-Scopeを指定しなかった場合は、スコープLocalMachineを指定したのと同じ扱いになる。
それぞれのスコープの意味
スコープ | 意味 |
---|---|
MachinePolicy | 全ユーザ向けグループポリシーによって設定される |
UserPolicy | 現在のユーザ向けグループポリシーによって設定される |
Process | 現在のPowerShellセッションのみに影響する。環境変数 $env:PSExecutionPolicyPreference に保存される。セッション終了後は削除される |
CurrentUser | 現在のユーザのみに影響する。レジストリ HKEY_CURRENT_USER に保存される |
LocalMachine | このマシンの全ユーザに影響する。レジストリ HKEY_LOCAL_MACHINE に保存される |
スコープを指定して実行ポリシーを設定することにより、スクリプトの実行許可が与えられる範囲を制限することができる。
スコープの優先順位
スクリプト実行の際にどのスコープの設定を反映するかには、優先順位が存在する。
MachinePolicy > UserPolicy > Process > CurrentUser > LocalMachine
Undefined以外が指定されている中で、優先順位の最も高いスコープの設定が採用される。どのスコープの設定もUndefinedである場合、規定値であるRestrictedが使われる。
PS> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser RemoteSigned
LocalMachine AllSigned
この例の場合、スコープ「CurrentUser」の「RemoteSigned」が、現在の実行ポリシーとして使用される。
64bit版Windowsでの実行ポリシー設定
- 64bit版Windowsには、32bit・64bit両方のPowerShellがインストールされている
- 実行ポリシーのうち、スコープ「LocalMachine」についての設定は、32bit用・64bit用で別に管理される
CurrentUser・LocalMachineの設定
CurrentUser・LocalMachine用の実行ポリシー設定は、それぞれレジストリに保存される。このうち、LocalMachine用の設定は、32bitと64bit用とでレジストリ内に別に保存領域が用意されている。
そのため、以下のような現象が発生する。
●LocalMachineの場合
- PowerShell(64bit)を管理者で起動し、スコープ「LocalMachine」の実行ポリシーを設定
PS(64)> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine Undefined
PS(64)> Set-ExecutionPolicy RemoteSigned
PS(64)> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine RemoteSigned
- PowerShell(32bit)を起動し、実行ポリシーを確認する
PS(32)> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine Undefined
- 64bitで設定したLocalMachineの実行ポリシーは、32bitには反映されていない
同様に、32bitPowerShellを起動してLocalMachineの実行ポリシーを設定した場合も、64bitには反映されない。スコープ「LocalMachine」の実行ポリシーが、32bit用と64bit用とでレジストリ内の別の場所に保存されることが原因。
●CurrentUserの場合
一方で、スコープ「CurrentUser」の実行ポリシーは、32bit・64bit用でレジストリ内の同じ場所に保存される。
そのため、CurrentUserの実行ポリシーは、64bit版で設定した内容が、32bit版からも参照できる。
PS(64)> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine Undefined
PS(64)> Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
PS(64)> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser RemoteSigned
LocalMachine Undefined
PS(32)> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser RemoteSigned
LocalMachine Undefined
以上のように、実行するPowershellの64/32bitに合わせて実行ポリシーの設定をしないと、スクリプトが実行できないことがある。
この、同じアプリケーションの32bit版・64bit版で別のレジストリにアクセスする動作は、レジストリリダイレクタという仕組みによって実現されている。
レジストリリダイレクタ
アプリケーションからレジストリへのアクセスをインターセプトし、32bit版と64bit版とでそれぞれレジストリ内の別の領域へ振り分ける仕組み。これにより、32bit版と64bit版のアプリケーション内で同じレジストリにアクセスするように記述しながら、実際には別のレジストリを使用することができる。
具体的には、32bit版アプリケーションからのアクセスは、レジストリ内の WOW6432Node 以下の領域にリダイレクトされる。
ただし、すべてのアクセスがリダイレクトされるわけではなく、アクセス対象のレジストリ領域によっては32bit版と64bit版で同じレジストリを「共有」することもある。
●Registry Redirector (Microsoft Docs)
実行ポリシーでのレジストリリダイレクタ
- LocalMachine設定を記録するレジストリ ¥HKEY_LOCAL_MACHINE¥SOFTWARE は、リダイレクト対象になっているため、64bit・32bitの設定がそれぞれ別の場所に保存される
- CurrentUser用のレジストリ ¥HKEY_CURRENT_USER¥Software は「共有」されるため、レジストリ内の同じ場所に設定が保存される
●Registry Keys Affected by WOW64 (Microsoft Docs)
このレジストリリダイレクタの仕組みにより、32bit版PowerShellを実行中にLocalMachineの実行ポリシーを読み出そうとした場合、64bit版で記録された内容は読み出すことができない。
実行ポリシー設定が実際に保存されるレジストリ
●LocalMachine
64/32bit | レジストリ内の保存場所 |
---|---|
64bit | ¥HKEY_LOCAL_MACHINE¥SOFTWARE¥Microsoft¥PowerShell¥1¥ShellIds¥Microsoft.PowerShell |
32bit | ¥HKEY_LOCAL_MACHINE¥SOFTWARE¥WOW6432Node¥Microsoft¥PowerShell¥1¥ShellIds¥Microsoft.PowerShell |
…32bit版からのアクセスが、リダイレクトされている
●CurrentUser
64/32bit | レジストリ内の保存場所 |
---|---|
64bit | ¥HKEY_CURRENT_USER¥Software¥Microsoft¥PowerShell¥1¥ShellIds¥Microsoft.PowerShell |
32bit | ¥HKEY_CURRENT_USER¥Software¥Microsoft¥PowerShell¥1¥ShellIds¥Microsoft.PowerShell |
…64bit版と32bit版で、レジストリを「共有」している
実行するPowerShellの選択(ファイルシステムリダイレクタ)
32bit版・64bit版のPowerShell実行ファイルは、それぞれ別の場所にインストールされている。
64bit版Windows環境では、32bitアプリケーションからPowerShellを呼び出すと、常に32bit版が実行される。32bitアプリケーション内で明示的に64bit版実行ファイルのパスを指定した場合でも、同じ結果になる。
これは、ファイルシステムリダイレクタの仕組みによる。
ファイルシステムリダイレクタ
64bit環境において、32bitアプリケーションからの特定の場所へのアクセスを、別の場所へリダイレクトする仕組み。
例えば、DLLは、64bit用と32bit用では別のものを用意する必要がある。ファイルシステムリダイレクタによって、64bit用と32bit用で同じ名前を持つDLLをそれぞれ別の場所に保存して、なおかつ64bit用と32bit用アプリケーションから、同じコードでアクセスすることが可能になる。
●File System Redirector (Microsoft Docs)
PowerShell実行ファイルのリダイレクト
PowerShell実行ファイルは、32bit版・64bit版がそれぞれ以下の場所に存在する。
64/32bit | PowerShell実行ファイルの場所 |
---|---|
64bit | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe |
32bit | C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe |
一方、ファイルシステムリダイレクタの規則は以下のようになる。
元のパス | 32bit用リダイレクト先 |
---|---|
%windir%\System32 | %windir%\SysWOW64 |
%windir%\lastgood\system32 | %windir%\lastgood\SysWOW64 |
%windir%\regedit.exe | %windir%\SysWOW64\regedit.exe |
64bit版PowerShell実行ファイルの場所はリダイレクト対象となっている。従って、32bit版アプリケーションからPowerShellを呼び出す場合は、常にリダイレクトされて32bit版が実行されることになる。
リダイレクトの回避
パスとして %windir%\System32 の代わりに %windir%\Sysnative を指定することで、ファイルシステムリダイレクタを無効にして、32bitアプリケーションからSystem32以下のパスにアクセスすることができる。
このSysnativeを利用すれば、32bitアプリケーションから64bit版PowerShellを呼び出すことも可能になる。
ただし、64bitアプリケーションでは、ディレクトリSysnativeは使用できない(存在しないパスとして無効になる)。そのため、Sysnativeを使う場合は32bit版と64bit版でコードを分ける必要が発生する。
解決方法
●C#からPowerShellスクリプトを呼び出すプログラムが、Windows7からWindows10に移行後に動作しなくなった問題について
- C#アプリケーションが32bit環境でコンパイルしたものであった
- 32bitバイナリのC#アプリケーションからは32bit版PowerShellが実行されていた
- 実行ポリシーは64bit用LocalMachineのみ設定され、32bit用には設定できていなかった
かなり以前に作ってWindows7(32bit)で運用していたアプリケーションを、64bit版Windows10環境にそのまま移行したことで、問題が発生した。
アプリケーションを新しく64bit用にコンパイルする以外では、以下のどちらかの方法で解決できる。
- 明示的に32bit版PowerShellを起動した上で、実行ポリシーを設定する
- 32bit用と64bit用で共有されるスコープを指定して、実行ポリシーを設定する
具体的には
32bitでコンパイルしたC#のコード
ProcessStartInfo psInfo = new ProcessStartInfo();
psInfo.CreateNoWindow = true;
psInfo.UseShellExecute = false;
psInfo.Arguments = "test.ps1";
psInfo.FileName = "powershell";
Process.Start(psInfo);
PowerShell(64bit)を管理者で起動し、実行ポリシーを設定すると、
PS(64)> Set-ExecutionPolicy RemoteSigned
この状態では、64bit版PowerShell用には実行ポリシー「RemoteSigned」が設定されているが、32bit用には設定されていない。32bitバイナリC#プログラムからは32bit版PowerShellが呼び出されているため、スクリプトファイルを実行できない。
ただし、この状態でも該当のスクリプトファイルを直接ダブルクリックすれば、64bit版PowerShellが呼び出されるため、スクリプト単体であれば実行できる。
対応
- 対応1: 明示的に32bit版PowerShellを起動して、32bit用の実行ポリシーを設定する
PS(32)> Set-ExecutionPolicy RemoteSigned
- 対応2: 32bit用と64bit用で共有されるスコープを指定して、実行ポリシーを設定する
PS> Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
この場合、実行ポリシーを設定するPowerShellは32bit/64bitどちらでもよい。
PowerShell実行スコープの設定について
実行ポリシーのスコープについては、実行できるスクリプトの範囲をなるべく制限するように設定した方がよいのだと思う。その点では、スコープ「LocalMachine」で実行許可を与えるよりは、「CurrentUser」を指定する対応の方がよいだろう。
さらに望ましいのは、スコープ「Process」で実行許可を設定することだが、その場合は呼び出し元のコードから変える必要があるだろう。