バッチファイルで32bitプロセスから64bitプロセスを起動する

※掲題の結論だけを知りたい方は、末尾の「32bitプロセスから64bitプロセスを起動する例」まで読み飛ばしてください。
 

はじめに

 近年運用されているWindows環境はほとんど64bit環境に移行されていると思いますが、動作しているプロセス自体は32bitのものも多く存在します。
 子プロセスを起動する場合、基本的には32bitのプロセスからは32bitのプロセスが、64bitのプロセスからは64bitのプロセスが生成されますが、それでは困る場合もあります。
 検索してみると、プログラム内部からプロセス生成する場合などは、いくつか事例が見つかりますがバッチファイルの場合についての情報が無さそうなので、記載しておきます。

 ※なお、後述しますが、C:\Windows配下のディレクトリ内の64bit版プログラムを実行しようとするとこの問題にぶち当たりますが、ユーザが自由に作った(c:\binのような)任意のディレクトリ配下に配置した64bitプログラムであれば、32bitで実行されたバッチファイルからであっても、アーキテクチャの差異を気にせず普通に実行できます。
 ※本投稿はバッチファイルからWSLが実行できないとか、バッチファイルからPowershellを起動したら32bit版が実行されるとか、そういった事態に対応するための情報です。
 

背景

 サクラエディタのブラウズ機能(ショートカット: Ctrl+B)を使うと、編集中のファイルを参照することができます。
 このように書くと何を言っているか解りづらいですが、例えばhtmlファイルを編集している時にCtrl+Bを押すと、ブラウザが起動し編集中のhtmlが表示されます。或いはバッチファイルを編集している時ならば、そのまま実行されます。要するに、ファイルをダブルクリックした時と同様に、拡張子に紐づいたデフォルトの挙動が発動します。
 で、バッチファイルをサクラから起動すると、一部のコマンド(sshやwsl等)が実行できないことに気付きます。ですが、構文エラーなどの類ではなく、エクスプローラコマンドプロンプトから同じバッチファイルを実行すると正常に実行できるのです。実行時の権限などの環境周りの何かが違うのかと思いつつも、ショートカットキーで実行できないだけ、すなわち多少面倒なだけで実害は無いので深く気にしていなかったのですが、フラストレーションが溜まってきたため今回調べてみました。
 ※私が調べるきっかけとなったのがサクラエディタのこの挙動だったというだけで、サクラ固有の話ではありません。
 

前提環境

  • Windows 10 Home (Proも同様) 1803
    • [オプション機能の管理]から[機能の追加]で、[OpenSSHクライアント]を追加済
    • [Windowsの機能の有効化または無効化]から[Windows Subsystems for Linux]を有効化済
    • [Microsoft Store]から[Ubuntu 18.04]をインストール済
  • サクラエディタ Ver. 2.2.0.1

 

サクラエディタから起動されたプロセスの調査

実行ユーザ

 サクラを実行しているのと同じユーザアカウントで、プロセスが生成されていることがタスクマネージャなどで確認できます。プロセス生成時に特殊なことをしていなければ、権限の差異はないはずです。
 

環境変数

 次に、環境変数の差異を確認してみるべく、以下の内容のバッチファイルを作成し、サクラからCtrl+Bで実行した場合と、コマンドプロンプトから実行した場合の差異を確認してみました。

SET
PAUSE

 いくつか差異が見つかりますが、以下の差異が気になりました。

変数名 サクラから実行 コマンドプロンプトから実行
PROCESSOR_ARCHITECTURE x86 AMD64
PROCESSOR_ARCHITEW6432 AMD64 当該変数無し
ProgramFiles C:\Program Files (x86) C:\Program Files

 察するに、サクラから実行されたバッチファイルは32bit環境(WOW64; 64bit環境で32bitアプリを動かすMicrosoftの技術)で動作しており、コマンドプロンプト*1から実行されたバッチファイルは64bit環境で動作しているっぽいことが読み取れます。私が使っているサクラエディタは32bit版*2なので、この挙動を示すのでしょう。
 というわけで、サクラの話はここまでにして、掲題の話に移ります。以降、32bit/64bitのアーキテクチャ差異に絞って調査することにします。
 

アーキテクチャ毎の実行体の差異

 32/64bitの各アーキテクチャでwhereコマンドを使って主要な実行体の場所を調べてみると以下の通りでした。

cmd.exe
Architecture Path
32bit C:\Windows\SysWOW64\cmd.exe
64bit C:\Windows\System32\cmd.exe

 異なるバイナリを指しています。
 

