AUTOMATION NOTE — 068

退職者アカウントの放置ファイルを棚卸しする実践ガイド:Google DriveとGASで自動化する

Google Workspaceの管理コンソールには、Driveアクティビティを詳細に確認できる新しい監査ログ機能が追加されています。しかし、この機能だけで退職者アカウントに紐づく放置ファイルを直接一覧化し、棚卸しするのは困難な場合があります。本記事では、この課題に対し、Admin SDK Reports APIとGoogle Apps Script (GAS) を組み合わせた実践的なアプローチで、放置ファイルの特定とクリーンアップを行う方法を解説します。

この記事を読んだほうが良い人

  • 100名規模の企業でGoogle Workspaceを管理している情シス担当者
  • 退職者アカウントのデータ引き継ぎが手運用で、放置ファイルによるストレージ圧迫に課題を感じている方
  • Google Driveのデータガバナンスを強化したいと考えている方
  • Admin SDK Reports APIやGoogle Apps Script (GAS) を使った実務改善に興味がある方

Google Driveの放置ファイルが情シスを悩ませる背景

企業のGoogle Workspace環境において、退職者アカウントのGoogle Driveにファイルが放置されたままになる問題は、多くの情シス担当者が直面する課題です。退職時にデータ引き継ぎが行われても、すべてが適切に移行されるとは限りません。

退職者アカウントのデータ引き継ぎの課題

退職者のGoogle Driveデータは、Google Vaultやオーナー移管機能を使って別のユーザーや共有ドライブに引き継ぐことが一般的です。しかし、以下のようなケースでファイルが放置されることがあります。

  • 引き継ぎ対象外と判断されたファイル: 個人利用と判断されたが、実際には業務関連のファイルだった。
  • 引き継ぎ漏れ: 膨大なファイルの中からすべてを特定し、適切に引き継ぐのは困難。
  • 共有ドライブ外のファイル: 共有ドライブに移行されず、個人のマイドライブに残されたままのファイル。
  • 引き継ぎ設定前の退職者: 自動オーナー移管設定が導入される前の退職者のファイル。

これらのファイルは、退職者アカウントが停止された後もGoogle Drive上に残り続け、誰もアクセスしない「放置ファイル」となります。

ストレージ圧迫とセキュリティリスク

放置ファイルが増えると、以下のような問題が発生します。

  • ストレージ容量の圧迫: 特に企業全体のストレージ容量に上限がある場合、無駄なファイルが容量を圧迫し、コスト増につながります。
  • セキュリティリスク: 誰にも管理されていないファイルは、意図せず共有設定が残っていたり、機密情報が含まれていたりするリスクがあります。監査や情報漏洩のリスク管理の観点からも望ましくありません。

Google Driveの「新しい監査ログ」と既存レポートでできること、できないこと

Google Workspace管理コンソールには、Driveの活動を監視するための「監査ログ」機能が強化されています(Google Workspace Updates、2023年9月20日付)。

新しいDrive監査ログで強化された点

この新しい監査ログは、ファイルやフォルダに対するユーザーのアクティビティ(作成、編集、移動、削除、共有設定の変更など)を詳細に記録し、管理者がよりきめ細かく監視できるように設計されています。特定のユーザーがどのファイルにどのような操作を行ったか、共有ドライブ内のアクティビティなどを追跡するのに役立ちます。

オーナー不在ファイルの特定における限界

しかし、この監査ログはあくまで「アクティビティの記録」であり、直接的に「オーナーが退職者であるファイル」や「最終更新日が著しく古い放置ファイル」を一覧で抽出する機能ではありません。管理コンソールで提供される既存のレポート機能も、ストレージ利用状況の概要は把握できますが、個々のファイルのオーナー情報や最終更新日を詳細にフィルタリングしてリスト化する用途には向いていません。

そのため、退職者アカウントの放置ファイルを効率的に棚卸しするには、Admin SDK Reports APIとGoogle Apps Script (GAS) を活用し、より実践的なアプローチを取る必要があります。

Admin SDK Reports APIとGASで放置ファイルを特定する実践フロー

