64bit版Windowsで32bit版PowerShellを実行する際の問題


# はじめに

Windows7環境の置き換えを進める過程で、C#からPowerShellスクリプトを呼び出すプログラムが、Windows10に移行後に動作しなくなった。PowerShell実行ポリシーの設定をしたにもかかわらず、スクリプトファイルが実行されていないようだった。

その解決のために調べた内容を、整理してメモしておく。

* **64bit版Windowsには、32bit版と64bit版のPowerShellがインストールされている**
* **PowerShell実行ポリシーの一部は、32bit用と64bit用で別に管理されている**
* **32bit版アプリケーションからは、原則として32bit版PowerShellが呼び出される**

そのため、起動するバージョンのPowerShellに合わせた実行ポリシーを設定しないと、スクリプトが想定どおりに動作しないことがある。

# PowerShellスクリプト実行ポリシーのスコープ

PowerShellスクリプトを実行するためには、まず実行ポリシーを設定して、スクリプトの実行を許可する必要がある。

●[**About Execution Policies** (Microsoft Docs)](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-6) 

## スコープごとの設定

PowerShellスクリプトの実行ポリシーには、スコープごとに設定が存在する。

スコープごとの設定は、-Listオプションで一覧できる。

``` powershell
PS> Get-ExecutionPolicy -List

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process       Undefined
  CurrentUser    RemoteSigned
 LocalMachine       AllSigned
```

スコープを指定してポリシーを設定･確認する場合は、-Scopeオプションを使用する。

```` powershell
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が使われる。

``` powershell
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の場合**

1. PowerShell(64bit)を管理者で起動し、スコープ「LocalMachine」の実行ポリシーを設定

``` powershell
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
```


2. PowerShell(32bit)を起動し、実行ポリシーを確認する

``` powershell
PS(32)> Get-ExecutionPolicy -List

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process       Undefined
  CurrentUser       Undefined
 LocalMachine       Undefined
```

3. 64bitで設定したLocalMachineの実行ポリシーは、32bitには反映されていない

同様に、32bitPowerShellを起動してLocalMachineの実行ポリシーを設定した場合も、64bitには反映されない。スコープ「LocalMachine」の実行ポリシーが、32bit用と64bit用とでレジストリ内の別の場所に保存されることが原因。

**●CurrentUserの場合**

一方で、スコープ「CurrentUser」の実行ポリシーは、32bit・64bit用でレジストリ内の同じ場所に保存される。

そのため、CurrentUserの実行ポリシーは、64bit版で設定した内容が、32bit版からも参照できる。

``` powershell
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
```

``` powershell
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)](https://docs.microsoft.com/ja-jp/windows/win32/winprog64/registry-redirector)

## 実行ポリシーでのレジストリリダイレクタ

* LocalMachine設定を記録するレジストリ ¥HKEY_LOCAL_MACHINE¥SOFTWARE は、リダイレクト対象になっているため、64bit・32bitの設定がそれぞれ別の場所に保存される
* CurrentUser用のレジストリ ¥HKEY_CURRENT_USER¥Software は「共有」されるため、レジストリ内の同じ場所に設定が保存される

●[**Registry Keys Affected by WOW64** (Microsoft Docs)](https://docs.microsoft.com/ja-jp/windows/win32/winprog64/shared-registry-keys) 

このレジストリリダイレクタの仕組みにより、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)](https://docs.microsoft.com/ja-jp/windows/win32/winprog64/file-system-redirector)

## 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用にコンパイルする以外では、以下のどちらかの方法で解決できる。

1. 明示的に32bit版PowerShellを起動した上で、実行ポリシーを設定する
2. 32bit用と64bit用で共有されるスコープを指定して、実行ポリシーを設定する

## 具体的には

32bitでコンパイルしたC#のコード

``` c
ProcessStartInfo psInfo = new ProcessStartInfo();

psInfo.CreateNoWindow = true;
psInfo.UseShellExecute = false;
psInfo.Arguments = "test.ps1";
psInfo.FileName = "powershell";

Process.Start(psInfo);
```

PowerShell(64bit)を管理者で起動し、実行ポリシーを設定すると、

``` powershell
PS(64)> Set-ExecutionPolicy RemoteSigned
```

この状態では、64bit版PowerShell用には実行ポリシー「RemoteSigned」が設定されているが、32bit用には設定されていない。32bitバイナリC#プログラムからは32bit版PowerShellが呼び出されているため、スクリプトファイルを実行できない。

ただし、この状態でも該当のスクリプトファイルを直接ダブルクリックすれば、64bit版PowerShellが呼び出されるため、スクリプト単体であれば実行できる。

### 対応

* **対応1:** 明示的に32bit版PowerShellを起動して、32bit用の実行ポリシーを設定する

``` powershell
PS(32)> Set-ExecutionPolicy RemoteSigned
```

* **対応2:** 32bit用と64bit用で共有されるスコープを指定して、実行ポリシーを設定する

``` powershell
PS> Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
```

この場合、実行ポリシーを設定するPowerShellは32bit/64bitどちらでもよい。

### PowerShell実行スコープの設定について

実行ポリシーのスコープについては、実行できるスクリプトの範囲をなるべく制限するように設定した方がよいのだと思う。その点では、スコープ「LocalMachine」で実行許可を与えるよりは、「CurrentUser」を指定する対応の方がよいだろう。

さらに望ましいのは、スコープ「Process」で実行許可を設定することだが、その場合は呼び出し元のコードから変える必要があるだろう。