質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.35%
VBA

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

VBScript

VBScript(Visual Basic Scripting Edition)はMicrosftが開発したスクリプト言語であり、Visual Basicのサブセットです。

Q&A

解決済

1回答

12834閲覧

GetObjectでファイルを指定してオブジェクトを生成する方法について

SkyRocket

総合スコア26

VBA

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

VBScript

VBScript(Visual Basic Scripting Edition)はMicrosftが開発したスクリプト言語であり、Visual Basicのサブセットです。

0グッド

0クリップ

投稿2020/03/10 14:28

編集2020/03/10 14:37

「マクロが登録されているExcel」が起動中に
「VBScriptで書かれたバッチファイル」を実行することで、
その「マクロが登録されているExcel」のマクロを実行するプログラムを考えています。

GetObjectを用いて「GetObject(,"Excel.Application")」
のようにファイルを指定しない方法では上手く実現できましたが、
GetObject("D:\Book1.xlsm","Excel.Application") のように、
ファイルを指定すると、その後のRunの行で「オブジェクトがありません。」
と表示され上手くいきません。
GetOjbectを用いて、オブジェクト生成時に起動しているファイルを指定して実現したいのですが、どのようにしたら宜しいでしょうか。お力添え頂けますと幸いです。

<行いたいこと>
手順1.次のマクロを標準モジュール(Module1)に登録している、ファイル(Book1.xlsm)を「D:\」に配置し起動しておく。

vba

1Sub TestJob() 2 Range("A1") = 1 3End Sub

手順2.次の内容のtest.vbsを「D:\」に配置し「test.vbs」をダブルクリックする。
※ファイルを指定してGetObjectでオブジェクトを生成したいです。

vbscript

1On Error Resume Next 2Dim fileName 3fileName="D:\Book1.xlsm" 4 5Dim objExcel 6Set objExcel=GetObject(fileName,"Excel.Application") 7On Error Goto 0 8If objExcel Is Nothing Then 9 '起動していない場合の処理を記載予定 10 Wscript.Quit 11End If 12objExcel.Run "Book1.xlsm!TestJob" 13Set objExcel=Nothing

手順3.
「手順1で起動しているファイル(Book1.xlsm)」のA1セルに1が書き込まれる。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

「オブジェクトがありません」と言われる理由

まず、「オブジェクトがありません。」というエラーが出力されてしまうのは、 GetObject に失敗した場合、そのエラーによる停止を抑制しても Set 文の実行が完了しないため、 objExcel 変数の値が Empty のままになり Is Nothing での評価が行えないせい ですね。つまり、態々 On Error Resume NextGetObject のエラーを抑制している意味自体が無くなってしまって居りますから、代わりに IsEmpty(objExcel) で評価するか、 Dim objExcel をした直後に Set objExcel = Nothing として初期値を代入しておくと良いかと存じます。

EmptyNothing の差については良い解説記事が見当たらないため、御自身で御調べ頂きたいのですが、簡単に言ってしまえば、前者が「初期化していない Variant であり、 中身の型すら不明 である」ことを意味するのに対し、後者は「参照先のオブジェクトは 存在しないが、型はオブジェクト型 である」ことを意味しています。 Is 演算子は オブジェクト型変数の参照先が同一オブジェクトかどうか を判定しますから、ここに Empty 値を持ち込むと「そもそもオブジェクト型ではないから比較できないよ」というエラーが生じているという寸法です。 VBScript では全ての変数が Variant である、という VBA との違いを踏まえずに VBA 的な書き方をされているようにも御見受けされますので、もしそこに誤解があるようでしたら御注意下さいませ。

また、こういった形で、起こっている状況自体が分からなくなり、余計な不具合を持ち込んでしまう原因となる為、少なくとも開発中は エラー抑制は行わない ことを推奨致します。

GetObject でファイルを指定する方法

次に、何故 GetObject で特定の Excel ブックを対象としたオブジェクトを開けないのか、正しく開くにはどうすれば良いかなのですが、これは少々、背景の説明がややこしくなります。

