質問するログイン新規登録

Q&A

解決済

1回答

270閲覧

CreatePseudoConsole関数がエラーを返す

majiponi

総合スコア1723

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

0グッド

2クリップ

投稿2026/06/06 12:46

0

2

実現したいこと

リモートサーバーのMySQLに接続し、Excelのシートにインポートするマクロを書いています。
リモートサーバーのMySQLポートには直接接続できないため、SSHトンネルを確立し、ポートフォワーディングを行い、トンネル確立中にODBC接続でデータを取得しようと考えています。
実際にそのマクロを利用することになる一般職員のITスキルは低く、CUIの操作でトンネルを確立させることも難しいため、コンソール画面を出さず、GUI上で「秘密鍵ファイルの指定」「パスフレーズの入力」の2つだけ行わせて、MySQLからのインポートを実現したいと考えています。

(有料ソフトで解決するはずですが、予算が出ず…)

発生している問題・分からないこと

コンソールを出さないために、Windows APIのCreatePseudoConsole関数を使い、疑似コンソールを生成しようとしていますが、引数が不正であることを表す E_INVALIDARG (0x80070057) を返してきて、疑似コンソールの生成に失敗します。
(エラーコードの意味は下記のページを参照)
https://learn.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values

エラーメッセージ

error

1One or more arguments are not valid

該当のソースコード

VBA

1Option Explicit 2Option Base 0 3 4Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long 5Private Declare PtrSafe Function CreatePipe Lib "kernel32" (ByVal phReadPipe As LongPtr, ByVal phWritePipe As LongPtr, ByVal lpPipeAttributes As LongPtr, ByVal nSize As LongPtr) As Long 6Private Declare PtrSafe Function CreatePseudoConsole Lib "kernel32" (ByVal size As Long, ByVal hInput As LongPtr, ByVal hOutput As LongPtr, ByVal dwFlags As Long, ByVal phPC As LongPtr) As Long 7Private Declare PtrSafe Sub ClosePseudoConsole Lib "kernel32" (ByVal hPC As LongPtr) 8 9Private Const ssh_exe_path As String = "C:\Windows\System32\OpenSSH\ssh.exe" 10Private Const nullptr As LongPtr = 0 11 12Private pipe_to_child As LongPtr 13Private pipe_from_child As LongPtr 14Private hpcon As LongPtr 15Private ssh_process As LongPtr 16 17Private Sub Class_Initialize() 18 Dim stdin_for_child As LongPtr 19 Dim stdout_for_child As LongPtr 20 If CreatePipe(VarPtr(stdin_for_child), VarPtr(pipe_to_child), nullptr, 0) <> 0 Then 21 If CreatePipe(VarPtr(pipe_from_child), VarPtr(stdout_for_child), nullptr, 0) <> 0 Then 22 If CreatePseudoConsole(30 * 65536 + 80, stdin_for_child, stdout_for_child, 0, VarPtr(hpcon)) >= 0 Then 23 ' FIXME: ここに到達できない 24 CloseHandle stdout_for_child 25 CloseHandle stdin_for_child 26 Exit Sub 27 End If 28 CloseHandle pipe_from_child 29 CloseHandle stdout_for_child 30 End If 31 CloseHandle pipe_to_child 32 CloseHandle stdin_for_child 33 End If 34 Err.Raise 1004, , Err.LastDllError 35End Sub 36 37Private Sub Class_Terminate() 38 If ssh_process Then 39 Disconnect 40 End If 41 ClosePseudoConsole hpcon 42 CloseHandle pipe_from_child 43 CloseHandle pipe_to_child 44End Sub 45 46Public Sub Connect() 47 ' TODO: ssh.exeの起動処理を記入予定 48End Sub 49 50Public Sub Disconnect() 51 ' TODO: ssh.exeの終了処理を記入予定 52End Sub 53

C++

