MENU

【Office Scripts移行】別ブックデータ取得の下準備|マーカー検索でセル番地を記録するVBAをAIで変換する

VBAのFindメソッドをOffice Scriptsに移行する方法を解説|Office Scripts
目次

はじめに

【Excel VBA】別ブックからデータを抜き出す処理(その1)では、「取得セル設定」シート上に置いたマーカー文字列(#1#2…)をCells.Findで検索し、見つかったセル番地を別シートに記録する処理を解説しました。この番地は、続く「その2」で別ブックを開いてデータをピンポイントで抜き出す際の「地図」として使われます。

今回はこのその1にあたる処理を生成AIでOffice Scriptsに変換し、実際にExcel on the webで動かして検証します。まだ別ブックを開く処理は登場しないため、PAD(Power Automate for Desktop)は使わず、Office Scripts単体での検証です。

なお、本記事では検証用のサンプルファイルは提供していません(次回その2で提供予定です)。同じシート構成(「Main」シート・「取得セル設定」シート)で試したい場合は、元記事「別ブックからデータを抜き出す処理(その1)」内でダウンロードできるVBA版のサンプルファイルをご利用ください。

注意: 本記事は実際にExcel on the webで手を動かして検証しています。AIが最初に出したコードはそのままでは動かず、2回のエラーを経て修正しています。つまずいた点もそのまま掲載します。

今回の検証結果(先に結論)

項目結果
VBAのCells.Findに相当するメソッドWorksheetにもRangeにも直接のFind系メソッドは存在しなかった(詳細は後述)
マーカー文字列からのセル番地取得✅ Find系APIを使わず、値の配列を自前でスキャンする方式で実現できた
セル番地の表記形式⚠️ getAddressLocal()はシート名付きで返るため、文字列処理での加工が必要だった
スクリプトのボタン割り当て✅ 「挿入」→「ボタン」から可能。VBAと同様の操作感で実行できる

要点: VBAのFindメソッドは非常によく使われますが、Office Scriptsには同名・同用途のAPIがそのまま存在しません。AIが生成したコードも実在しないメソッドを使っていたため、値配列を自前でスキャンする方式に置き換えることで解決しました。

元になるVBAコードのおさらい

その1のVBAコードは、以下のように「Main」シートの設定(マーカー記号・設定シート名)を読み取り、設定シート内をマーカー記号で検索してセル番地を記録します。

'ターゲットのセル番地を取得する処理
'同じ符号を複数付けた場合最初に見つかった1個だけになります
Sub GetTargetCells()
    Dim strTarget As String
    Dim strMark As String
    Dim i As Long, m As Long
    Dim FCell As Range
    Dim strFind As String
    Dim shSeting As String

    With WorkSheets("Main")
        shSeting = .Range("F2") 'Target設定用シート名
        .Range("C:D").Clear     'Targetセル番地保存エリアをクリア
        strMark = .Range("B2")  'Targetの設定用符号をセット
        Sheets(shSeting).Activate 'Target設定用シートをアクティブに
        m = Application.CountIf(Cells, strMark & "*")   'Targetの数を調べる
        For i = 1 To m
            strTarget = strMark & i '符号+番号
            Set FCell = Cells.Find(What:=strTarget) 'Findメソッド
            If FCell Is Nothing Then
                strFind = "見つかりません"
            Else
                strFind = FCell.Address 'Targetのアドレス代入
            End If
            .Cells(i, 3) = strTarget
            .Cells(i, 4).Formula = strFind
        Next
        .Activate
    End With
End Sub

ポイントは3つです。

  • Application.CountIf(Cells, strMark & "*"):ワイルドカードで「マーカー記号から始まるセル」の数を数える
  • 符号+番号で順番にFindメソッドでセル番地を取得し、シートに「符号+番号」と「セル番地」を書き込んでいく
  • Cells.Find(What:=strTarget):見つからなければNothingが返る。この分岐でエラーを防いでいる

生成AIにOffice Scripts変換を依頼する

第4回で得た知見(AIは「動きそうなコード」を出すが、Office Scripts特有のAPI差異を把握していないことがある)を踏まえ、以下を明示してAIに変換を依頼しました。

  • 対象VBAコードと処理の目的(マーカー符号をFindで検索し、セル番地を記録する)
  • Office Scripts(TypeScript/ExcelScript API)への変換であること
  • Cells.Findが見つからない場合Nothingを返す挙動を、Office Scripts側でどう置き換えるか

1回目:AIの変換結果でエラー

AIはWorksheetに対してfindOrNullObjectを呼び出すコードを生成しましたが、実行するとエラーになりました。

1回目のAI変換コードの実行時エラー
Property 'findOrNullObject' does not exist on type 'Worksheet'.

findOrNullObjectWorksheetではなくRangeのメソッドだったため、getUsedRange()で取得したRangeに対して呼び出す形にAIがコードを修正しました。

2回目:正しい作法で呼び出しても別のエラー

findOrNullObjectは本来Rangeに対して呼び出すのが正しい作法です(Microsoft Learn: Excel アドイン用 JavaScript APIのドキュメントにも記載があります)。そこでgetUsedRange()で取得したRangeに対して呼び出すよう修正しましたが、今度は次のエラーになりました。

2回目のAI変換コードの実行時エラー
Property 'findOrNullObject' does not exist on type 'Range'.

ここで気づいたのは、上記のMicrosoft Learnのドキュメントは「Excel アドイン用 JavaScript API」(Excel名前空間)のものであり、今回使っているOffice Scripts(ExcelScript名前空間)とは別のAPIセットだという点です。両者は似ていますが同一ではなく、アドイン用APIのメソッドがすべてOffice Scriptsに移植されているわけではありません。正しい作法(Rangeに対して呼び出す)で試しても存在しないエラーになったことから、少なくとも今回の検証環境では、Office Scriptsの型定義にfindOrNullObject自体が含まれていないと判断しました。

3回目:Find系APIを使わない方式に変更して成功

方針を変え、Find系のメソッドを一切使わず、getValues()で取得済みの値配列を自前でスキャンしてマーカー文字列とセル番地の対応表(Map)を作る方式にしたところ、正常に動作しました。

function main(workbook: ExcelScript.Workbook) {
  const mainSheet = workbook.getWorksheet("Main");
  const settingSheetName = mainSheet.getRange("F2").getValue() as string;

  // Target番地保存エリア(C:D列)をクリア
  mainSheet.getRange("C:D").clear(ExcelScript.ClearApplyTo.contents);

  const mark = mainSheet.getRange("B2").getValue() as string;
  const settingSheet = workbook.getWorksheet(settingSheetName);
  settingSheet.activate();

  const usedRange = settingSheet.getUsedRange();
  const values = usedRange.getValues();

  // マーカー文字列 → セル番地 のマップを、値配列を自前でスキャンして作る
  const addressMap = new Map<string, string>();
  for (let r = 0; r < values.length; r++) {
    for (let c = 0; c < values[r].length; c++) {
      const cellValue = values[r][c];
      if (typeof cellValue === "string" && cellValue.indexOf(mark) === 0) {
        const fullAddress: string = usedRange.getCell(r, c).getAddressLocal();
        // "取得セル設定!A1" のようにシート名が付くため、"!"より後ろだけを取り出す
        const address: string = fullAddress.includes("!") ? fullAddress.split("!")[1] : fullAddress;
        addressMap.set(cellValue, address);
      }
    }
  }

  const m = addressMap.size;
  for (let i = 1; i <= m; i++) {
    const target = `${mark}${i}`;
    const address: string = addressMap.has(target) ? addressMap.get(target) : "見つかりません";
    mainSheet.getRange("C" + i).setValue(target);
    mainSheet.getRange("D" + i).setValue(address);
  }
}

実際に動かして検証する

「Main」シートにマーカー記号()と設定シート名(取得セル設定)を設定し、「取得セル設定」シートに★1★12をバラバラに配置した状態で実行しました。

1回目・2回目の実行:上記の通りエラーで停止

3回目(自前スキャン方式)の実行:「スクリプトが正常に実行されました。」と表示され、C列・D列に★1★12とそれぞれの取得セル番地のペアが正しく書き出されました。

ただし、D列の値が取得セル設定!A1のようにシート名付きで書き出されていました。VBA版の.Addressはシート名を含まないA1形式だったため、"!"で文字列を分割してシート名部分を取り除く処理を追加し、最終的にA1C2のようなシート名なしの番地で記録できることを確認しました。

3回目AI変換コードで正常に実行されたが問題が発覚した画像

さらに2つの課題が見つかった

エラーなく動作するようになった後、実機であらためて確認したところ、VBA版との挙動の違いが2つ見つかりました。

課題1:「Main」シートで実行したのに「取得セル設定」シートが開いたままになる

VBAの元コードを見直すと、Sheets(shSeting).Activateで設定シートに切り替えた後、処理の最後に.ActivateWith WorkSheets("Main")ブロック内なので実質WorkSheets("Main").Activate)でMainシートに戻す処理が入っていました。Office Scripts版はこの「戻す」部分が抜けており、実行後に「取得セル設定」シートが開いたままになっていました。

