はじめに
前回までのシリーズでは、VBAとOffice Scriptsの違いや基本的な書き換えパターンを解説してきました。
今回は実際のVBAコードを生成AIに渡して、Office Scriptsへの変換を試みます。
題材は kurumico.com で公開している「スリープ対策VBA」のサンプルファイル。このVBAには keybd_event というWindows専用のAPIが使われており、変換の難易度が高いコードです。
検証を通じて見えてきたのは「AIに何を・どう伝えるか」によって変換品質が大きく変わるという事実。記事の後半では、再現性のある変換依頼テンプレートもご紹介します。
今回の題材:汎用転記エンジンVBAの仕様
今回使用するVBAは、表面上は「スリープ対策コード」ですが、その本体は動的な列マッピングで2シート間のデータを引き当てる汎用転記エンジンです。
シートの構造
このVBAが前提とするシート構造は以下のとおりです。
貼付元シート(マクロ実行シート)
| 行 | 内容 |
|---|---|
| 1行目 | 設定情報(G1=データ開始行、K1=貼付先シート名) |
| 2行目 | マッピング定義(▼=ID照合列、1・2・3…=転記列番号) |
| 3行目 | 見出し行 |
| 4行目〜 | データ本体 |
貼付先シート
| 行 | 内容 |
|---|---|
| 1行目 | 設定情報(G1=データ開始行) |
| 2行目 | マッピング定義(▼=ID照合列、1・2・3…=転記先列番号) |
| 3行目 | 見出し行 |
| 4行目〜 | 転記先データ(IDのみ入力済み) |
動作の仕組み
貼付先シートの▼列(ID)を1件ずつ取り出す
↓
貼付元シートの▼列から同じIDを検索(MATCH)
↓
2行目の数字(1, 2, 3…)で定義された列のデータを取得
↓
貼付先シートの対応する列へ転記
ポイント: 列の番号はシートごとに自由な位置に置けるため、シート構造が異なっていても自動でマッピングされます。たとえば貼付元の「商品名」がB列でも、貼付先では別の列に転記できます。
VBAコードの全体像
変換対象のVBAコードです。#If VBA7 による32/64ビット対応の宣言と、メインの処理ループが含まれます。
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare PtrSafe Sub keybd_event Lib "user32" ( _
ByVal bVk As Byte, ByVal bScan As Byte, _
ByVal dwFlags As Long, ByVal dwExtraInfo As LongPtr)
#Else
Private Declare Sub Sleep Lib "KERNEL32" (ByVal dwMilliseconds As Long)
Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, _
ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)
#End If
Private Const KEYEVENTF_KEYUP = &H2
Sub Array_match()
' ~ メイン処理(省略)~
' ループ内でkeybd_eventを実行(スリープ防止)
If tgetTmpR Mod 1000 = 0 Then
keybd_event 0, 0, 0, 0
keybd_event 0, 0, KEYEVENTF_KEYUP, 0
End If
End SubVBAの起動方法:シートボタンによるマクロ実行
元のVBAはシート上に設置したボタン(フォームコントロール) を押すことで起動する設計になっていました。
シート上のボタン → クリック → Array_match() マクロが実行される
VBAではシート上にボタンを設置し、右クリック →「マクロの登録」で任意のSubプロシージャを割り当てるのが一般的な操作起動の方法です。ボタンはシートに常駐しているため、ファイルを開くたびに同じ操作でマクロを実行できます。
Office Scriptsでの代替方法
Office Scriptsも同様に、シート上のボタンにスクリプトを割り当てることができます。
手順は以下のとおりです。
- Excel on the web で「挿入」タブをクリック
- 「ボタン」を選択してシート上に配置
- 表示されるダイアログで割り当てるスクリプトを選択
これにより、VBAと同様にシートボタンクリックでスクリプトが起動します。
| 項目 | VBA | Office Scripts |
|---|---|---|
| ボタンの設置 | 挿入 → フォームコントロール → ボタン | 挿入 → ボタン |
| 割り当て方法 | 右クリック → マクロの登録 | ボタン配置時にスクリプトを選択 |
| 実行環境 | デスクトップExcel | Excel on the web |
| ボタンの保存 | ブックに保存される | ブックに保存される |
注意点: Office Scriptsのボタンは Excel on the web でのみ 動作します。同じファイルをデスクトップ版Excelで開いてもボタンからスクリプトは実行できません。
AIにVBAを渡すだけでは何が起きるか(失敗例)
まず「よくある依頼方法」で変換を試みます。
失敗するプロンプト例
以下のVBAコードをOffice Scriptsに変換してください。
[VBAコードをそのまま貼り付け]
AIが返してくる変換結果(問題あり)
この依頼で多くのAIが返してくるのは、以下のようなシンプルな列転記コードです。
// ❌ AIが誤解して生成したコード(シート構造を把握できていない)
function main(workbook: ExcelScript.Workbook) {
const sheet1 = workbook.getWorksheet("Sheet1");
const sheet2 = workbook.getWorksheet("Sheet2");
const lastRow = sheet1.getUsedRange().getRowCount();
for (let i = 1; i < lastRow; i++) {
const id = sheet1.getRangeByIndexes(i, 0, 1, 1).getValue();
sheet2.getRangeByIndexes(i, 0, 1, 1).setValue(id);
}
}何が問題か:
- A列→A列の単純コピーになっている
- 2行目のマッピング定義を読み取っていない
- ▼列の自動検出がない
- G1・K1の設定情報を参照していない
- ID照合(MATCH)が実装されていない
AIはVBAコードのロジックだけを見て変換するため、そのVBAが前提とするシートの構造を把握できないのです。
keybd_eventについてはどう返すか
keybd_event については、どのAIも正直に変換不可と回答します。
「
keybd_eventはWindowsのWin32 APIであり、Office Scriptsはブラウザのサンドボックス環境で動作するためOSレイヤーへのアクセスができません。この機能は変換できません。」
これは正しい回答です。ただし「変換できない=移行できない」ではありません。後述します。
シート構造を伝えた上で変換依頼する(成功例)
AIへの依頼にシート構造の説明を加えると、変換品質が大きく変わります。
正しいプロンプト例
以下のVBAコードをOffice Scriptsに変換してください。
【シートの構造】
・貼付元シート(マクロの実行シート)
– 1行目:設定情報
– G1:データ開始行番号(例:4)
– K1:貼付先のシート名
– 2行目:マッピング定義
– ▼:ID照合列を示すマーカー
– 数字(1, 2, 3…):転記する列の番号(データが入っている列に置く)
– 3行目:見出し行
– G1の値の行〜:データ本体
・貼付先シート
– 同様の構造(G1=データ開始行)
– 2行目に▼と数字でマッピング列を定義
【変換の条件】
– 変換できない機能(Win32 API等)は削除して理由をコメントで明記すること
– TypeScript形式で記述すること
– VBAとの対応関係がわかるようにコメントを入れること
– ヘルパー関数に分けて整理すること
【VBAコード】
[VBAコードを貼り付け]
変換後のOffice Scriptsコード
上記のプロンプトで生成されたコードが以下です。
function main(workbook: ExcelScript.Workbook) {
// 貼付元シート(実行シート)= VBAの prefSh
const prefSh = workbook.getActiveWorksheet();
// K1から貼付先シート名を取得 = VBAの sName
const sName = prefSh.getRange("K1").getValue() as string;
const workSh = workbook.getWorksheet(sName);
if (!workSh) {
console.log(`シート "${sName}" が見つかりません`);
return;
}
// ▼マーク列の自動検出 = VBAの WorksheetFunction.Match("▼", ...)
const ptCol = findValueInRow(prefSh, 1, "▼");
const wtCol = findValueInRow(workSh, 1, "▼");
if (ptCol === -1 || wtCol === -1) {
console.log("▼マークが見つかりません(2行目を確認してください)");
return;
}
// マッピング列数の取得 = VBAの WorksheetFunction.Max(...)
const pMCol = getMaxInRow(prefSh, 1);
const wMCol = getMaxInRow(workSh, 1);
if (pMCol !== wMCol) {
console.log("引き当てる列の数が不整合のため中止します!");
return;
}
// データ開始行の取得 = VBAの prefSh.Range("G1") / workSh.Range("G1")
const pRow = prefSh.getRange("G1").getValue() as number;
let wRow = workSh.getRange("G1").getValue() as number;
// 列マッピング配列の構築 = VBAの pCol() / wCol()
const pCol: number[] = [];
let pFlgCol = 0;
for (let i = 1; i <= pMCol; i++) {
const col = findValueInRow(prefSh, 1, i);
pCol.push(col);
if (i > 1 && col !== pCol[i - 2] + 1) pFlgCol = 1;
}
const wCol: number[] = [];
let wFlgCol = 0;
for (let i = 1; i <= wMCol; i++) {
const col = findValueInRow(workSh, 1, i);
wCol.push(col);
if (i > 1 && col !== wCol[i - 2] + 1) wFlgCol = 1;
}
// 最終行の取得 = VBAの Cells(Rows.Count, col).End(xlUp).Row
const workShEndR = getLastRow(workSh, wtCol);
const prefShEndR = getLastRow(prefSh, ptCol);
// ID照合用にターゲット列を配列として一括取得
const tgetRng = prefSh
.getRangeByIndexes(pRow - 1, ptCol, prefShEndR - pRow + 1, 1)
.getValues();
// 開始行の上書き指定(N1セル)= VBAの再開始行機能
const n1Val = prefSh.getRange("N1").getValue();
if (typeof n1Val === "number" && n1Val > wRow) wRow = n1Val;
let lngHcount = 0;
// メインループ = VBAの For tgetTmpR = wRow To workShEndR
for (let tgetTmpR = wRow; tgetTmpR <= workShEndR; tgetTmpR++) {
const tmpStr = workSh
.getRangeByIndexes(tgetTmpR - 1, wtCol, 1, 1)
.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: (string | number | boolean)[] = [];
if (pFlgCol === 1) {
for (let i = 0; i < pMCol; i++) {
myArray.push(
prefSh.getRangeByIndexes(matchRng - 1, pCol[i], 1, 1)
.getValue() as string | number | boolean
);
}
} else {
const vals = prefSh
.getRangeByIndexes(matchRng - 1, pCol[0], 1, pMCol)
.getValues()[0];
myArray.push(...(vals as (string | number | boolean)[]));
}
// 貼付先への書き込み
if (wFlgCol === 1) {
for (let i = 0; i < wMCol; i++) {
workSh.getRangeByIndexes(tgetTmpR - 1, wCol[i], 1, 1)
.setValue(myArray[i]);
}
} else {
workSh.getRangeByIndexes(tgetTmpR - 1, wCol[0], 1, pMCol)
.setValues([myArray]);
}
lngHcount++;
// ★ keybd_event(スリープ防止)→ 変換不可・削除
// 理由:Win32 APIはOffice Scriptsから呼び出せない
// ただしOffice ScriptsはクラウドでPC自体がスリープしないため不要
// ★ UserForm(プログレスバー)→ 変換不可・削除
// 理由:Office ScriptsにUI部品なし
// ★ キャンセルボタン / DoEvents → 変換不可・削除
// 理由:イベント駆動モデルがない
if (tgetTmpR % 1000 === 0) {
console.log(`処理中... ${tgetTmpR} / ${workShEndR} 件`); // プログレスバーの代替
}
}
console.log(`完了!【データ更新件数: ${lngHcount} 件】`);
}
// ── ヘルパー関数 ──────────────────────────────────
// 行内で値を検索して列インデックスを返す(0-indexed)
// = VBAの WorksheetFunction.Match の代替
function findValueInRow(
sheet: ExcelScript.Worksheet,
rowIndex: number,
value: string | number
): number {
const colCount = sheet.getUsedRange().getColumnCount();
const rowValues = sheet
.getRangeByIndexes(rowIndex, 0, 1, colCount)
.getValues()[0];
return rowValues.indexOf(value);
}
// 行の数値の最大値を返す
// = VBAの WorksheetFunction.Max の代替
function getMaxInRow(sheet: ExcelScript.Worksheet, rowIndex: number): number {
const colCount = sheet.getUsedRange().getColumnCount();
const rowValues = sheet
.getRangeByIndexes(rowIndex, 0, 1, colCount)
.getValues()[0];
const nums = rowValues.filter(v => typeof v === "number") as number[];
return nums.length > 0 ? Math.max(...nums) : 0;
}
// 最終行を返す(1-indexed)
// = VBAの Cells(Rows.Count, col).End(xlUp).Row の代替
function getLastRow(sheet: ExcelScript.Worksheet, colIndex: number): number {
const rowCount = sheet.getUsedRange().getRowCount();
const colValues = sheet
.getRangeByIndexes(0, colIndex, rowCount, 1)
.getValues();
for (let r = rowCount - 1; r >= 0; r--) {
if (colValues[r][0] !== "" && colValues[r][0] !== null) return r + 1;
}
return 1;
}実際にExcel on the webで動かしてみた
上記のコードをExcel on the webのOffice Scriptsエディタに貼り付けて実行しました。
テスト環境
貼付元シート
| 行\列 | A | B | C | … | G | … | K |
|---|---|---|---|---|---|---|---|
| 1行目 | (説明ラベル) | 4 | 貼付先 | ||||
| 2行目 | ▼ | 1 | 2 | ||||
| 3行目 | ID(見出し) | 商品名 | 単価 | ||||
| 4行目 | ID001 | りんご | 100 | ||||
| 5行目 | ID002 | バナナ | 200 | ||||
| 6行目 | ID003 | みかん | 150 |
貼付先シート(実行前)
| 行\列 | C(▼) | D(1) | E(2) |
|---|---|---|---|
| 4行目 | ID001 | (空) | (空) |
| 5行目 | ID003 | (空) | (空) |
| 6行目 | ID002 | (空) | (空) |
IDは順不同で配置しています。
実行結果
コンソールに以下が出力されました。
完了!【データ更新件数: 3 件】
貼付先シート(実行後)
| 行\列 | C(▼) | D(1:商品名) | E(2:単価) |
|---|---|---|---|
| 4行目 | ID001 | りんご | 100 |
| 5行目 | ID003 | みかん | 150 |
| 6行目 | ID002 | バナナ | 200 |
IDが順不同でも、ID照合によって正しい行に転記されています。✅
変換できた機能・できなかった機能の整理
✅ 変換できた機能
| VBAの機能 | Office Scriptsでの実装 |
|---|---|
WorksheetFunction.Match | 配列の線形検索(indexOf) |
WorksheetFunction.Max | Math.max() |
Cells(Rows.Count, col).End(xlUp).Row | getUsedRange()からの逆順検索 |
Range().Value(一括読み書き) | getValues() / setValues() |
Forループ・配列処理 | 標準TypeScript |
On Error Resume Next | try/catch |
ActiveSheet / Worksheets(name) | getActiveWorksheet() / getWorksheet() |
❌ 変換できなかった機能
| VBAの機能 | 変換できない理由 | 代替・備考 |
|---|---|---|
keybd_event(スリープ防止) | Win32 API:OSレイヤー不可 | クラウド実行のため不要 |
Sleep(待機) | Win32 API | 同上 |
UserForm・プログレスバー | UI部品なし | console.logで代替 |
DoEvents・キャンセルボタン | イベント駆動なし | 代替手段なし |
Application.ScreenUpdating | 対応APIなし | 不要(クラウドは常に最適) |
MsgBox | ダイアログなし | console.logのみ |
#If VBA7 Then(条件コンパイル) | 不要 | Office Scriptsは64bit固定 |
UserForm(プログレスバー・キャンセルボタン)が失われる影響
今回のVBAで最も「使い勝手の差」として現れるのが、UserFormの消滅です。
元のVBAでは処理中に以下のUIが表示されていました。
' ループ内で毎回更新されるプログレスバー
With UserForm1
.Show vbModeless ' モードレスで表示(裏で処理継続)
.ProgressBar1.Value = tgetTmpR ' バーの進捗を更新
.Label1.Caption = percent & "%完了【処理件数: " & _
tgetTmpR & " / " & workShEndR & " 】" & _
"【HIT件数:" & lngHcount & "件】"
End With
' キャンセルボタンが押されたら中断
If UserForm1.blCancel = True Then
Unload UserForm1
MsgBox "処理を中断しました。"
End
End Ifこれによりユーザーは処理中に以下を把握できていました。
| VBAで表示されていた情報 | Office Scriptsでの状況 |
|---|---|
| 進捗バー(視覚的な%表示) | ❌ 表示不可 |
| 処理件数 / 総件数(リアルタイム) | ❌ リアルタイム更新不可 |
| HIT件数(照合成功数) | ❌ リアルタイム更新不可 |
| キャンセルボタン(途中中断) | ❌ 中断手段なし |
| 処理完了後のMsgBox(時間・件数) | ❌ ダイアログ表示不可 |
Office Scriptsで代替できるのは console.log のみです。
// 1000件ごとにコンソールへ出力(プログレスバーの代替)
if (tgetTmpR % 1000 === 0) {
console.log(`処理中... ${tgetTmpR} / ${workShEndR} 件`);
}
// 完了後の出力(MsgBoxの代替)
console.log(`完了!【データ更新件数: ${lngHcount} 件】`);ただし console.log はスクリプトエディタの「出力」タブにのみ表示され、処理中にリアルタイムで更新されるわけではありません。スクリプトが完了して初めてまとめて表示されます。
なぜUserFormは変換できないのか
Office Scriptsは TypeScript をベースとしたサーバーサイドの処理エンジンとして動作します。ウィンドウやダイアログを表示する仕組み(UI スレッド)を持たないため、VBAの UserForm・MsgBox・DoEvents に相当するAPIが存在しません。
処理の見える化が必要な場合は、Power Automate と組み合わせることで対応できます(次回以降で解説予定)。
「変換できない=移行できない」ではない
keybd_event によるスリープ防止は変換できませんでしたが、Office Scriptsではそもそもこの問題が発生しません。
VBAのスリープ問題はPCがスリープ状態になるために起きます。Office ScriptsはMicrosoftのクラウドサーバー上で実行されるため、PCの電源状態に関係なく処理が継続します。
つまり削除された機能が不要になったのであり、機能が失われたわけではありません。
まとめ:AIへの変換依頼チェックリスト
- AIにVBAコードだけ渡しても、シート構造を理解した変換はできない
- シートの行構造・特殊列の意味・設定セルの用途を事前に説明することで変換品質が上がる
- Win32 APIは変換不可だが、クラウド実行環境ではそもそも不要になるケースがある
- UserForm・プログレスバー・キャンセルボタンは代替手段がなく、設計の見直しが必要
- 変換依頼時は「変換できない場合の対応方針」も明示するとよい
変換依頼プロンプトのテンプレート:
以下のVBAコードをOffice Scriptsに変換してください。
【シートの構造】
(シートの行・列構成を具体的に説明)
【変換の条件】
– 変換できない機能は削除し、理由をコメントで明記すること
– TypeScript形式で記述すること
– VBAとの対応関係がわかるようにコメントを入れること
– ヘルパー関数に分けて整理すること
【VBAコード】
(コードを貼り付け)
関連記事
- 【Web版Excel】VBAをOffice Scriptsに移行する方法
- 【保存版】VBA→Office Scripts 書換チートシート
- 生成AIでVBAをOffice Scriptsに変換できるか?
- スリープ対策VBA(keybd_event)の解説記事
サンプルファイルのダウンロード
この記事で使用したテスト用Excelファイルはこちらからダウンロードできます。
サンプルファイルにはスクリプトは登録されていません。 ダウンロードしたExcelファイルを開き、「自動化」タブ >「新しいスクリプト」を開き、「変換後のOffice Scriptsコード」をコピーして貼り付けて保存してください。
想定される質問・エラー対処
Q. 「▼マークが見つかりません」と表示される
→ アクティブシートが「貼付元」になっているか確認してください。貼付先シートで実行すると、2行目の構成が異なるためエラーになります。
Q. 「シート “〇〇” が見つかりません」と表示される
→ K1セルに入力した貼付先シート名と、実際のシート名(タブ名)が一致しているか確認してください。全角・半角・スペースの違いでもエラーになります。
Q. 転記件数が0件になる
→ 以下を確認してください。
- 貼付元と貼付先の2行目にマッピング番号(1, 2…)が正しく入っているか
- マッピング番号はデータが入っている列に置く(別の空き列ではなく)
- G1のデータ開始行番号が正しいか(見出し行の次の行番号)
当サイトの記事で使用したVBAなどのサンプルをDLできます
ダウンロードページへは下のカードをクリックすればジャンプできます。
よろしければご利用ください!

