Excelのマクロやアドオンなどを利用する際に「これ使っても大丈夫なんだよね」と気になる事は無いでしょうか?
ではそうなると、「悪くない安全なマクロやアドオンとは?どのようなコーディンクになるのか?」という疑問が沸いてくるのですが、Microsoft ドキュメントの中を探してみても、これに答えてくれるページを見つける事はできませんでした。
そこでシリーズで「Excel VBAコーディングの安全性」について考えて行く事にしました。
シリーズ五回目の今回は、「実行可能プログラムの実行(外部アプリケーションの起動)」について取り上げます。
VBAの標準関数以外にWindows Shellの実装に関してはいくつかのバリエーションが存在します。
なお、これまでと同様に追加モジュールや環境設定は必要としない前提でご説明いたします。
※動作は32bit版Excel 2016と64bit版Excel 2021の バージョン2312(ビルド 17126.20132)を使用して検証しています。
実行可能プログラムの実行(外部アプリケーションの起動)のバリエーション
冒頭にも書きましたがいつかの方法を一覧表にまとめて見ました。
すべてを網羅しているとは言い切れませんがWeb上で探すと見つかるものを列挙していますが、デフォルト環境のままで使えるやり方に限定しています。
No | 名前 | ライブラリ/ 参照設定 | 種類 | 機能など |
---|---|---|---|---|
1 | ShellExecute | shell32.dll | win32 | 指定されたファイルに対して操作を実行 |
2 | Shell.Application | shell32.dll/ Microsoft Shell Controls And Automation | comクラス | 同上 |
3 | Wscript.Shell | wshom.ocx/ Windows Script Host Object Model | comクラス | C:\Windows\SysWOW64(windows10ベース) C:\Windows\System32(windows11ベース) |
4 | Shell | Visual Basic For Applications | 関数 | 実行可能プログラムを実行 |
本題に入る前に、ここでバッチファイルとスクリプトファイルについて簡単にまとめて置きたいと思います。
というのも「指定されたファイルに対して操作を実行」する際にバッチファイルとスクリプトファイルを避けて通る分けにはいかないためです。
バッチファイルとスクリプトファイル
バッチファイルとスクリプトファイルの違いについては、2006年に書かれている@IT(at mark IT)の「第1回 WSHを始めよう」の連載の最初のページでまとめて説明されていますが、その内容を簡単にもとめます。
- バッチファイルはMS-DOSの時代から存在していて、基本的には記述したコマンドやアプリケーションを出現順に実行するためのものです。
- Windows 2000になってバッチ処理機能はかなり強化されて複雑な処理ができるようになったのですが、もともと単純だったバッチ・ファイルに複雑な処理をさせようとしたため、その構文は直感的ではなく複雑なものになっています。
- Windows Script Host(以下WSH)は、Windows 98からWindows OSに搭載された、ある決まった一連の手続き(スクリプト)をテキスト・ファイルに記述したもの(スクリプト・ファイル)を実行する「スクリプト実行環境」になります。
- WSHには標準で2つのスクリプト言語のエンジンが含まれています。
- VBScript」と「JScript(マイクロソフト版JavaScript)」
そして上記の参考記事の中でもう一つのスクリプトファイルとしてPowerShellをつぎのように紹介されています。
将来的にWSHは、Windows PowerShell(PS、旧Microsoft Command Shell= MSH)にスクリプト実行環境のバトンを渡すことになる。
第1回 WSHを始めよう
この記事に書かれた内容がようやく現実に成ろうとしています。
PowerShell でのスクリプト実行
PowerShellはコマンドラインツールPowerShell.exeを使用する事でセッションを開始する事ができますが、PowerShellプロファイルや実行ポリシーが正しく設定されていないとエラーになってしまいます。
そのためにプロファイルなしで PowerShellセッションを開始する、PowerShell.exeのNoProfileパラメーターと特定の実行ポリシーで開始するためのExecutionPolicyパラメータを使用します。
詳細は下記のMicrosoft Learnのページに掲載されていますのでご参照いただければ幸いです。
なおExecutionPolicyの実行ポリシーの内容は下記で説明されています。
※今回のコーディング事例では実行ポリシーは “Bypass”を使用しています。
Windows Script Host(WSH)の終焉に向けた動き
バッチファイルとスクリプトファイルは実行可能プログラムであるEXEファイルとは異なりテキスト形式の情報があれば良いので、悪意のあるプログラムで利用されると危険度が高まります。
そのためWindows Script Host(WSH)というWindows98からWindows OSに搭載されたスクリプトファイルの中の1つのVBscriptについては、Microsoftから2023年10月10日に非推奨の機能としてつぎのように発表されています。
VBScript は、将来の Windows リリースで廃止される前に、オンデマンドの機能として利用できるようになります。最初は、VBScript の廃止に備えて中断なく使用できるように、VBScript オンデマンド機能がプレインストールされます。
「非推奨の機能に関するリソース」(英語を翻訳)
先程ご紹介した関連記事が書かれた2006年から18年経って、ようやくWindows Script Host(WSH)は終焉に向かって一歩進み始めた事になります。
以上を踏まえて、今回のコーディング事例ではPowerShellを実行するサンプルでご紹介いたします。
PowerShellでHTTP|S通信をするには
実はPowerShellに詳しいわけではないので、実際に動かせるスクリプトが無いかGoogleで検索したところ下記のQiitaのサイトが上位に表示されていたのでご紹介いたします。
このなかでInvoke-WebRequestコマンドレットでGETメソッドを使用するサンプルコーディングが掲載されていましたので今回はこのスクリプトファイルを使って動作確認して見たいと思います。
手抜きのようで恐縮ですが、掲載されているコーディング内容をコピーして、これからご紹介するコーディング事例を保存するXlmxファイルの格納場所と同じフォルダーに”powershell1.ps1″というファイル名で保存して置いてください。
なおこのコーディングが正しい動きをする事は確認していますが、8行目のコンテンツをファイルに保存するフォルダーはご自身の環境に合わせて変更をしてください。
※そのままだと’D:\tmp\out.txt’になります。
win32のShellExecute関数での実行可能プログラムの実行
話を元に戻します。
本シリーズは「Excel VBAコーディングの安全性」をテーマにしておりますが、win32APIの使用についてはすべてを掘り下げてご説明するのは難しいと感じていますが、ただ危険性の高い関数につきましては取り上げていて、ShellExecute関数もそのうちの一つです。
コーディング事例
FTPでは接続時にユーザーid・パスワードを設定するのでご注意ください。
- ShellExecuteA([1],[2],[3],[4],[5],[6])関数
※ FTPサーバーからファイルを取得してローカルに指定したファイル名で保存します。- [1]:親ウインドウのハンドル値をセットします。
- [2]:実行するアクションを指定
- edit:編集用にドキュメントを開く
- explore:指定されたフォルダーを探索
- find:指定されたディレクトリから検索
- open:ファイルまたフォルダーを開く
- print:指定されたファイルを印刷
- runas:管理者としてアプリケーションを起動
- NULL:デフォルトのアクションが使用される
- [3]:指定されたアクションを実行するファイルまたはオブジェクト
- [4]:アプリケーションに渡されるパラメータ
- [5]:アクションのデフォルトの作業ディレクトリを指定。nullの場合は現在の作業ディレクトリ
- [6]:アプリケーションをどのように表示するかを指定
- 戻り値はHINSTANCE型
- 関数が成功すると32よりおおきな値が返る。
この関数の説明はデフォルト英語でかつC++言語向けですが下記Microsoft Learnのページに掲載されています。
win32APIのDeclareステートメントを標準モジュールの先頭でコーディングします。
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _ (ByVal hwnd As LongPtr, ByVal lpOperation As String, ByVal lpFile As String, _ ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
先の章でご説明した”powershell.ps1″を実行するコーティング事例になります。
Private Sub ShellExecute1() Const SW_HIDE As Long = 0 'ウィンドウ非表示 Const SW_SHOWNORMAL As Long = 1 '通常の状態で開く Dim lrt As Long lrt = ShellExecute(0, "runas", "powershell", "-NoProfile -ExecutionPolicy Bypass " & ThisWorkbook.Path & "\powershell1.ps1", vbNull, SW_HIDE) Debug.Print lrt End Sub
- 2番目の実行するアクションは”runas”をセットしています。
- 4番目のアプリケーションに渡すパラメータにNoProfileパラメーターと実行ポリシーの”Bypass”を設定しています。
- 6番目のパラメータは”0″のウィンドウ非表示にしてあります。
なおこのコーティング事例を実行すると左画面の「このアプリがデバイスに変更を加えることを許可しますか?」の確認メッセージが表示されて、「はい」をクリックしないと動作しません。
左図ではPowerShellが警告を出しているように見えますが、実際はShellExecute関数が表示させているメッセージになります。
※ちなにみnotepad.exeを実行しようとするとPowerShellではなくメモ帳が表示されます。
という事でShellExecute関数は、このブロックを解除させないと実行できないような制限がかけられているようです。
Shell.Applicationでの実行可能プログラムの実行
つぎにShell.Applicationでの場合をご説明いたします。
MFC(Microsoft oundation Class)ライブラリでは、スクリプト言語からWindowsシェルを操作するためのオートメーションオブジェクト(プログラム可能なオブジェクト)が提供されています。
※MicrosoftドキュメントのMFCの概念のオートメーションを参照しています。
そのために、わざわざwin32のShellExecute関数を使用しなくても同様の機能がMicrosoft Visual BasicのShellオブジェクトを使えば実装する事ができます。
このShellオブジェクトのメソッドのなかには、“ShellExecute”のメソッドがあるので、これを使用すれば実行可能プログラムを実行させることができます。
パラメータの内容は、win32の場合とほぼほぼ同じです。
- iRetVal = Shell.ShellExecute([1],[2],[3],[4],[5])
- [1]:実行するファイルの名前
- [2]:操作のパラメーター値を含む文字列
- [3]:指定されたファイルを含むディレクトリの完全修飾パス
- 指定しない場合は、現在の作業ディレクトリ
- [4]:実行する操作
- オブジェクト動詞を参照(Microsoftドキュメント)
- [5]:アプリケーション ウィンドウを最初に表示する方法
Shell.ShellExecuteはCALLステートメントで呼び出す事ができるためか、戻り値については記載がありません。
“powershell.ps1″を実行するコーティング事例になります。
Sub shellApp1() Const SW_HIDE = 0 'ウィンドウ非表示 Dim rt Dim sh As Object Set sh = CreateObject("Shell.Application") rt = sh.ShellExecute("powershell", "-NoProfile -ExecutionPolicy Bypass " & ThisWorkbook.Path & "\powershell1.ps1", "", "runas", SW_HIDE) Debug.Print rt End Sub
実行のために必要な設定で、”runas”やSW_HIDEなど両者に違いはありません。
また、前章と同様に「このアプリがデバイスに変更を加えることを許可しますか?」の確認メッセージが表示されて、「はい」をクリックしないと動作しません。
Wscript.Shellでの実行可能プログラムの実行
先の章でWindows Script Host(WSH)の終焉に向けた動きをご説明しましたが、まだしばらくは動く環境が残ると思いますので、コーディンク事例をご紹介いたします。
MicrosoftドキュメントでWSHを説明しているページはあまり残されていませんが、現時点でも見る事ができるページをご紹介いたします。
下記はWSHで”powershell.ps1″をexecメソッドで実行するコーティング事例になります。
Sub Wscript1() Dim sh As Object Dim obj_rt As Object Set sh = CreateObject("Wscript.Shell") Set obj_rt = sh.exec("powershell -NoProfile -ExecutionPolicy Bypass " & ThisWorkbook.Path & "\powershell1.ps1") Debug.Print obj_rt.Status End Sub
なおWSHでは戻り値を取得しない場合はrunメソッドを使用する事ができます。
Sub Wscript1_1() Dim sh As Object Set sh = CreateObject("Wscript.Shell") sh.Run ("powershell -NoProfile -ExecutionPolicy Bypass " & ThisWorkbook.Path & "\powershell1.ps1") End Sub
WSHでは実行するファイル以外にパラメータも無く、また「このアプリがデバイスに変更を加えることを許可しますか?」の確認メッセージも表示されずに実行されてしまいます。
この辺りのガードの低さが、WSHを終焉させる一つの要因になっているのかもしれません。
VBAのShell関数での実行可能プログラムの実行
最後にVBAの標準のShell関数でのコーティング事例をご紹介いたします。
なおMicrosoftドキュメントのShell関数のページは下記になります。
Shell関数の戻り値は成功した場合はプログラムのタスクIDを表すVariantの値が返り、失敗した場合は0を返します。
Shell関数で”powershell.ps1″を実行するコーティング事例になります。
※ウィンドウのスタイルを指定する引数が存在します。
Sub Shell1() Dim rt rt = Shell("powershell -NoProfile -ExecutionPolicy Bypass " & ThisWorkbook.Path & "\powershell1.ps1", vbHide) Debug.Print rt End Sub
ウインドウのスタイルのパラメータはありますが、「このアプリがデバイスに変更を加えることを許可しますか?」の確認メッセージはWSHと同様に表示されずに実行されてしまいます。
「VBAの標準関数には制約を加え難い」といった事なのかもしれません…
「実行可能プログラムの実行」では何を確認するべきか?
「言わずもがな」になりますが、それぞれのやり方で「何のプログラムを実行するか?」を確認する必要があります。
ただそれが変数に格納されている場合は、なかなか何を実行しているか?明確に判定する事が難しいケースが存在します。
そうなると直前でステップ実行を止めて変数の中身を確認する事になりますが、なかなかリスキーな話です。
最悪何が起こっても大丈夫なオフライン環境のパソコンを用意して確認するなどの対応が必要になるかもしれません…
今回のまとめと次回
今回は実行可能プログラムの実行(外部アプリケーションの起動)の方法を4つご紹介しましたが、コーディング事例としてはコーディング量が短くて目新しい情報は少なかった事と存じます。またその内の1つは終焉が発表されたVBScriptの事例でした。
ただ、今回のコーディングを見逃してしまうと、悪意のあるプログラムにとっては世界に向けて大きなダメージを与える事が出来てしまうので注意が必要です。
過去3回ほど第一回に掲載いたしましたCOMクラスに関係するコーディング事例をご説明してきましたが、データベース接続を除いて大きな脅威になりそうなものは一通りご紹介いしたつもりです。
そこで次回はVBAの中の「オブジェクト モデル」や「ステートメント」などにおける注意すべき点をご紹介したいと思います。
※COMクラスのように世界に向かって悪意を働かせるということは難しいのですが、動作しているパソコン環境に影響を与える事は十分に可能です。
以上最後までご一読いただき誠にありがとうございました。