1// 参考:APIの挙動を確認するためにC++で書いたラピッドプロトタイプ 2 3#include <windows.h> 4#include <stdexcept> 5#include <string_view> 6 7HANDLE CreateProcessWOnPseudoConsole(HPCON hpcon, LPCWSTR lpCmdLine); 8bool CreateTunnel(HANDLE process, HANDLE pipe_from_child, HANDLE pipe_to_child); 9void DestroyTunnel(HANDLE process, HANDLE pipe_from_child, HANDLE pipe_to_child); 10 11bool isProcessAlive(HANDLE process); 12void readLineFromPipe(HANDLE pipe_from_child, char8_t* buf, size_t size); 13void writeLineToPipe(HANDLE pipe_to_child, const char8_t* str); 14void removeEscapeSequence(char8_t* str); 15 16constexpr LPCWSTR commandLine = L"" 17 "C:\\Windows\\System32\\OpenSSH\\ssh.exe " 18 "-L XXXXX:localhost:XXXXX (IPaddress) -p XXXXX " 19 "-i \"(key_path)\""; 20 21constexpr const char8_t* passPhrase = u8"xxxxxxxx"; 22 23int APIENTRY wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) 24{ 25 int ret = -1; 26 27 // sshの標準入力、標準出力と繋ぐパイプを生成 28 HANDLE stdin_for_child; 29 HANDLE pipe_to_child; 30 if(CreatePipe(&stdin_for_child, &pipe_to_child, nullptr, 0)){ 31 HANDLE stdout_for_child; 32 HANDLE pipe_from_child; 33 if(CreatePipe(&pipe_from_child, &stdout_for_child, nullptr, 0)){ 34 35 // sshを動かす疑似コンソールを生成 36 HPCON hpcon; 37 constexpr COORD size{80, 30}; 38 if(SUCCEEDED(CreatePseudoConsole(size, stdin_for_child, stdout_for_child, 0, &hpcon))){ 39 40 // sshを起動する 41 if(HANDLE process = CreateProcessWOnPseudoConsole(hpcon, commandLine)){ 42 43 // sshへ指示を送り、トンネルを確立する 44 if(CreateTunnel(process, pipe_from_child, pipe_to_child)){ 45 46 // TODO: 本番環境ではここでDB取得処理を書く 47 MessageBoxW(nullptr, L"Connecting.", L"", MB_OK); 48 ret = 0; 49 DestroyTunnel(process, pipe_from_child, pipe_to_child); 50 } 51 CloseHandle(process); 52 } 53 ClosePseudoConsole(hpcon); 54 } 55 CloseHandle(pipe_from_child); 56 CloseHandle(stdout_for_child); 57 } 58 CloseHandle(pipe_to_child); 59 CloseHandle(stdin_for_child); 60 } 61 return ret; 62} 63 64// 以下省略 65

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

元々私はC++が専門で、Windows APIを使ったプログラミングもC++言語で長く経験を積んできたので、まず、C++言語でAPIの挙動を確認するためのラピッドプロトタイプを作成しました。
ラピッドプロトタイプは無事に完成し、サーバーへの接続、接続中のDB更新も上手くいきました。そのため、VBA向けに移植して、目的のマクロを記述しようとしたのですが、VBAでCreatePseudoConsole関数を使うと、なぜかエラーが出る現象に直面しました。

エラーコードから、引数の指定に問題があると考え、CreatePipe関数も含めて、合計3回分のAPI呼び出しの引数とメモリ配置をログファイルにダンプしてみましたが(後述1)、ラピッドプロトタイプとVBA版どちらも、同じ対応関係で変数を渡しており、どこを間違えたのか自分では気が付くことができませんでした。

Geminiにも相談し、上手くいかない原因の候補を5つ挙げるようプロンプトを書いたのですが、引数の順番、構造体の値渡しのエミュレート、プロトタイプ宣言、パイプハンドルの継承設定、引数のアライメント(後述2)、どれも再確認すれど、原因の特定には至りませんでした。

※1:ログファイルは、次の手順で作成しました。

  1. 呼ばれた関数と全く同じプロトタイプ型を持ち、メモリアドレスと中身、ポインタならその中身をログファイルに書き出すダミー関数を用意する(本物の関数を呼び出し、入出力をspyする)。
  2. その関数をDLL化して、ラピッドプロトタイプとVBA版の両方に読み込ませる。
  3. それぞれの版で、ダミー関数を呼び出すよう切り替える。

※2:プロトタイプ版で逆アセンブルして、8バイト境界であることを確認、VBA版の引数をすべて8バイト単位になるよう書き換えたものの上手くいかず。

補足

使用環境は下記の通りです。
Windows 11 Home 25H2
Visual Studio Community 2026
Microsoft Office Home and Business 2021

お手数をおかけしますが、ご知見のあります方がいらしましたら、ご教示いただけますと幸いです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

hiroki-o

2026/06/07 05:45

試したわけでなく、質問を読んだだけの意見ですが、以下は検討対象外でしょうか? ・ラピッドプロトタイプをVBAから呼び出す ・GUIもExcel変換も含めて全部C++で作る
setoppu

2026/06/07 07:53

試したわけでもないですが… >If CreatePseudoConsole(30 * 65536 + 80, stdin_for_child, stdout_for_child, 0, VarPtr(hpcon)) >= 0 Then 第1引数は構造体を渡すはずですが、この渡し方でVBAはよろしく変換してくれるんでしょうか?
hiroki-o