ここでは、Admin SDK Reports APIとGoogle Apps Script (GAS) を用いて、退職者アカウントがオーナーとなっている放置ファイルを特定し、棚卸しを行う具体的なフローを解説します。

1. Admin SDK Reports APIとDrive APIを有効化する

Google Apps Script (GAS) からGoogle Workspaceの情報を取得するには、関連するAPIを有効化する必要があります。

  1. GASプロジェクトの作成: Google Driveから新しいGoogle Apps Scriptプロジェクトを作成します。
  2. Advanced Google Servicesの有効化: - GASエディタの左側メニューにある「サービス」アイコン(+ のようなマーク)をクリックします。 - 「Admin SDK API」と「Drive API」を検索し、それぞれ「追加」をクリックして有効化します。

2. 退職者アカウントのリストを取得する

放置ファイルを特定するには、まず「どのユーザーが退職者であるか」のリストが必要です。Admin SDK Directory APIを使って、停止済み(suspended)ユーザーのメールアドレスリストを取得します。

/**
 * 停止済み(退職済み)ユーザーのメールアドレスリストを取得する
 * @returns {string[]} 退職者のメールアドレスリスト
 */
function getDepartedUserEmails() {
  const departedEmails = [];
  let pageToken = null;
  do {
    const response = AdminDirectory.Users.list({
      customer: 'my_customer', // ドメイン全体のユーザーを取得
      query: 'isSuspended=true', // 停止済みユーザーをフィルタリング
      maxResults: 500, // 1ページあたりの取得数
      pageToken: pageToken
    });

    if (response.users) {
      response.users.forEach(user => {
        if (user.primaryEmail) {
          departedEmails.push(user.primaryEmail);
        }
      });
    }
    pageToken = response.nextPageToken;
  } while (pageToken);

  Logger.log(`停止済みユーザーアカウント数: ${departedEmails.length}`);
  return departedEmails;
}

customer: 'my_customer' は、組織の全ユーザーを対象とします。特定のドメインを指定する場合は、ドメイン名を文字列で指定します。

3. Drive APIでDriveファイルを列挙する

次に、組織内のすべてのGoogle Driveファイル(共有ドライブ、ユーザーマイドライブ含む)を列挙し、オーナー情報や最終更新日などの詳細を取得します。Drive.Files.list() メソッドを使用します。

/**
 * Google Driveの放置ファイルを特定し、スプレッドシートに出力する
 */
function exportDepartedUserFiles() {
  const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID'; // ★ここに出力先スプレッドシートのIDを設定してください★
  const SHEET_NAME = '放置ファイルリスト';

  const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
  let targetSheet = ss.getSheetByName(SHEET_NAME);
  if (!targetSheet) {
    targetSheet = ss.insertSheet(SHEET_NAME);
  } else {
    targetSheet.clearContents(); // 既存のシートをクリア
  }

  // ヘッダー行を書き込む
  const headers = ['ファイル名', 'ファイルID', 'オーナーメールアドレス', '最終更新日時', 'ファイルサイズ (MB)', 'MIMEタイプ', '共有状況', 'URL', '最終更新からの経過日数'];
  targetSheet.getRange(1, 1, 1, headers.length).setValues([headers]).setFontWeight('bold');
  let row = 2;

  const departedUsers = getDepartedUserEmails();
  if (departedUsers.length === 0) {
    Logger.log('退職者アカウントが見つかりませんでした。処理を終了します。');
    SpreadsheetApp.getUi().alert('注意', '退職者アカウントが見つかりませんでした。', SpreadsheetApp.getUi().ButtonSet.OK);
    return;
  }

  Logger.log('Driveファイルの検索を開始します...');
  let pageToken = null;
  const today = new Date();

  do {
    const response = Drive.Files.list({
      q: "trashed = false and mimeType != 'application/vnd.google-apps.folder'", // ゴミ箱にない、フォルダではないファイル
      fields: 'nextPageToken, files(id, name, owners, modifiedTime, size, mimeType, shared, webViewLink)',
      pageSize: 1000, // 1ページあたりの取得数を最大化
      pageToken: pageToken,
      corpora: 'allDrives', // 共有ドライブとマイドライブの両方を対象
      includeItemsFromAllDrives: true,
      supportsAllDrives: true
    });

    if (response.files) {
      response.files.forEach(file => {
        if (file.owners && file.owners.length > 0) {
          const ownerEmail = file.owners[0].emailAddress; // 最初のオーナーを取得

          // オーナーが退職者リストに含まれるかチェック
          if (departedUsers.includes(ownerEmail)) {
            const modifiedTime = new Date(file.modifiedTime);
            const daysSinceLastModified = Math.floor((today.getTime() - modifiedTime.getTime()) / (1000 * 60 * 60 * 24));

            // 例: 最終更新日が1年以上前 (365日以上) のファイルを「放置ファイル」と定義
            if (daysSinceLastModified >= 365) {
              const fileSizeMB = file.size ? (file.size / (1024 * 1024)).toFixed(2) : 'N/A';
              targetSheet.getRange(row, 1, 1, headers.length).setValues([[
                file.name,
                file.id,
                ownerEmail,
                file.modifiedTime,
                fileSizeMB,
                file.mimeType,
                file.shared ? '共有' : '非共有',
                file.webViewLink,
                daysSinceLastModified
              ]]);
              row++;
            }
          }
        }
      });
    }
    pageToken = response.nextPageToken;
  } while (pageToken);

  Logger.log(`処理完了。${row - 2} 件の放置ファイルを特定し、スプレッドシートに出力しました。`);
  SpreadsheetApp.getUi().alert('完了', `処理が完了しました。${row - 2} 件の放置ファイルを特定し、スプレッドシートに出力しました。`, SpreadsheetApp.getUi().ButtonSet.OK);
}