結論としては、公式に Office automation server の GetObject および CreateObject の動作にて示されている様に、 Microsoft Office の automation を扱うのであれば ファイル名を指定する場合は、 ProgID を指定せずに GetObject を実行する 、つまり GetObject(fileName) とするのが基本的に正解となります。

但しこの場合、 「ファイルをダブルクリックする」のとほぼ同じ動作をする ため、「ファイルを指定した GetObject」自体には成功しても、「そのファイルが起動中であれば」という条件を満たすことは出来なくなります。もしも開いていない場合、勝手に新しいインスタンスを立てて該当のファイルを開いてしまうからです。

その点を踏まえた解決策は後に述べますが、このように「ProgID を指定しない」GetObject が適切と言えるのは、以下の 2 点が理由となります。

1) ProgID がそもそも異なる

実際に上記の通り、ファイルパスだけから GetObject をしてみると分かるのですが、 GetObject(,"Excel.Application") とは異なり、得られるオブジェクトは Application オブジェクト ではなく Workbook オブジェクト となります。

これは単純に、 ファイルと対応するオブジェクトは Application ではなく Workbook だから という事実から来ているだけなのですが、それ故に指定するべき ProgID は、少なくとも "Excel.Application" ではなくなります。指定するとしたら、 "Excel.Sheet" あるいは "Excel.SheetMacroEnabled" が適切です。

2) GetObject の内部動作が異なる

では "Excel.SheetMacroEnabled" を指定すればよいか? と言うと、そうでもありません。恐らく今回の要件で「正常に動作すべき条件」を整えて行うと、やはり GetObject に失敗してしまうはずです。これは Microsoft の GetObject の内部動作に関する解説 を御覧頂くと分かるのですが、実のところ、 ProgID を指定するか否かで、 GetObject の動作が大きく変わってしまう事が理由です。

簡単に言えば、 ProgID を指定しない場合は前述の通り、「ファイルが既に開いていればそのインスタンスを取得し、開いていなければ開く」 (COM 的には、ファイルモニカを使用して BindToObject を呼び出し、 Running Object Table に既知のオブジェクトがあるかを探しに行く) 一方で、 ProgID を指定した場合は「起動している Excel を使い、常にそのファイルを新しく開こうとする」 (COM的には、 ProgID から CoCreateInstance でオブジェクトを作成し、 IPersistFile.Load でファイルを読み込もうとする) という違いがあります。

この結果、仮に適切な ProgID を指定したとしても、それを指定した GetObject 呼び出しは、 既に同じ名前のファイルが開いている場合は Excel の「同じ名前のファイルは新しく開けない」という制限によって失敗してしまう ことになります。

ある Excel ブックが開いていることを確かめるにはどうすればよいか

結局のところ、単に GetObject にパスを指定するだけでは、仰るような「既にその Excel ブックが開いているとき」という条件判断を実現することが出来ません から、何らかの方法でそれを判定する必要があります。

残念ながら、これを確実に行う容易な方法は存在しないかと思われます。確実に行うには、実行されている全ての Excel のインスタンスを列挙する必要がありますが、 VBScript からはそれを実現する手段が存在しないからです。 GetObject で得られる "Excel.Application" インスタンスは「最初に見つかった」ものでしかなく、他に同じインスタンスが実行されていないことは保証されませんし、他にあり得るインスタンスを得る方法も存在しないかと存じます。

そのため、 こちらの Stack Overflow 記事 に解説が御座いますが、どうしても実現をしようとするならば、 Windows API を用いて次のステップで全ての Excel インスタンスを列挙する ことになります。同じような事は こちらの方も日本語でやっていらっしゃいます ね。

  1. FindWindowEx を使い、ウィンドウクラスが "XLMAIN" であるウィンドウを探す
  2. さらに、その中にある "EXCEL7" クラスのウィンドウを探す
  3. 見つかったウィンドウが開いているブックに対応するので、それぞれに対して AccessibleObjectFromWindow を呼び、 IDispatch を取得する