2026/06/07 08:03

あー、それですね。気づかずに変なコメントを書いてしまいました。 typedef struct _COORD { SHORT X; SHORT Y; } COORD, *PCOORD;
majiponi

2026/06/07 10:03

第一引数の構造体についてですが、VBAでは構造体の値渡しがそもそもできないらしく、ByValで宣言すると文法エラーになります。プロトタイプを逆アセンブルして確認したところ、第一引数はecxレジスタに4バイトの値をセットして渡しており、同じ効果を出すためにLong型の引数として宣言し、同じビット列が渡されるような値を計算して渡しています(同じビット列が渡されていることはメモリダンプで確認済み)。
hiroki-o

2026/06/07 11:00

Excelから疑似コンソールを作り出すこと自体が難しいのかもしれませんね。 CreatePseudoConsoleではなくCreateProcessでは駄目なのでしょうか? 外部プログラムの実行と、入出力にパイプを使う点は、CreateProcessでも可能だと思います。
majiponi

2026/06/07 15:00

実はこのプロトタイプを作る前に一度、CreateProcessで直接起動するのを試したんです。が、 Pseudo-terminal will not be allocated because stdin is not a terminal という別のエラーが出たんですよ。ユーザーインタラクティブに動作させる必要があって、通常のパイプを標準入力、標準出力に当てる方法だと正しく動作せず、それで疑似コンソールを作る方法を試しています。
stockflowlab

2026/06/09 07:20 編集

Cでは、 BOOL CreatePipe( PHANDLE hReadPipe, PHANDLE hWritePipe, ... ); なので、宣言を試しに変えてみるのはどうですか? Private Declare PtrSafe Function CreatePipe Lib "kernel32" ( _ ByRef phReadPipe As LongPtr, _ ByRef phWritePipe As LongPtr, _ ByVal lpPipeAttributes As LongPtr, _ ByVal nSize As Long _ ) As Long また、エラー時の各変数の表示をするとどうなりますか? hr = CreatePseudoConsole( _ 30 * 65536 + 80, _ stdin_for_child, _ stdout_for_child, _ 0, _ VarPtr(hpcon)) Debug.Print "HRESULT = &H" & Hex$(hr) Debug.Print "stdin = &H" & Hex$(stdin_for_child) Debug.Print "stdout = &H" & Hex$(stdout_for_child) Debug.Print "hpcon = &H" & Hex$(hpcon)
hiroki-o

2026/06/09 10:19

stockflowlabさん ByRefに変えたらパイプのハンドルが定義されない(ゼロ)だけで、戻りが0x80070057なのは同じですね。 そもそも、Excelから疑似コンソールを作り出せるのか、何か見解はありますか?
AkitoshiManabe

2026/06/09 21:41

まともな回答ではないので、追記欄で失礼します。私も気になり、Google検索AIと対話してみたところ Excel VBAだけでCreatePseudoConsoleを実現するのは現実的ではないと、理由を3点挙げられました。 1. VBAは単一のスレッド(シングルスレッド)で動くためフリーズが懸念される 2. VBAは32bit/64bit環境でポインタの扱いが異なり、Excelの強制終了が懸念される 3. VBAは Windowsの「コールバック(イベント通知)」を高度に制御するのが苦手 代替案として、「WScript.Shellオブジェクトを使うこと」を提案されました。 「コンソールを出さないため」 というのが目的であれば、「WScript.Shellオブジェクトを使う」という代替案があるとのことです。
guest

回答1

0

自己解決

多くの皆様からコメントを頂き、ありがとうございました。
大変申し上げにくいのですが、今回は、「原因不能だがおそらく不可能」という結論とさせていただきます。
AkitoshiManabe様の仰るように、シングルスレッドという性質上推奨されないことは承知しておりながら、会社の指示ということで設計しておりましたが、昨日、開発方針が変わり、利用するDBの置き場サーバーを変更し、VPN接続が必須化されることになりました。
VPNの接続はもはやExcelのアドインとssh.exeでは解決できないレベルのため、VPNのためにソフトウェアを入れることになったのですが、そのインストール時に専用のバッチファイルを入れ、コマンドラインからトンネルのonをした上でexcelに取り込みを行わせることになりました。
ここまでご協力いただいた皆様には申し訳ございませんが、vbaにこだわる必要がなくなったこと、何卒ご理解いただけますと幸いです。

投稿2026/06/13 11:11

majiponi

総合スコア1723

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.25%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問