Driveバックアップのコピー事故を実行前に防ぐ|GASで計画表をdry-run監査

Driveバックアップのコピー事故を実行前に防ぐ|GASで計画表をdry-run監査

ツールおじ|GoogleアカウントをAIエージェント化

ツールおじ|GoogleアカウントをAIエージェント化

バックアップを自動化する前に、元フォルダと保存先が同じ、保持する世代数が空欄、有効化したまま手動確認が終わっていない、といった設定ミスが残ることがあります。そのままコピー処理を動かすと、同じファイルが増え続けたり、意図しない場所へ保存したりする原因になります。

この記事ではGoogle Driveへ接続せず、スプレッドシート上のバックアップ計画だけを点検する無料GASを紹介します。ファイルのコピー、削除、移動、トリガー作成は行わず、ログには行番号とエラー分類だけを残します。

先に知っておきたいこと

  • できること:必須項目、元フォルダと保存先の重複、頻度、保持世代数、有効化、手動確認、重複計画を点検します。
  • できないこと:フォルダが実在するか、権限があるか、容量が足りるか、実際にバックアップできるかは判断しません。
  • 安全設計:Google Driveへ接続せず、セルの更新、ファイル操作、メール送信、外部通信、トリガー作成を行いません。
  • ログ範囲:フォルダIDや計画名は出さず、行番号とエラー分類だけを記録します。
  • 停止条件:dryRunがtrueでない、見出しが一致しない、確認対象が200行を超える場合は処理を止めます。

学習・検証用の例です。最初は元シートのコピーと架空のフォルダIDで試してください。

準備するシート

シート名を「バックアップ計画」にします。1行目のA列からG列へ、次の見出しを入力してください。

  1. 計画名
  2. 元フォルダID
  3. 保存先フォルダID
  4. 頻度
  5. 保持世代数
  6. 有効
  7. 手動確認

頻度は「毎日」「毎週」「毎月」のいずれか、保持世代数は1から30までの整数にします。有効の列にはチェックボックスを置き、監査中はオフにしてください。手動確認には内容を確認した行だけ「済」と入力します。

テスト用には架空データを2〜4行だけ用意します。実際のフォルダID、顧客名、ファイル名、メールアドレスは使わないでください。

コピーして使える最小コード

function auditDriveBackupPlan() {
  const config = {
    sheetName: 'バックアップ計画',
    maxRows: 200,
    dryRun: true
  };

  if (config.dryRun !== true) {
    throw new Error('安全のためdryRunはtrue固定です。');
  }

  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(config.sheetName);
  if (!sheet) {
    throw new Error('「バックアップ計画」シートが見つかりません。');
  }

  const expectedHeaders = [
    '計画名',
    '元フォルダID',
    '保存先フォルダID',
    '頻度',
    '保持世代数',
    '有効',
    '手動確認'
  ];
  const actualHeaders = sheet.getRange(1, 1, 1, expectedHeaders.length)
    .getDisplayValues()[0]
    .map(function(value) {
      return String(value).trim();
    });

  const headerMismatch = expectedHeaders.some(function(header, index) {
    return actualHeaders[index] !== header;
  });
  if (headerMismatch) {
    throw new Error('A1:G1の見出しを手順どおりにしてください。');
  }

  const targetCount = Math.max(0, sheet.getLastRow() - 1);
  if (targetCount > config.maxRows) {
    throw new Error('確認対象が200行を超えています。範囲を分けてください。');
  }

  const rows = targetCount === 0
    ? []
    : sheet.getRange(2, 1, targetCount, expectedHeaders.length).getValues();
  const allowedFrequencies = ['毎日', '毎週', '毎月'];
  const seen = {};
  const issues = [];

  rows.forEach(function(row, index) {
    const rowNumber = index + 2;
    const name = String(row[0]).trim();
    const sourceId = String(row[1]).trim();
    const destinationId = String(row[2]).trim();
    const frequency = String(row[3]).trim();
    const generations = Number(row[4]);
    const enabled = row[5];
    const confirmed = String(row[6]).trim();

    const isBlank = row.every(function(value) {
      return value === '';
    });
    if (isBlank) {
      return;
    }

    if (!name) {
      issues.push({ row: rowNumber, code: 'NAME_EMPTY' });
    }
    if (!sourceId) {
      issues.push({ row: rowNumber, code: 'SOURCE_EMPTY' });
    }
    if (!destinationId) {
      issues.push({ row: rowNumber, code: 'DESTINATION_EMPTY' });
    }
    if (sourceId && destinationId && sourceId === destinationId) {
      issues.push({ row: rowNumber, code: 'SAME_FOLDER' });
    }
    if (allowedFrequencies.indexOf(frequency) === -1) {
      issues.push({ row: rowNumber, code: 'FREQUENCY_INVALID' });
    }
    if (!Number.isInteger(generations) || generations < 1 || generations > 30) {
      issues.push({ row: rowNumber, code: 'GENERATIONS_INVALID' });
    }
    if (enabled !== false) {
      issues.push({ row: rowNumber, code: 'ENABLED_NOT_FALSE' });
    }
    if (confirmed !== '済') {
      issues.push({ row: rowNumber, code: 'MANUAL_CHECK_MISSING' });
    }

    if (sourceId && destinationId && allowedFrequencies.indexOf(frequency) !== -1) {
      const planKey = sourceId + '|' + destinationId + '|' + frequency;
      if (seen[planKey]) {
        issues.push({ row: rowNumber, code: 'DUPLICATE_PLAN' });
      } else {
        seen[planKey] = true;
      }
    }
  });

  Logger.log('dry-run: true');
  Logger.log('確認行数: ' + targetCount);
  if (issues.length === 0) {
    Logger.log('入力上の不整合: 0件');
  } else {
    issues.forEach(function(issue) {
      Logger.log('行' + issue.row + ': ' + issue.code);
    });
  }
  Logger.log('計画名とフォルダIDはログへ出していません。');

  return {
    status: issues.length === 0 ? 'OK' : 'HOLD',
    checked: targetCount,
    issues: issues.length
  };
}

