やりたいこと
- 特定の契機でPDFを自動印刷したい。
これに尽きます。
そこで、Adobe Acrobat Reader DC(以下Adobe Reader)を使って実装を試みます。
つまづくこと
単純にpdfファイルがAdobe Readerが関連付けられている時、コンテキストメニューから[印刷]を行うと、印刷は行われるものの、Adobe Readerが閉じることなく残ってしまいます。
更に、関連付けがAdobe Readerとは限りません。
このことから、pdfファイルを直接実行したり、WinAPIのShellExecuteAを利用するような方法論は正解とは言えません。
そんなことを考えていると、下記も要件として求めるようになります。
- Adobe Reader は事前に起動しているなら起動したままにしたい。
- Adobe Reader は事前に起動していないなら終了したい。
Adobe Readerのウィンドウハンドルを持つプロセスは捕捉できない
Adobe Readerを起動すると、AcroRd32.exeというプロセスが2つできます。
このうちの1つは視覚的に見えているプロセス(ウィンドウハンドルを持つ)ですが、もう1つはウィンドウハンドルを持たないプロセスです。
Adobe Readerを起動させたり、pdfを開いたりした時に実行されているAcroRd32.exeは後者です。
色々検証していたら、イメージでは、後者が実行の入口であって、その後者から自分自身である前者のプロセスを作り上げて視覚的に表示しているようでした。
そのため、外部プログラムでは、ウィンドウハンドルを持つプロセスを把握することができません。
プログラムでpdfファイルの実行やAdobe Readerを起動させる実装を行った時、後者は起動したらすぐ入力待ち状態になり、終了するわけでもありません。
よって、『外部アプリケーションが終了するまで待つ』という方法論はムリです。
また、C#でいうところのProcess.WaitForInputIdle()も時間指定がなければ一瞬で帰ってきてしまうので意味がありません。
(時間指定はPC性能に依存するという意味で却下)
プログラムで後者をKillすると2つとも落ちますが、プロセスの状態が上記のようになるため待機することができず、起動を指示したコードの後続処理が連続で行われます。
Killするような実装を行っていると、視覚的な起動や印刷指示が行われるより前にKillされ、うんともすんとも言わなくなります。
AcroRd32.exeプロセス自体に注目して何とかしようとすると中途半端にAcroRd32.exeプロセスが1つだけ取り残されたり、泥沼にハマります。
あとIEでAdobe Readerを起動していたりすることも考えられるので、そもそもAcroRd32.exeのプロセスを見て、という方法論は破たんします。
どのプロセスが、プログラム実行によって利用しているAcroRd32.exeの2セットなの?が分からないわけです。
ウィンドウタイトルからプロセスを特定してはいけない
上記のようなプロセス問題にぶち当たった時、ウィンドウタイトルからハンドルを取得し、何とかしようと考えました。
- 何もファイルが開かれていない場合は[Adobe Acrobat Reader DC]
- ファイルが開かれている場合は[Test.pdf - Adobe Acrobat Reader DC]
のようにウィンドウタイトルが変化します。
ここに注目して、プログラムで起動を指示後、2.のウィンドウタイトルからウィンドウハンドルが取れるようになるまで無限ループで待ち続ける。
ウィンドウハンドルが取れたら起動したと見なして後続処理を行う。
しかしこれもPCの性能に依存することと、無限ループのせいでPC性能を奪われてやたら遅くなります。
更に、その無限ループのせいで応答待ち状態を食らい、システムの他機能が使えなくなるなどいいことがありません。
これもこの方法論で何とかしようとすると確実に泥沼にハマります。
コンテキストメニュー[印刷]って何やってんの?
視点を変えて、そもそもコンテキストメニューの[印刷]って何やってんのってところを調べました。
レジストリ[HKEY_CLASSES_ROOT\AcroExch.Document.DC\shell\Print\command]を確認すると、
["C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" /p /h "%1"]みたいな情報が入っています。
つまり、パラメーター[/p /h]を実行するとAdobe Readerで印刷するんだな、ということでした。
ただ、Adobe Readerは閉じない、と。
とりあえず私が調べて見つけたパラメーターを紹介しているページ。
Windows のコマンドラインから Acrobat や Adobe Reader を使用して印刷する方法
Adobe Reader 8.1の起動オプション(その2)
あと /h パラメーターは、 /p パラメーターとの組み合わせでないと意味がない様子。
どうすればAdobe Readerを閉じることができるのか
pdfをただキックしたり、Adobe Readerを単純に利用するだけではAdobe Readerを閉じることができません。
そこで、Adobe Readerには[AcroExch.Document]という名前で操作できるOLEオブジェクトがありました。
これを使って印刷を指示します。
これを行うには、下記手順が必要になります。
- レジストリからAdobe Readerのexeパスを取得する。
- OLEオブジェクトを生成する。
- パラメーターを指定したコマンド実行によって印刷を指示する。
もはやAdobe Readerを前提としていることから、実際は、レジストリからパスが得られなければNGだよ、といったチェックロジックも入ってくるでしょう。
Adobe Readerの機能を利用している点、わけのわからない捕捉をしてごちゃごちゃする必要がない点から、この方法論が最も最適ということになります。
コード
' レジストリ
' HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AcroRd32.exe から
' Adobe Acrobat Reader DC のexeパスを取得
Set shell = CreateObject("WScript.Shell")
path = shell.RegRead("HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\acrord32.exe\")
' 後続の印刷で使われるように、Adobe Acrobat Reader DC のOLEオブジェクトを生成
' 既に画面で起動している場合はAdobe Acrobat Reader DC の仕様上、空振りのようになる
' これがないと、印刷完了後に Adobe Acrobat Reader DC が閉じない
Set pdf = CreateObject("AcroExch.Document")
filePath = "D:\Test.pdf"
' Adobe Acrobat Reader DC を使って、pdfファイルを右クリックした
' コンテキストメニュー[印刷] と同じ動作をさせる(既定のプリンタで印刷)
' HKEY_CLASSES_ROOT\AcroExch.Document.DC\shell\Print\command と同じコマンド
' 引数:ファイルパス
' Adobe Acrobat Reader DC は表示されない
shell.Run """" & path & """ /p /h """ & filePath & """", , True
' プリンタを指定して印刷する
' HKEY_CLASSES_ROOT\AcroExch.Document.DC\shell\Printto\command と同じコマンド
' 引数:ファイルパス, プリンタ名, プリンタドライバ名, ポート名
' プリンタ名まで指定すれば大体いける、というかプリンタドライバ名、ポート名まで指定する意味とかがよくわからない
' 印刷が完了するまで Adobe Acrobat Reader DC が表示される
printerName = "FinePrint"
shell.Run """" & path & """ /t """ & filePath & """ """ & "" & printerName & """", , True
driverName = "FinePrint 6"
portName = "FPR6:"
shell.Run """" & path & """ /t """ & filePath & """ """ & "" & printerName & """ """ & driverName & """ """ & portName & """", , True
OLEオブジェクトの操作が可能な開発言語ならば、同じ流れを作り上げることで印刷の自動化を行うことができます。
とりあえずPowerBuilderでは実装可能でした。
これによって当初の目的であった『特定の契機でPDFを自動印刷したい』が叶うとともに、下記のように動作することで、Adobe Reader特有のウィンドウが残る現象も解消されます。
- 予めAdobe Readerが起動していないなら、Adobe Readerは閉じられる。
- 予めAdobe Readerが起動していたなら、ファイルは閉じるが、Adobe Readerは閉じられない。
視覚的にAdobe Readerが見えないパターン
最もスマートに印刷指示をかけるように思えます。
でも、PCが非力だったり、印刷に時間がかかるpdfファイルだった時、視覚的にAdobe Readerが起動しないパターンは、今どうなっているのかが分かりづらいです。
その結果、指示を連打する人もいるかもしれません。
視覚的にAdobe Readerが見えるパターン
どんな小さなpdfファイルでもAdobe Readerが一度は起動します。
でも、印刷に時間がかかるpdfファイルだった時、Adobe Readerが目に見えてきて応答待ち状態になるので、恐らく「ああ、頑張ってるんだな」と思ってもらえます。
まとめ
- Adobe Readerを利用してPDFを自動印刷したいなら、vbsなどのプログラムからOLEオブジェクトの利用およびコマンド実行で行う。
- 実行方法は2パターンある。
- Adobe Readerが視覚的に見えないパターン
- Adobe Readerが視覚的に見えるパターン
どちらのパターンを利用するかは良し悪しに思えるので、状況に応じてといったところでしょうか。
どの方法論であっても、Adobe Readerをキックしたらしっぱなしなので、『印刷が正しく行われたのか』とか、『印刷が完了するまで待つ』とか、『印刷が完了したのか』ということを把握することはできません。
古いAdobe Readerだとレジストリの位置が違うらしいです。
レジストリから Acrobat・Adobe Reader のバージョン情報を得る方法について(別解)(Windows 版 Acrobat/Adobe Reader/Acrobat Reader)
更につまづく者達
コマンドパラメーター『/p /h』はダメ
うまく印刷できる時とできない時がありました。
具体的には10ファイル印刷しろという指示であっても7ファイルしか印刷されないとか、1ファイルしか印刷されないとか。
恐らく印刷が終わる前にオブジェクトが破棄されることによっておかしな動作になるのだと思います。
プログラム言語によっては、OLEオブジェクトをグローバル変数的な持ち方をすれば回避できそうでしたが、そうするとAdobe Readerが閉じなくなります。
よって、作りを変えたりすると求めた挙動をしなくなるし不安定すぎるので、選択肢としてはチョイスしない方が良さそうでした。
Adobe Readerのアップデートが走るとダメ
上記のことから、コマンドパラメーター『/t』が正攻法でした。
『/t』は求めたファイル数を問題なく印刷してくれます。
しかし、ひとたびAdobe Readerのアップデートが動き出すとまともに印刷することはできなくなります。
具体的には10ファイル印刷しろという指示によって3ファイルまでは印刷できたが、4ファイルから印刷ができなくなるとか、Adobe Readerが閉じなくなります。
アップデートが終わればちゃんと動き出します。
そのため、アップデートによってそういう挙動を起こす可能性もある、というところは理解を得る必要があります。
これはコマンドパラメーター『/p /h』でも起こり得るのかもしれません。
この現象は、Adobe Readerの自動アップデート機能を無効にするという手を加えれば回避できるかもしれません。
アップデーターの自動チェックを変更する / 無効にする方法 (Acrobat/Reader DC)
説明の通り、レジストリ『HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Adobe\Acrobat Reader\DC\FeatureLockDown』に下記DWORD値を追加することで
アップデート機能を無効化させます。
種類 | 名前 | 値 |
---|---|---|
DWORD | bUpdater | 0 |
コマンドパラメーター『/t』は理解が必要
例えば以下の順で印刷指示を行ったとします。
- 1.pdf
- 2.pdf
- 3.pdf
- 4.pdf
しかし、印刷ジョブに流れてくる順序は下記だったりします。
- 3.pdf
- 2.pdf
- 1.pdf
- 4.pdf
具体的に調べてませんが、ファイルの内容や容量とかに左右されるのかもしれません。
これは印刷順序を厳密に定められた場合、もはや手立てがありません。
Adobe Readerの制限として理解してもらう必要がありそうです。