【検証】生成AIはVBAをGoogle Apps Scriptに変換できるか?Office Scriptsとの違いも解説

生成AIでVBAをGoogle Apps Scriptに変換する検証
目次

はじめに

以前のシリーズでは、ExcelのVBAをOffice Scripts(Excel on the web専用のTypeScript環境)へ生成AIで変換する検証を行いました。今回は同じ題材のVBAを、今度はGoogle Apps Script(GAS)へ変換してみます。

GASはGoogleスプレッドシートに組み込まれたJavaScriptベースのスクリプト環境です。Office Scriptsと同様に「クラウド上で動くスクリプト」という位置づけですが、実装の作法もAPIもまったくの別物です。同じVBAを題材にすることで、「クラウド実行環境」という共通点を持つ2つのサービスが、実際にはどう違うのかが見えてきます。

注意: 本記事のGASコードは、実際にGoogleスプレッドシート上で動作検証を行っています。カスタムメニュー実行時のUi.alert()表示、時間主導型トリガーでのgetUi()エラー、大量データ処理時の実行時間上限への抵触など、検証で判明した内容をそのまま反映しています。

今回の題材:汎用転記エンジンVBAの仕様

題材はOffice Scriptsシリーズの第4回で使用したものと同じ、動的な列マッピングで2シート間のデータを引き当てる汎用転記エンジンです。詳しいシート構造や元のVBAコード全文は第4回の記事を参照してください。ここでは要点だけおさらいします。

シート内容
貼付元シート(マクロ実行シート)1行目に設定情報(G1=データ開始行、K1=貼付先シート名)、2行目にマッピング定義(▼=ID照合列、1・2・3…=転記列番号)
貼付先シート同様の構造(G1=データ開始行、2行目に▼と数字のマッピング)

動作は「貼付先シートの▼列(ID)を1件ずつ取り出し、貼付元シートの▼列から同じIDをMATCHで検索して、対応する列のデータを転記する」というものです。加えて、元のVBAにはkeybd_eventによるスリープ防止コードが1000件ごとに挟まっています。

AIにVBAを渡すだけでは何が起きるか(失敗例)

Office Scriptsのときと同様、まず「よくある依頼方法」で変換を試みます。

以下のVBAコードをGoogle Apps Script(GAS)に変換してください。

[VBAコードをそのまま貼り付け]

この依頼で多くのAIが返してくるのは、シート構造を理解しないままの単純な列コピーです。

// ❌ AIが誤解して生成したコード(シート構造を把握できていない)
function arrayMatch() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet1 = ss.getSheetByName('Sheet1');
  const sheet2 = ss.getSheetByName('Sheet2');

  const lastRow = sheet1.getLastRow();
  for (let i = 2; i <= lastRow; i++) {
    const id = sheet1.getRange(i, 1).getValue();
    sheet2.getRange(i, 1).setValue(id);
  }
}

Office Scriptsのときとまったく同じ問題が起きます。2行目のマッピング定義や、G1・K1の設定情報を読み取らず、A列→A列の単純コピーになってしまうのです。AIはVBAのロジックだけを見て変換するため、そのVBAが前提とするシートの構造までは推測できません。

keybd_eventについては、GASでもやはり「Windows専用のAPIであり、ブラウザ/クラウド上で動くGASからは呼び出せない」という趣旨の回答が返ってきます。これはOffice Scriptsのときと同じく正しい回答です。

シート構造を伝えた上で変換依頼する(成功例)

Office Scripts第4回と同じく、シート構造の説明を加えたプロンプトで依頼し直します。

以下のVBAコードをGoogle Apps Script(GAS)に変換してください。

【シートの構造】
・貼付元シート(マクロの実行シート)
– 1行目:設定情報
– G1:データ開始行番号(例:4)
– K1:貼付先のシート名
– 2行目:マッピング定義
– ▼:ID照合列を示すマーカー
– 数字(1, 2, 3…):転記する列の番号(データが入っている列に置く)
– 3行目:見出し行
– G1の値の行〜:データ本体

・貼付先シート
– 同様の構造(G1=データ開始行)
– 2行目に▼と数字でマッピング列を定義

【変換の条件】
– 変換できない機能(Win32 API等)は削除して理由をコメントで明記すること
– Google Apps Script(JavaScript)形式で記述すること
– SpreadsheetAppクラスを使用すること
– VBAとの対応関係がわかるようにコメントを入れること
– 関数に分けて整理すること