実行手順

  1. 元のスプレッドシートをコピーします。
  2. コピー側に「バックアップ計画」シートを作り、見出しと架空データを入力します。
  3. 有効の列へチェックボックスを設定し、すべてオフにします。
  4. Apps Scriptへコードを貼り付け、auditDriveBackupPlanを実行します。
  5. 実行ログで行番号とエラー分類を確認します。
  6. エラーが出た行だけを見直し、もう一度dry-runを実行します。

このコードはスプレッドシートの読み取り権限を使います。Google Driveのファイルには接続しません。

エラー分類の読み方

  • NAME_EMPTY:計画名が空欄です。
  • SOURCE_EMPTY:元フォルダIDが空欄です。
  • DESTINATION_EMPTY:保存先フォルダIDが空欄です。
  • SAME_FOLDER:元フォルダと保存先に同じIDが入力されています。
  • FREQUENCY_INVALID:頻度が許可した3種類に一致しません。
  • GENERATIONS_INVALID:保持世代数が1から30までの整数ではありません。
  • ENABLED_NOT_FALSE:有効がオフになっていません。
  • MANUAL_CHECK_MISSING:手動確認が「済」ではありません。
  • DUPLICATE_PLAN:元フォルダ、保存先、頻度の組み合わせが前の行と重複しています。

同じ行に複数の不整合がある場合は、エラーも複数表示されます。ログにはフォルダIDを出さないため、行番号を使ってシート側の入力を確認してください。

このコードが判断しないこと

フォルダIDの文字列が入っていても、そのフォルダが実在するとは限りません。このコードはDriveAppを使わないため、閲覧権限、共有範囲、空き容量、ファイル数、コピー所要時間は確認しません。

「入力上の不整合が0件」は、計画表の形式と組み合わせに問題が見つからなかったという意味です。実行してよいことや、バックアップが成功することを保証する結果ではありません。

実データへ進む前に、権限のある担当者が元フォルダと保存先を開き、少量のテストファイルでコピー結果を確認してください。自動実行トリガーは、その確認が終わるまで作成しないでください。

公開前チェックリスト

  • 元フォルダと保存先が別になっている。
  • 有効のチェックボックスがオフになっている。
  • 保持世代数の上限を決めている。
  • ログへ計画名、フォルダID、ファイル名を出していない。
  • 少量の架空データでdry-runを終えている。
  • 実行前に元フォルダと保存先の権限を人が確認する。

まとめ

バックアップ処理では、コピーするコードより先に元フォルダ、保存先、頻度、保持世代数、有効化の条件を点検する必要があります。今回のコードはGoogle Driveへ接続せず、計画表だけをdry-runで確認します。

まずはコピーしたシートと架空データで実行し、エラーが出た行だけを直してください。実際のコピー処理と自動実行は、フォルダの実在、権限、容量を人が確認してから別工程で進めます。

複数フォルダやサブフォルダを一覧にして、バックアップ対象を整理したい方へ

DriveFileList Pro|Google Driveのファイル一覧化GASテンプレ

GasおじのTips商品一覧を見る

※学習・検証用の例です。実データへ適用する前にコピー環境で確認してください。Google公式・Google公認のツールではありません。


あなたも記事の投稿・販売を
始めてみませんか?

Tipsなら簡単に記事を販売できます!
登録無料で始められます!

Tipsなら、無料ですぐに記事の販売をはじめることができます Tipsの詳細はこちら
 

この記事の販売者

ツールおじ|GoogleアカウントをAIエージェント化

おう、Gasおじだ。 AIツールとSaaSに散々突っ込んで遠回りした結果、最後に強かったのは「今あるGoogleアカウント」だった。 今ではAIエージェントであらゆるタスクを自動化して無双してるぞ。 GAS、スプレッドシート、フォーム、Gmail、AIエージェントを組み合わせれば、個人でも小さな会社でも、手作業だらけの業務はかなり自動化できる。 ここでは机上のAI論じゃなく、現場で動くテンプレ、コード、手順だけを置いていくぞ。 高いツールを増やす前に、まずお前さんのGoogleアカウントを仕事するエージェントに変えようじゃないか。

この販売者が書いた他の記事

  • Gmailの予約送信ミスを送信前に発見|GASで宛先・日時をdry-run監査

  • スプレッドシートの入力ミスを公開前に発見|GASでルール表をdry-run監査

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)

関連のおすすめ記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化

  • USBメモリで持ち運べるLinux Mint 22.1と永続的な書き込み領域の作成法【Rufus だけで作成する方法を初心者向けに徹底解説】

    ¥500
    1 %獲得
    (5 円相当)
    SmartSenior

    SmartSenior

  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化