スクリプト実行前の準備:

  1. 出力先スプレッドシートの作成: 新しいGoogleスプレッドシートを作成し、そのURLからID(YOUR_SPREADSHEET_ID の部分)を取得してスクリプトに設定します。
  2. GASの実行権限承認: 初回実行時に、Admin SDK APIとDrive APIへのアクセス権限を求められますので、承認してください。

このスクリプトは、以下の条件でファイルをフィルタリングします。

  • ゴミ箱にないファイル(trashed = false
  • フォルダではないファイル(mimeType != 'application/vnd.google-apps.folder'
  • オーナーが退職者リストに含まれるファイル(かつ最終更新から365日以上経過)

4. 棚卸し結果をメールで通知する(応用)

exportDepartedUserFiles() の実行後にそのまま放置するよりも、件数と容量をメールで通知しておくと管理記録として残せます。以下の関数は出力済みスプレッドシートを集計し、実行者のメールアドレス宛にサマリーを送信するものです。月次の定期実行と組み合わせると、毎回スプレッドシートを開かなくても状況を把握できます。

/**
 * 棚卸し結果スプレッドシートを集計し、管理者にサマリーメールを送信する
 * exportDepartedUserFiles() 実行後に呼び出す
 */
function sendAuditSummaryEmail() {
  const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID'; // exportDepartedUserFiles と同じIDを設定
  const SHEET_NAME = '放置ファイルリスト';

  const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
  const sheet = ss.getSheetByName(SHEET_NAME);
  if (!sheet) {
    Logger.log('シートが見つかりません。先に exportDepartedUserFiles() を実行してください。');
    return;
  }

  const lastRow = sheet.getLastRow();
  const totalFiles = lastRow - 1; // ヘッダー行を除く
  if (totalFiles <= 0) {
    Logger.log('放置ファイルが0件のため、メール通知をスキップします。');
    return;
  }

  // ファイルサイズ列(5列目)を集計
  const sizeValues = sheet.getRange(2, 5, totalFiles, 1).getValues();
  const totalSizeMB = sizeValues.reduce((sum, row) => {
    const v = parseFloat(row[0]);
    return sum + (isNaN(v) ? 0 : v);
  }, 0);

  const adminEmail = Session.getActiveUser().getEmail();
  const dateStr = new Date().toLocaleDateString('ja-JP');
  const subject = `[Drive棚卸し完了] 退職者オーナーファイル ${totalFiles} 件 / ${totalSizeMB.toFixed(0)} MB を検出 (${dateStr})`;
  const body = [
    '■ Google Drive 放置ファイル棚卸しレポート',
    `実行日時: ${new Date().toLocaleString('ja-JP')}`,
    '─────────────────────',
    `検出ファイル数: ${totalFiles} 件`,
    `合計サイズ: ${totalSizeMB.toFixed(0)} MB`,
    '─────────────────────',
    '次のアクションを確認してください:',
    '1. 業務関連ファイルの引き継ぎ先を部門管理者に確認する',
    '2. 「共有」ステータスのファイルは共有設定を即時取り消す',
    '3. 不要ファイルは Vault アーカイブ後に削除する',
  ].join('\n');

  GmailApp.sendEmail(adminEmail, subject, body);
  Logger.log(`棚卸しサマリーを ${adminEmail} に送信しました。`);
}

件数・合計容量・次のアクション一覧を本文に含めることで、メール単体でも何をすべきかが分かる構成にしています。GmailApp.sendEmail() は GAS の標準サービスなので、追加の API 有効化は不要です。

棚卸し結果をどう活用するか

スクリプト実行後、スプレッドシートには退職者オーナーの放置ファイル一覧が出力されます。この一覧をそのまま放置せず、以下のステップで処理を進めます。

業務関連ファイルの確認と引き継ぎ: ファイル名やMIMEタイプ(Googleスプレッドシート、Googleドキュメントなど)を手がかりに、業務関連と判断されるファイルを該当部門のマネージャーに確認します。必要なファイルは共有ドライブへの移動またはオーナー変更を行います。

不要ファイルの削除: 業務関連でないと確認できたファイルは削除します。削除前にGoogle Vaultでアーカイブしておくと、後から復元が必要になった場合も対応できます。

共有設定の見直し: 出力結果の「共有状況」列が「共有」になっているファイルは特に注意が必要です。退職者のアカウントが停止されていても、ファイルへのリンクが外部に流出している可能性があります。管理コンソールの「ドライブとドキュメント」設定から共有を取り消すか、ファイルを削除します。

運用上の注意点

このGASスクリプトを実際に使う際に押さえておきたい制約があります。

まず、Drive.Files.list()corpora: 'allDrives' を指定しても、共有ドライブ内のファイルは owners フィールドが返らない点に注意が必要です。共有ドライブにはオーナーという概念がなく、代わりにドライブ自体が所属する組織が管理します。このスクリプトが対象とするのは主に退職者のマイドライブに残ったファイルです。共有ドライブの管理は別途、管理コンソールの共有ドライブ一覧から行います。

次に、組織のDriveファイル数が非常に多い場合(数十万件規模)、GASの実行時間制限(6分)に引っかかる可能性があります。その場合は、pageToken を利用した分割実行や、処理対象ユーザーを退職者の一部に絞るなどの工夫が必要です。

また、このスクリプトを実行するGoogleアカウントには、管理コンソールの「サービス」→「Admin SDK」へのアクセス権と、ドメイン全体のDriveファイルを閲覧できる権限(スーパー管理者またはDrive閲覧者ロール)が必要です。

まとめ

Google Workspace標準の監査ログは、アクティビティの追跡には有用ですが、退職者オーナーの放置ファイルを一覧化する用途には直接使えません。Admin SDK Directory APIで停止済みユーザーを取得し、Drive APIでファイルと突き合わせるGASスクリプトを組み合わせることで、棚卸し作業を自動化できます。

まず小規模なテスト(退職者1〜2名分)で動作を確認してから、全退職者を対象に実行することをお勧めします。棚卸し結果は放置するのではなく、部門確認・引き継ぎ・削除の判断フローと組み合わせて初めて意味を持ちます。定期実行(月次など)をトリガーに設定しておくと、退職者が増えるたびに手動で走らせる手間を省けます。

コーポレートITのご相談はお気軽に

この記事で書いたような業務改善・自動化の設計から実装まで、DRASENASではコーポレートITの現場に寄り添った支援を行っています。 「まず相談だけ」でも大歓迎です。DRASENAS 公式サイトからお気軽にどうぞ。

CONTACT

御社の IT 部門、ここにあります。

「ITのことはあまりわからない」── そのような状態からで、まったく問題ございません。まずはお気軽にご相談ください。

一社ずつ、一から。