powershell.exe
Architecture Path
32bit C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
64bit C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

 同じパスを指しており、同一バイナリのように見えますが、実は異なります。

 32bitのcmd.exeから確認してみると、以下のように430,592バイトの実行体であることが判ります。

C:\>echo %PROCESSOR_ARCHITECTURE%
x86

C:\>dir C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
 ドライブ C のボリューム ラベルは OS です
 ボリューム シリアル番号は 8C3F-AAA1 です

 C:\Windows\System32\WindowsPowerShell\v1.0 のディレクトリ

2018/04/12  08:35           430,592 powershell.exe
               1 個のファイル             430,592 バイト

 一方、64bitのcmd.exeから確認してみると、447,488バイトの実行体であることが判ります。32bit版とはバイナリのサイズが異なる時点で別物であることが明らかです。

C:\>echo %PROCESSOR_ARCHITECTURE%
AMD64

C:\>dir C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
 C:\Windows\System32\WindowsPowerShell\v1.0 のディレクトリ

2018/04/12  08:35           447,488 powershell.exe
               1 個のファイル             447,488 バイト

 

wsl.exe
Architecture Path
32bit 情報: 与えられたパターンのファイルが見つかりませんでした。
64bit C:\Windows\System32\wsl.exe

 WSLは64bit版Windowsでしかサポートされていないため、32bit版は存在しないのも当然でしょう。
 

ssh.exe
Architecture Path
32bit 情報: 与えられたパターンのファイルが見つかりませんでした。
64bit C:\Windows\System32\OpenSSH\ssh.exe

 前提環境に記した通り、自前でダウンロードして適当に配置したバイナリではなく、Windowsの機能の[オプション機能の管理]から追加したOpenSSHクライアントですが、32bit版は64bit環境には導入されないようです。
 

32bitプロセスから64bitプロセスの起動を試行

 32bit版cmd.exeからWSLやSSHの実行を試みると、以下のようにエラーとなります。

C:\>wsl
'wsl' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

C:\>ssh
'ssh' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

 実行した結果としてエラーが発生しているわけではなく、そもそも実行できていません。先述の通り、whereコマンドでもパスを解決できないことから、予め64bit環境で調べておいたフルパス表現で再試行してみます。

C:\>C:\Windows\System32\wsl.exe
'C:\Windows\System32\wsl.exe' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

C:\>C:\Windows\System32\OpenSSH\ssh.exe
指定されたパスが見つかりません。

 存在するパスの実行体を指定しているにも拘らず、そんなもん無いと言われています。念のためdirコマンドで確認してみると、以下の通り。

C:\>dir /a C:\Windows\System32\wsl.exe
 C:\Windows\System32 のディレクトリ

ファイルが見つかりません

C:\>dir /a C:\Windows\System32\OpenSSH\ssh.exe
指定されたファイルが見つかりません。

 ???

 ここで、64bit版cmd.exeで同じdirコマンドを叩いてみると、確かに当該バイナリは存在していることが確認できます。

C:\>dir /a C:\Windows\System32\wsl.exe
 C:\Windows\System32 のディレクトリ

2018/04/13  02:48           114,688 wsl.exe
               1 個のファイル             114,688 バイト

C:\>dir /a C:\Windows\System32\OpenSSH\ssh.exe
 C:\Windows\System32\OpenSSH のディレクトリ

2018/03/11  03:20           894,464 ssh.exe
               1 個のファイル             894,464 バイト

 

 同じC:\Windows\System32でも32bitと64bitでは見えているものの実体が異なるようです。
 …って、あー、これがWOW64の挙動の一つなのかと身を以て体感しました。

 ところで、Windows標準機能や公式機能とは関係なく、私のPCには64bit版ffmpegを配置していますが、32bitのcmd.exeからも普通に起動できています。
 嵌っているWSLやSSHとは異なり、ffmpegはC:\binのようなディレクトリにパスを通して配置しています。
 と、考えるとC:\Windows\System32のような、C:\Windows配下のディレクトリの一部がWOW64によってトリッキーな差し替えが行われているのではないかと予想されます。
 

File System Redirector

 調べてみると、ファイルシステムリダイレクタなる物が存在するようです。
File System Redirector | Microsoft Docs

 上記Microsoftの解説から引用すると以下の通り。