課題2:セル番地が相対参照(A1)になっている

VBA版のFCell.Addressは既定で絶対参照形式($A$1)を返しますが、Office ScriptsのgetAddressLocal()は相対参照形式(A1)で返ってきます。

一度目の修正:処理の最後にMainシートへ戻す

VBAと同じく、処理の最後にmainSheet.activate()を追加し、絶対参照への変換も加えて再検証しました。動作上は問題ありませんでしたが、ここで実機ならではの気づきがありました。VBAはシート切り替えが一瞬で終わるため気にならなかったのに対し、Office Scriptsはシート遷移が画面上ではっきり見えてしまい、体感的に気になるという点です。

最終的な修正:そもそもシートを切り替えない

Office ScriptsはWorksheetRangeオブジェクトに対して直接値の読み書きができるため、画面上でどのシートが表示されているかに関わらず処理が可能です。VBAのように処理対象のシートをActivate/Selectする必要はありません。この点を踏まえ、settingSheet.activate()も含めてシート切り替えの処理自体を撤去し、画面がMainシートから一切動かないまま処理が完了する形に修正しました。

function main(workbook: ExcelScript.Workbook) {
  const mainSheet = workbook.getWorksheet("Main");
  const settingSheetName = mainSheet.getRange("F2").getValue() as string;

  // Target番地保存エリア(C:D列)をクリア
  mainSheet.getRange("C:D").clear(ExcelScript.ClearApplyTo.contents);

  const mark = mainSheet.getRange("B2").getValue() as string;
  const settingSheet = workbook.getWorksheet(settingSheetName);

  const usedRange = settingSheet.getUsedRange();
  const values = usedRange.getValues();

  // マーカー文字列 → セル番地(絶対参照形式)のマップを、値配列を自前でスキャンして作る
  const addressMap = new Map<string, string>();
  for (let r = 0; r < values.length; r++) {
    for (let c = 0; c < values[r].length; c++) {
      const cellValue = values[r][c];
      if (typeof cellValue === "string" && cellValue.indexOf(mark) === 0) {
        const fullAddress: string = usedRange.getCell(r, c).getAddressLocal();
        // "取得セル設定!A1" のようにシート名が付くため、"!"より後ろだけを取り出す
        const localAddress = fullAddress.includes("!") ? fullAddress.split("!")[1] : fullAddress;
        // VBAの.Addressと同じ絶対参照形式($A$1)にする
        const address: string = localAddress.replace(/([A-Z]+)(\d+)/, "$$$1$$$2");
        addressMap.set(cellValue, address);
      }
    }
  }

  const m = addressMap.size;
  for (let i = 1; i <= m; i++) {
    const target = `${mark}${i}`;
    const address: string = addressMap.has(target) ? addressMap.get(target) : "見つかりません";
    mainSheet.getRange("C" + i).setValue(target);
    mainSheet.getRange("D" + i).setValue(address);
  }
}