ただこれは、現行の Excel 本体のウィンドウ構造に依存している事や、 VBScript からは直接 WIndows API を扱えないために Excel 等、他のコンポーネント経由で Windows API を叩く ようなアクロバティックな手法が必要になる事から、あまり御勧めできるような方法でも御座いません。

現実的には、 「多くの状況で、 Excel のインスタンスは単一であろう」 という楽観的な前提の下、 GetObject した Excel の Application オブジェクトから該当のファイルが開かれているかを確認し、開かれていれば処理を実行する といった実装になってしまうかと思われます。仮に条件が整っているはずなのに失敗した場合、複数の Excel インスタンスが実行されているということですから、その状況を手動で解決してから再実行することになるでしょう。コードとしては、次のような形の事を言っています。

vbscript

1' 対象のファイルとマクロ名 2Dim fileName, macroName 3fileName = "D:\Book1.xlsm" 4macroName = "Book1.xlsm!TestJob" 5 6 7' Excel の Application オブジェクトを取得 8Dim objExcel 9On Error Resume Next 10Set objExcel = GetObject(, "Excel.Application") 11On Error Goto 0 12 13If IsEmpty(objExcel) Then 14 WScript.Echo( _ 15 "Excel インスタンスに接続できませんでした。" & _ 16 "Excel が実行されていることを確認してください。" _ 17 ) 18 WScript.Quit 19End If 20 21 22' 該当のファイルが開いているかを確認 23Dim objTargetWb, objFSO 24Set objFSO = CreateObject("Scripting.FileSystemObject") 25 26For Each workbook In objExcel.Workbooks 27 If objFSO.BuildPath(workbook.Path, workbook.Name) = fileName Then 28 Set objTargetWb = workbook 29 End If 30Next 31 32If IsEmpty(objTargetWb) Then 33 WScript.Echo( _ 34 "対象の Excel ブックが開いていることを確認できませんでした。" & _ 35 "Excel が一つだけ実行され、かつ実際にブックが開かれていることを" & _ 36 "確認してください。" _ 37 ) 38 WScript.Quit 39End If 40 41 42' マクロを実行する 43objExcel.Run macroName

どうしてもこういった運用での対応が許容できない場合は、前述のような Windows API を用いた実装にチャレンジしてみるのも宜しいかと存じます。

投稿2020/03/13 14:27

argparse

総合スコア1017

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SkyRocket

2020/03/14 06:21

丁寧にご教示頂きありがとうございます。とても良く分かりました。 >指定するとしたら、 "Excel.Sheet" あるいは "Excel.SheetMacroEnabled" が適切です。 このことについてだけ気になりました。「Set objExcel = GetObject(, "Excel.Application")」(※1)とするより、【「Set objExcel = GetObject(, "Excel.Sheet")」か「Set objExcel = GetObject(, "Excel.SheetMacroEnabled")」】と記載したほうが良いということでしょうか。 最後にご記載頂きましたソースコードには※1のコードが使われていて、そこだけスッキリできませんでした。
argparse

2020/03/15 00:31

`"Excel.Sheet"` や `"Excel.SheetMacroEnabled"` を指定すべきというのは、「ファイルを指定して `GetObject` する場合」に限った話をしています。その場合、 `GetObject` が返すのは特定のファイルの Workbook オブジェクトとなりますから、 ProgID もそれに型をそろえてやらなければなりません。 然しながら、結局「ファイルを指定して `GetObject` する」方法では、仰るような要件が満たせないことが分かったため、最後のコードでは特にファイルを指定せず、 ProgID に `"Excel.Application"` を指定し、起動している Excel を探して開いているファイルの一覧を列挙し比較する、というような処理を行っています。
SkyRocket

2020/03/18 10:29

返信が遅くなり大変恐縮です。詳細に知ることができました。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問