32-bit applications can access the native system directory by substituting %windir%\Sysnative for %windir%\System32. WOW64 recognizes Sysnative as a special alias used to indicate that the file system should not redirect the access. This mechanism is flexible and easy to use, therefore, it is the recommended mechanism to bypass file system redirection. Note that 64-bit applications cannot use the Sysnative alias as it is a virtual directory not a real one.

 適当に意訳すると、以下のような感じです。

  • 32bitアプリは%windir%\System32を%windir%\Sysnativeに置き換えることで、システム本来(のアーキテクチャ)のディレクトリにアクセス可能
  • WOW64はSysnativeを(ファイルへの)アクセスをリダイレクトすべきではない特別なエイリアスとして認識
  • このメカニズムは柔軟で使いやすいので、ファイルシステムのリダイレクトを回避するのにお勧め
  • Sysnativeエイリアスは仮想ディレクトリで実在するものではないので、64bitアプリでは使えないことに注意

 要するに、64bit版Windows上で動く32bitプロセスから、64bitのC:\Windows\System32にアクセスしたければ、C:\Windows\Sysnativeにアクセスすればいいようです。
 先の検証時に32bit版cmd.exeからC:\Windows\System32配下のWSLやSSHバイナリが見えなかったのは、このファイルシステムリダイレクタの挙動によるものと考えられます。
 

Sysnativeエイリアスを使ってみる

 32bit版cmd.exeから、WSLやSSHのパス中のSystem32をSysnativeに置き換えてdirコマンドを叩いてみます。

C:\>dir /a C:\WINDOWS\Sysnative\wsl.exe
 C:\WINDOWS\Sysnative のディレクトリ

2018/04/13  02:48           114,688 wsl.exe
               1 個のファイル             114,688 バイト

C:\temp>dir /a C:\WINDOWS\Sysnative\OpenSSH\ssh.exe
 C:\WINDOWS\Sysnative\OpenSSH のディレクトリ

2018/03/11  03:20           894,464 ssh.exe
               1 個のファイル             894,464 バイト

 64bit版cmd.exeから見えていたものと同一(ファイルサイズ)の実行体が確認できます。

 逆に、先述のMSの説明通りですが64bit版cmd.exeからはSysnativeに置き換えたパスは存在しないように見えます。

C:\>dir /a C:\WINDOWS\Sysnative
 C:\WINDOWS のディレクトリ

ファイルが見つかりません

 以上より、実行環境が32bitの場合のみ、実行体のパス中の%windir%直下のSystem32をSysnativeに置き換えたバッチファイルを作れば、物理64bit環境で動作させるバッチファイルとしては実行環境に依存せず意図した挙動をさせることが可能です。
 

32bitプロセスから64bitプロセスを起動する例

 例えばSSHを使ってリモートでdfコマンドを叩く場合、以下のようなバッチファイルで実現できます。
 やっていることは単純で、環境変数PROCESSOR_ARCHITECTUREを見て、64bit環境(AMD64)なら普通にパスの通ったssh.exeを実行、そうでなければ(x86の32bit環境なら)Sysnativeエイリアスを使ってフルパス指定したssh.exeを実行するように叩き分けているだけです。

@Echo off
setlocal enabledelayedexpansion
set USERNAME=user
set REMOTEHOST=host
set REMOTECMD=df

IF %PROCESSOR_ARCHITECTURE% equ AMD64 (
  echo [%DATE% %TIME%] AMD64 environment
  SET SSHBIN=ssh.exe
) ELSE (
  echo [%DATE% %TIME%] x86 environment
  SET SSHBIN=C:\Windows\Sysnative\OpenSSH\ssh.exe
)
!SSHBIN! %USERNAME%@%REMOTEHOST% %REMOTECMD%

 なお、これはあくまでも物理64bit環境を前提としたバッチファイルであることに注意が必要です。物理64bit環境でバッチファイルが32bitプロセスとして実行されてしまった場合でも、%windir%配下の実行体から64bitプロセスを起動するのが目的ですので、物理32bit環境で実行することを想定したバッチファイルではありません。
 手元にはすぐ動かせる物理32bit環境がもう無いため、物理32bit環境で実行させた場合の挙動については不明です*3。物理32bit環境も考慮したバッチファイルとするなら、PROCESSOR_ARCHITECTUREだけではなく、PROCESSOR_ARCHITEW6432も組み合わせた判定を行うなど、改良が必要だと思われます。
 



以上。

*1:またはエクスプローラからダブルクリック等

*2:サクラエディタの64bit版は存在するものの、十分にテストしていないことが謳われてます。

*3:Sysnativeが32bitバイナリを指していれば動きそうですけど、WOW64が動いてない時点でSysnativeエイリアスが存在するのか不明ですし。