再検証の結果、画面はMainシートから動かないまま、D列には$A$1$C$2のような絶対参照形式で番地が記録されることを確認できました。

「見つかりません」の分岐(マーカーが存在しない場合の挙動)は、今回のテストデータでは★1★12すべてが見つかったため未検証です。

絶対参照形式で番地が記録された画像

スクリプトをボタンに紐付ける

Office Scriptsもシート上のボタンにスクリプトを割り当てて、ワンクリックで実行できます。ただし「挿入」タブからボタンを配置する方法ではありません(現在のExcel on the webでは、この方法でOffice Scripts用のボタンを追加することはできません)。正しくは、スクリプト側の共有設定からボタンを追加します。

  1. 「自動化」タブで、ボタンに割り当てたいスクリプトを選択して開く
  2. スクリプトの詳細画面で「このスクリプトを共有する」セクションまでスクロール
  3. 「ブックに関連付ける」トグルが無効になっている場合は有効にする
  4. 「ワークシートに追加」ボタンを選択する

これでシート上にボタンが追加され、そのブックを編集できる全ユーザーとスクリプトが共有された状態になります。

項目VBAOffice Scripts
ボタンの設置挿入 → フォームコントロール → ボタン自動化タブ → スクリプトの共有設定 → 「ワークシートに追加」
割り当て方法右クリック → マクロの登録スクリプト作成時点でボタンと1対1に紐付く
実行環境デスクトップExcelExcel on the web
共有範囲ブック内(マクロ有効ブックとして配布)ブックを編集できる全ユーザーと自動共有

コードエディターで未保存のスクリプトを複数回実行すると、「スクリプトは正常に複数回実行されました。ブックに追加しますか?」という確認が表示されることがあります。これはエラーではなく、まだブックに保存されていないスクリプトを保存するかどうかの確認です。ボタンに割り当てて再利用する場合は、ここで保存しておく必要があります。