【VBAコード】
[VBAコードを貼り付け]

変換後のGASコード

function arrayMatch() {

  const ss = SpreadsheetApp.getActiveSpreadsheet();

  // 貼付元シート(実行シート)= VBAの prefSh
  // ★ GASにも「アクティブシート」という概念(getActiveSheet)はあるが、
  //   時間主導型トリガーなど「開いていない状態」で実行される場合は
  //   意図したシートを取得できないため、シート名を直接指定する
  const prefSh = ss.getSheetByName('貼付元');
  // K1から貼付先シート名を取得 = VBAの sName
  const sName  = prefSh.getRange('K1').getValue();
  const workSh = ss.getSheetByName(sName);

  if (!workSh) {
    SpreadsheetApp.getUi().alert(`シート "${sName}" が見つかりません`);
    return;
  }

  // ▼マーク列の自動検出 = VBAの WorksheetFunction.Match("▼", ...)
  const ptCol = findValueInRow(prefSh, 2, '▼');
  const wtCol = findValueInRow(workSh, 2, '▼');

  if (ptCol === -1 || wtCol === -1) {
    SpreadsheetApp.getUi().alert('▼マークが見つかりません(2行目を確認してください)');
    return;
  }

  // マッピング列数の取得 = VBAの WorksheetFunction.Max(...)
  const pMCol = getMaxInRow(prefSh, 2);
  const wMCol = getMaxInRow(workSh, 2);

  if (pMCol !== wMCol) {
    SpreadsheetApp.getUi().alert('引き当てる列の数が不整合のため中止しました');
    return;
  }

  // データ開始行の取得 = VBAの prefSh.Range("G1") / workSh.Range("G1")
  const pRow = prefSh.getRange('G1').getValue();
  const wRow = workSh.getRange('G1').getValue();

  // 列マッピング配列の構築 = VBAの pCol() / wCol()
  const pCol = [];
  for (let i = 1; i <= pMCol; i++) {
    pCol.push(findValueInRow(prefSh, 2, i));
  }
  const wCol = [];
  for (let i = 1; i <= wMCol; i++) {
    wCol.push(findValueInRow(workSh, 2, i));
  }

  // 最終行の取得 = VBAの Cells(Rows.Count, col).End(xlUp).Row
  const workShEndR = getLastRow(workSh, wtCol);
  const prefShEndR = getLastRow(prefSh, ptCol);

  // ID照合用にターゲット列を配列として一括取得(パフォーマンス対策。後述)
  const tgetRng = prefSh
    .getRange(pRow, ptCol, prefShEndR - pRow + 1, 1)
    .getValues();

  let lngHcount = 0;

  // メインループ = VBAの For tgetTmpR = wRow To workShEndR
  for (let tgetTmpR = wRow; tgetTmpR <= workShEndR; tgetTmpR++) {

    const tmpStr = workSh.getRange(tgetTmpR, wtCol).getValue();

    // ID照合 = VBAの WorksheetFunction.Match(tmpStr, tgetRng, 0)
    let matchRng = -1;
    for (let r = 0; r < tgetRng.length; r++) {
      if (tgetRng[r][0] === tmpStr) {
        matchRng = r + pRow;
        break;
      }
    }

    if (matchRng === -1) continue;

    // 転記元データの取得
    const myArray = [];
    for (let i = 0; i < pMCol; i++) {
      myArray.push(prefSh.getRange(matchRng, pCol[i]).getValue());
    }

    // 貼付先への書き込み
    for (let i = 0; i < wMCol; i++) {
      workSh.getRange(tgetTmpR, wCol[i]).setValue(myArray[i]);
    }

    lngHcount++;

    // ★ keybd_event(スリープ防止)→ 変換不可・削除
    //   理由:Win32 APIはGASから呼び出せない
    //   ただしGASもクラウド実行のためPC自体がスリープしても処理は継続する。不要
  }

  // ★ MsgBox → 完了通知はUi.alert()ではなくtoast()を使う
  //   理由:alert()はOKが押されるまで実行をブロックし、その待機時間も
  //   実行時間の上限(6分/30分)にカウントされてしまうため(詳しくは後述)
  SpreadsheetApp.getActiveSpreadsheet().toast(`データ更新件数: ${lngHcount} 件`, '完了!', 10);
}