VBAとOffice Scriptsの差分まとめ

項目VBAOffice Scripts
セル検索Cells.Find直接の代替メソッドなし。値配列を自前でスキャン
見つからない場合の判定Nothingかどうかで判定Mapに存在するかどうかで判定
ワイルドカード集計Application.CountIf(Cells, "★*")前方一致(indexOf(mark) === 0)でループ集計
セル番地の取得Range.Address(シート名なし・既定で絶対参照)Range.getAddressLocal()(シート名付き・相対参照。文字列処理で加工)
他シートの値を読む際の操作Sheets(...).Activateが必要な場面が多いWorksheet/Rangeオブジェクトに直接アクセスでき、Activateは不要

よくある質問・エラー対処

Q. Property 'findOrNullObject' does not exist というエラーが出る

findOrNullObjectWorksheetではなくRangeに対して呼び出すのが正しい作法ですが、正しくRangeに対して呼び出してもOffice Scriptsではエラーになりました。Find系のAPIに頼らず、getValues()で取得した値配列を自前でループしてセル番地を特定する方式に切り替えてください(本記事の「実際に動かして検証する」参照)。

Q. Microsoft公式ドキュメントにはRange.findOrNullObjectが載っているのに、Office Scriptsでエラーになる

→ 公式ドキュメントの多くはExcel JavaScript API(Excelアドイン用、Excel名前空間)のものです。Office Scripts(ExcelScript名前空間)はこれと似ていますが別のAPIセットで、すべてのメソッドが移植されているわけではありません。今回の検証では、WorksheetRangeどちらに対して呼び出してもOffice Scriptsの型定義上findOrNullObjectは存在せず、Find系のメソッドに頼らない自前実装への切り替えが必要でした。

Q. セル番地を取得するとシート名!A1のようにシート名が付いてくる

getAddressLocal()の仕様です。VBAの.Addressと同じくシート名なしの番地が欲しい場合は、"!"で文字列を分割し、後半部分だけを取り出してください。

Q. セル番地がA1のような相対参照になり、VBAの$A$1と形式が違う

getAddressLocal()は既定で相対参照形式を返します。VBAの.Addressと同じ絶対参照形式にするには、localAddress.replace(/([A-Z]+)(\d+)/, "$$$1$$$2")のように正規表現で$を付与してください。

Q. スクリプト実行後、別のシートが開いたままになってしまう

→ VBAではSheets(...).Activateで処理対象のシートに切り替えるのが一般的ですが、Office ScriptsはWorksheetRangeオブジェクトに直接アクセスできるため、そもそもActivateする必要がありません。Activateの呼び出しを削除すれば、画面のシートを切り替えずに処理できます。

Q. コードエディターで「ブックに追加しますか?」と表示される

→ エラーではありません。まだブックに保存されていないスクリプトを、複数回実行したタイミングで保存するかどうかの確認です。ボタンに割り当てて再利用したい場合は保存しておくことをおすすめします。

まとめ

  • VBAのCells.Findに相当する検索専用メソッドは、Office ScriptsのWorksheetにもRangeにも存在しない
  • AIが生成したコードも実在しないAPI(findOrNullObject)を使っており、2回のエラーを経て自前実装に切り替えた
  • getValues()で取得した値配列を自前でスキャンし、マーカー文字列とセル番地の対応表(Map)を作る方式で代替できた
  • getAddressLocal()はシート名付き・相対参照で返るため、VBAの.Addressと同じ形式にするには文字列処理が必要
  • Office ScriptsはWorksheetRangeオブジェクトに直接アクセスできるため、VBAで多用するActivate/Selectは不要。むしろ画面のシート遷移が目立つ分、削除した方が体感的にも自然
  • スクリプトのボタン割り当てはVBAと同様の操作感で行える

次回予告

次回は、いよいよ「その2」にあたる別ブックを実際に開いてデータを抜き出す本編です。今回記録したセル番地を使い、複数の別ブックを順番に開いてデータを集計する処理をOffice Scriptsで実現します。Office Scriptsはサーバーサイド実行のためVBAのWorkbooks.Openをそのまま使えず、PAD(Power Automate for Desktop)との連携が本格的に必要になる見込みです。

関連記事

当サイトの記事で使用したVBAなどのサンプルをDLできます

この記事のサンプルはありません!

ダウンロードページへは下のカードをクリックすればジャンプできます。
よろしければご利用ください!

よかったらシェアしてね!
  • URLをコピーしました!
目次