// ── ヘルパー関数 ──────────────────────────────────

// 行内で値を検索して列番号を返す(1-indexed。GASのgetRangeは1始まり)
// = VBAの WorksheetFunction.Match の代替
function findValueInRow(sheet, row, value) {
  const lastCol = sheet.getLastColumn();
  const rowValues = sheet.getRange(row, 1, 1, lastCol).getValues()[0];
  const idx = rowValues.indexOf(value);
  return idx === -1 ? -1 : idx + 1;
}

// 行の数値の最大値を返す
// = VBAの WorksheetFunction.Max の代替
function getMaxInRow(sheet, row) {
  const lastCol = sheet.getLastColumn();
  const rowValues = sheet.getRange(row, 1, 1, lastCol).getValues()[0];
  const nums = rowValues.filter(v => typeof v === 'number');
  return nums.length > 0 ? Math.max(...nums) : 0;
}

// 最終行を返す(1-indexed)
// = VBAの Cells(Rows.Count, col).End(xlUp).Row の代替
function getLastRow(sheet, col) {
  const lastRow = sheet.getLastRow();
  const colValues = sheet.getRange(1, col, lastRow, 1).getValues();
  for (let r = colValues.length - 1; r >= 0; r--) {
    if (colValues[r][0] !== '' && colValues[r][0] !== null) return r + 1;
  }
  return 1;
}

GASのRangeはOffice Scripts(getRangeByIndexes)と違い1始まり(1-indexed)です。行・列番号のズレは変換時に混入しやすいミスなので、AIの変換結果もこの点は必ず確認してください。

初回実行時の注意:権限の承認が必要

このコードを初めて実行すると、「このアプリは Google で確認されていません」という警告画面が表示され、そのままでは実行できません。作成したスクリプトがスプレッドシートの情報にアクセスするための権限(スコープ)を、まだ承認していないために表示されるものです。

画面下部の「詳細」(または「Advanced」)を開き、「(プロジェクト名)に移動(安全ではないページ)」を選択したうえで、要求される権限の内容を確認して許可してください。これはフィッシング等の警告ではなく、Googleの審査を受けていない個人用スクリプトである旨の標準的な確認フローです。自分で作成したスクリプトであれば、内容を確認したうえで許可して問題ありません。承認は基本的に初回のみで、以降は毎回表示されません。

GASでボタンから実行する方法(VBAのシートボタンに相当)

VBAでは、シート上に配置したボタン(フォームコントロール)にマクロを登録し、クリックひとつで実行するのが定番の起動方法でした。GASにも専用の「ボタン」部品こそありませんが、図形(描画)にスクリプトを割り当てることで同じ操作性を再現できます。

設置手順

  1. スプレッドシートのメニューから「挿入」→「図形描画」を選択し、シンプルな図形やテキストを作成して保存する
  2. シート上に配置された図形をクリックすると右上に表示される「︙」(縦三点リーダー)アイコンから「スクリプトを割り当て」を選択する
  3. 実行したい関数名(例:arrayMatch。括弧や引数は付けない)を入力してOKを押す

これで、この図形をクリックするだけでスクリプトが実行されるようになります。

項目VBAOffice ScriptsGAS
ボタンの設置挿入 → フォームコントロール → ボタン挿入 → ボタン挿入 → 図形描画 → スクリプトを割り当て
割り当て方法右クリック → マクロの登録ボタン配置時にスクリプトを選択図形の「︙」メニューから「スクリプトを割り当て」
専用のボタン部品あり(フォームコントロール)ありなし(図形で代用)

この方法で実行した場合、ユーザーが図形を直接クリックする操作になるため、後述するカスタムメニュー実行と同様にUIと紐づいたコンテキストとして扱われます。つまり、Ui.alert()toast()も問題なく使えます(時間主導型トリガーのような制約は受けません)。

今回のサンプルスプレッドシートでは、実際に「制御」シート上にこの方法でボタンを設置しています。ボタン押下で実行している画像がこちらです。「スクリプトを実行しています」と「キャンセル」が表示されます。

設置したスクリプト実行用のボタンと実行時の表示画像

Office Scriptsとの決定的な違い:Ui.alert()でMsgBoxが再現できる

Office Scriptsシリーズでは、UserForm・プログレスバー・MsgBoxはすべて「代替手段なし」という結論になり、その後Power Automateとの連携(第5〜7回)で完了通知だけを何とか実現する、という展開になりました。

GASではここが大きく異なります。GASはSpreadsheetに紐づくUIサービス(SpreadsheetApp.getUi())を持っており、alert()prompt()showModalDialog()といった、VBAのMsgBox・InputBox・UserFormに近い機能をネイティブに呼び出せます。

// VBAのMsgBoxに近い書き方
SpreadsheetApp.getUi().alert('完了!【データ更新件数: 3 件】');

// ボタンの選択を受け取る場合(VBAのMsgBox(vbYesNo)相当)
const response = SpreadsheetApp.getUi().alert(
  '確認',
  '処理を実行しますか?',
  SpreadsheetApp.getUi().ButtonSet.YES_NO
);
if (response === SpreadsheetApp.getUi().Button.YES) {
  // 実行
}

これはOffice Scriptsとの間で大きな体験の差になります。

項目VBAOffice ScriptsGAS
MsgBox相当の完了通知MsgBox❌ 代替手段なし(console.logのみ)Ui.alert()で再現可能
入力ダイアログInputBoxUi.prompt()で再現可能
プログレスバーUserFormHtmlServiceによるモーダルダイアログで作り込みは可能(後述の制約あり)

ただし、呼び出せる条件に制約がある

getUi()どこからでも呼べるわけではありません。GASの実行がUI(スプレッドシートの画面)と紐づいたコンテキストで行われている場合のみ使えます。

実行方法getUi()の呼び出し
カスタムメニュー(onOpenで追加したメニュー項目)から実行✅ 可能
スプレッドシートのエディタから「実行」ボタンで直接実行✅ 可能
図形(描画)に割り当てたボタンから実行(前述)✅ 可能
時間主導型トリガー(定期実行)❌ 例外が発生する(UIコンテキストが存在しないため)
別のスプレッドシートやフォームから呼び出す installable トリガー経由❌ 同上(トリガーの種類による)

つまり、「シート上のメニューやボタンから手動で実行する」という、VBAに一番近い使い方をする場合に限り、MsgBox相当の通知が使えるということです。定期実行・自動実行のケースでは、Office Scripts第5回と同じように、通知には別の手段(GASの場合はメール送信MailApp.sendEmail()や、Google Chatへの通知など)を組み合わせる必要があります。

実際に検証した結果: カスタムメニューから実行した場合は、Ui.alert()のダイアログ(「完了!【データ更新件数: 1000 件】」)が正しく表示されることを確認しました。一方、時間主導型トリガーで実行した場合は、想定どおり以下の例外が発生し、ダイアログは表示されませんでした。

定期実行・自動実行のトリガーでこのスクリプトを動かす場合は、Ui.alert()は使えないという前提で設計してください。

alert()の待機時間も実行時間にカウントされる(要注意)

もう一つ、実際に検証して分かった重要な注意点があります。Ui.alert()は、ユーザーが「OK」を押すまでスクリプトの実行をブロックします。そして、この待機時間も実行時間の上限(6分/30分)にカウントされます

つまり、以下のような事態が起こり得ます。

処理に5分50秒かかる

完了ダイアログ(Ui.alert())を表示

ユーザーがすぐに「OK」を押さない

表示したまま経過し、合計6分を超える

タイムアウトでスクリプトが強制終了される

処理自体はすでに完了しているにもかかわらず、ダイアログを表示したまま放置しているだけでタイムアウトしてしまうという、VBAのMsgBoxとの決定的な違いです(VBAのMsgBoxには実行時間の上限自体がないため、この問題は起こりません)。

対処法:完了通知にはtoast()を使う

この問題を避けるには、ユーザーの応答を待たないtoast()メソッドを使うのが効果的です。toast()はスプレッドシート右下に小さな通知を一定時間表示するだけで、Ui.alert()と違ってスクリプトの実行をブロックしません

// ★ 完了通知はUi.alert()ではなくtoast()を使う
//   理由:alert()はOKが押されるまでブロックし、その待機時間も実行時間の上限にカウントされる。
//   toast()はブロックしないため、処理が上限ギリギリでも「表示中に力尽きる」ことがない
SpreadsheetApp.getActiveSpreadsheet().toast(`データ更新件数: ${lngHcount} 件`, '完了!', 10);

toast(メッセージ, タイトル, 表示秒数)という構文で、指定した秒数(例では10秒)が経過すると自動的に消えます。ボタンやユーザーの選択を伴わない「完了しました」といった一方向の通知であれば、alert()よりtoast()のほうが適しています。

注意: シートの不整合など、処理の早い段階で発生するエラー(本記事のコードでいう「シートが見つかりません」等)は、まだ実行時間にほとんど余裕がある状態で表示されるため、alert()のままでも実務上の問題は起きにくいです。今回問題になるのは、大量データの処理後に表示する完了通知のように、実行時間の上限に近づいた状態で表示するケースです。

変換できた機能・できなかった機能の整理

✅ 変換できた機能

VBAの機能GASでの実装
WorksheetFunction.Match配列の線形検索(indexOf
WorksheetFunction.MaxMath.max()
Cells(Rows.Count, col).End(xlUp).RowgetLastRow()からの逆順検索
Range().Value(一括読み書き)getValues() / setValues()
MsgBoxSpreadsheetApp.getUi().alert()呼び出しコンテキストの制約あり
InputBoxSpreadsheetApp.getUi().prompt()(同上)
ActiveSheet / Worksheets(name)getActiveSheet() / getSheetByName()

❌ 変換できなかった機能

VBAの機能変換できない理由代替・備考
keybd_event(スリープ防止)Win32 API:OSレイヤー不可クラウド実行のため不要
Sleep(待機)Win32 API同上(GASにはUtilities.sleep()という別の待機関数があるが用途が異なる)
リアルタイムのプログレスバーサーバーサイド実行が同期的でブロッキングのため、実行中に画面を都度更新する仕組みがないHtmlServiceのダイアログ+クライアント側ポーリングで擬似的に作り込むことは可能だが、実装コストが高い
DoEvents・処理中のキャンセルボタンイベント駆動モデルがない代替手段なし
#If VBA7 Then(条件コンパイル)不要GASはそもそもOS非依存

GAS特有の注意点:実行時間の上限

Office Scriptsにはなかった、GAS特有の制約としてスクリプトの実行時間上限があります。

アカウント種別1回の実行あたりの上限時間
個人のGoogleアカウント(無料)6分
Google Workspaceアカウント30分

実際に1000件のデータで実行したところ、6分の上限に達してタイムアウトしました。 実行ログには以下のように記録されます。

実行開始
(6分後)
エラー   Exceeded maximum execution time

原因は、今回のコードのメインループが1件ごとにgetRange().getValue() / setValue()を呼び出す作りになっているためと考えられます。GASのようなクラウド実行環境では、セル単位のAPI呼び出し1回ごとに通信のオーバーヘッドが発生するため、呼び出し回数が多いほど実行時間が伸びます。今回は転記元・転記先の列マッピングが動的(列位置が固定でない)なため、単純にgetValues() / setValues()へ一括化するには、行ごとの列マッピングをいったん配列に展開してから読み書きするような追加の作り込みが必要になり、本記事のコードではそこまでは対応していません。

対応方針としては以下が考えられます。

  • メインループ内のセル単位アクセスをまとめて配列化し、getValues() / setValues()で一括読み書きするように書き換える(データ量が多い場合は実質的に必須の改善ポイントです)
  • どうしても間に合わない場合は、ScriptAppのトリガー機能で処理を分割し、続きを別実行に引き継ぐ設計にする

今回のコードをそのまま使う場合、目安として数百件程度までのデータ量にとどめることをおすすめします。 1000件規模以上の転記を想定する場合は、上記の一括読み書きへの書き換えが実質的に必須です。

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

Q. 初回実行時に「このアプリはGoogleで確認されていません」と表示される

→ Google側の標準的な確認画面です。「詳細」を開き、プロジェクト名の項目から実行を許可してください。自分で作成した未公開のスクリプトでは、初回実行時にこの画面が表示されます(詳しくは本文「初回実行時の注意」を参照)。

Q. getUi()を呼び出すと「例外が発生しました」となる

→ 時間主導型トリガーなど、UIコンテキストを持たない実行方法で呼び出している可能性があります。カスタムメニューやエディタからの直接実行など、スプレッドシートの画面と紐づいた実行方法に切り替えるか、getUi()を使わない通知方法(メール送信など)に変更してください。

Q. 「▼マークが見つかりません」と表示される

→ 貼付元・貼付先シートの2行目にマッピング定義が正しく入力されているか確認してください。行番号・列番号のズレ(GASは1-indexed)が原因になっているケースもあります。

Q. 大量データで処理が途中で止まる(タイムアウトする)

→ 実際に1000件のデータで複数回実行検証したところ、GASの実行時間上限(無料アカウントで6分)に達してタイムアウトするケースがありました。今回のコードはメインループ内でセル単位のgetValue()/setValue()を呼び出す作りになっているため、データ量が増えるほど実行時間が伸びます。getValues()/setValues()による一括読み書きへの書き換えが必要です(本文「GAS特有の注意点」参照)。

Q. 完了ダイアログ(Ui.alert())を表示したまま放置していたらタイムアウトした

Ui.alert()はユーザーが「OK」を押すまでスクリプトの実行をブロックし、その待機時間も実行時間の上限(無料アカウントで6分)にカウントされます。処理自体が完了していても、ダイアログを表示したまま長く放置するとタイムアウトすることがあります。完了通知には、ブロックしないSpreadsheetApp.getActiveSpreadsheet().toast()を使うことをおすすめします(本文「対処法:完了通知にはtoast()を使う」参照)。

Q. Office Scriptsと比べてGASを選ぶメリットは?

→ 最大のメリットは、Ui.alert()などのUIサービスにより、VBAのMsgBoxに近い体験を(呼び出しコンテキストの制約付きながら)再現できる点です。手動実行中心のマクロをクラウドへ移行したい場合、GASのほうがVBAからの移行ギャップが小さく感じられるはずです。一方、実行時間の上限はOffice Scriptsにはない制約なので、大量データを扱う場合は注意が必要です。

まとめ:AIへの変換依頼チェックリスト

  • AIにVBAコードだけ渡しても、シート構造を理解した変換はできない点はOffice Scriptsと同じ
  • シートの行構造・特殊列の意味・設定セルの用途を事前に説明することで変換品質が上がる
  • GASのRangeは1始まり(1-indexed)。Office Scripts(0-indexed)からの類推でミスしやすい
  • keybd_event等のWin32 APIは変換不可だが、クラウド実行環境ではそもそも不要
  • Office Scriptsと違い、GASにはUi.alert()などのUIサービスがあり、MsgBox相当の通知を再現できる。ただし時間主導型トリガーなどUIコンテキストがない実行方法では使えない
  • GAS特有の制約として実行時間の上限(個人アカウントで6分)があり、大量データの処理では設計上の考慮が必要

変換依頼プロンプトのテンプレート:

以下のVBAコードをGoogle Apps Script(GAS)に変換してください。

【シートの構造】
(シートの行・列構成を具体的に説明)

【変換の条件】
- 変換できない機能は削除し、理由をコメントで明記すること
- SpreadsheetAppクラスを使用すること
- VBAとの対応関係がわかるようにコメントを入れること
- 関数に分けて整理すること

【VBAコード】
(コードを貼り付け)

次回予告: 今回のコードは、大量データを扱うと実行時間の上限に達してタイムアウトする課題が残りました。次回は、Office Scriptsシリーズ第6回(セルでの進捗表示+実行ボタンの停止機能でキャンセル)に相当する内容として、GASで進捗表示を実装する方法を掘り下げます。

サンプルファイルのダウンロード

この記事で使用したテスト用スプレッドシートはこちらからダウンロードできます。GASのスクリプトはスプレッドシートに紐づいた状態(コンテナバインド型)で保存されているため、このシートをコピーすればスクリプトごとそのまま複製されます。Office Scriptsのようにファイルとスクリプトを別々に用意する必要はありません。

サンプルファイルをダウンロード

↑ リンク先ファイルのURL末尾を/copyにした「コピー用リンク」にしています。クリックするだけでご自分のGoogleドライブにスプレッドシートのコピーが複製(GASスクリプトごと)されるようになっています。末尾/copyを外してアクセスした場合は、「閲覧のみ」となりますのでコピーを作成してご利用ください。

関連記事

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

この記事のサンプルへのリンクはこちらです!

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

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