[総務 × GAS] Googleドライブのファイルに一括で権限付与してみた

[総務 × GAS] Googleドライブのファイルに一括で権限付与してみた

2026.02.19

こんにちは!徐(Seo)です。

前回はGASを使ったGoogleドライブ内のファイル名一括変更での業務改善をご紹介しました。
[総務 × GAS] Googleドライブのファイル名を一括で変更してみた

今回は、Googleドライブ内のファイルに一括で個別の権限付与をご紹介します!

↓↓↓↓手順はこちら↓↓↓↓

①ドライブ操作用スプレッドシートの作成

新しいスプレッドシートを作成し、以下の通りタイトル行を作成しておきます。
A1:ファイルID
B1:ファイルURL
C1:ファイル名
D1:権限付与アドレス
kaizen4-1

②GASの設定

拡張機能 → Apps Scriptと進み、自動で最初に入力されているスクリプトを削除し、以下スクリプトを貼り付けて保存します。

/**
 * スプレッドシート起動時にカスタムメニューを追加
 */
function onOpen() {
  const UI = SpreadsheetApp.getUi();

  // カスタムメニューを作成
  UI.createMenu('メニュー')
    .addItem('ファイル情報取得', 'ファイル情報取得')
    .addItem('権限付与', '権限付与')
    .addToUi();
}

/**
 * Googleドライブフォルダからファイル情報を取得してシートに出力
 */
function ファイル情報取得() {
  const ACTIVE_SPREADSHEET = SpreadsheetApp.getActiveSpreadsheet(); 
  const SHEET = ACTIVE_SPREADSHEET.getSheetByName('シート1');

  // A2:D列をクリア
  const RANGE = SHEET.getRange('A2:D');
  RANGE.clear();

  // ポップアップでフォルダリンクを入力
  const FOLDER_LINK = Browser.inputBox(
    'フォルダリンクを入力', 
    'Googleドライブのフォルダリンクを入力してください', 
    Browser.Buttons.OK_CANCEL
  );

  // キャンセルされた場合は処理を中断
  if (FOLDER_LINK === 'cancel') {
    return;
  }

  // フォルダIDを抽出
  const FOLDER_ID = extractFolderIdFromLink(FOLDER_LINK);

  if (!FOLDER_ID) {
    Browser.msgBox('無効なフォルダリンクです。正しいリンクを入力してください。');
    return;
  }

  try {
    const FOLDER = DriveApp.getFolderById(FOLDER_ID);

    // フォルダ名を取得
    const FOLDER_NAME = FOLDER.getName();

    // 確認ダイアログ
    const CONFIRMED = Browser.msgBox(
      '以下フォルダからファイルを取得します', 
      FOLDER_NAME + '\n\n間違いなければ【OK】を押してください', 
      Browser.Buttons.OK_CANCEL
    );

    // キャンセルされた場合は処理を中断
    if (CONFIRMED === 'cancel') {
      return;
    }

    const FILES = FOLDER.getFiles();
    const FILE_ARRAY = [];

    // ファイル情報を配列に格納
    while (FILES.hasNext()) {
      const FILE = FILES.next();
      const ID = FILE.getId();
      const URL = FILE.getUrl();
      const NAME = FILE.getName();
      FILE_ARRAY.push([ID, URL, NAME, '']);
    }

    // ファイル名(C列)で昇順にソート
    FILE_ARRAY.sort((a, b) => {
      if (a[2] < b[2]) return -1;
      if (a[2] > b[2]) return 1;
      return 0;
    });

    // ソート済みのデータを書き込む
    if (FILE_ARRAY.length > 0) {
      const DATA_RANGE = SHEET.getRange(2, 1, FILE_ARRAY.length, FILE_ARRAY[0].length);
      DATA_RANGE.setValues(FILE_ARRAY);
    }

    // 取得したファイル数を含むメッセージ
    Browser.msgBox(`${FILE_ARRAY.length}件のファイルを取得し、ファイル名順にソートしました。`);

  } catch (error) {
    // エラーハンドリング
    Browser.msgBox('エラーが発生しました: ' + error.message);
  }
}

/**
 * フォルダリンクからフォルダIDを抽出
 * @param {string} link - GoogleドライブのフォルダリンクまたはID
 * @return {string|null} - 抽出されたフォルダID、失敗時はnull
 */
function extractFolderIdFromLink(link) {
  // 異なる形式のリンクに対応
  const PATTERNS = [
    /\/folders\/([a-zA-Z0-9\-_]+)/,  // 標準的なGoogleドライブフォルダリンク
    /id=([a-zA-Z0-9\-_]+)/,           // 別の形式のリンク
    /^([a-zA-Z0-9\-_]+)$/             // 直接IDが入力された場合
  ];

  for (const PATTERN of PATTERNS) {
    const MATCH = link.match(PATTERN);
    if (MATCH && MATCH[1]) {
      return MATCH[1];
    }
  }

  return null;
}

/**
 * D列のメールアドレスに対してファイルの権限を付与
 */
function 権限付与() {
  // HTMLダイアログを作成
  const HTML_CONTENT = `
    <!DOCTYPE html>
    <html>
      <head>
        <base target="_top">
        <style>
          body {
            font-family: Arial, sans-serif;
            padding: 20px;
          }
          .radio-group {
            margin: 20px 0;
          }
          .radio-item {
            margin: 10px 0;
          }
          .button-group {
            margin-top: 20px;
            text-align: center;
          }
          button {
            padding: 10px 20px;
            margin: 0 5px;
            cursor: pointer;
            border-radius: 4px;
          }
          #submitBtn {
            background-color: #4CAF50;
            color: white;
            border: none;
          }
          #submitBtn:hover {
            background-color: #45a049;
          }
          #submitBtn:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
          }
          #cancelBtn {
            background-color: #f44336;
            color: white;
            border: none;
          }
          #cancelBtn:hover {
            background-color: #da190b;
          }
          #cancelBtn:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
          }
        </style>
      </head>
      <body>
        <div class="radio-group">
          <div class="radio-item">
            <input type="radio" id="editor" name="permission" value="editor" checked>
            <label for="editor">編集者</label>
          </div>
          <div class="radio-item">
            <input type="radio" id="viewer" name="permission" value="viewer">
            <label for="viewer">閲覧者</label>
          </div>
          <div class="radio-item">
            <input type="radio" id="commenter" name="permission" value="commenter">
            <label for="commenter">閲覧者(コメント可)</label>
          </div>
        </div>

        <div class="button-group">
          <button id="submitBtn" onclick="submitPermission()">実行</button>
          <button id="cancelBtn" onclick="google.script.host.close()">キャンセル</button>
        </div>

        <script>
          function submitPermission() {
            // 選択されたラジオボタンの値を取得
            const selectedPermission = document.querySelector('input[name="permission"]:checked').value;

            // ボタンを無効化
            document.getElementById('submitBtn').disabled = true;
            document.getElementById('cancelBtn').disabled = true;

            // サーバー側の関数を実行
            google.script.run
              .withSuccessHandler(onSuccess)
              .withFailureHandler(onFailure)
              .executePermissionGrant(selectedPermission);
          }

          function onSuccess(result) {
            // 結果を表示
            alert(result);
            // ダイアログを閉じる
            google.script.host.close();
          }

          function onFailure(error) {
            // エラーを表示
            alert('エラーが発生しました: ' + error.message);
            // ボタンを再度有効化
            document.getElementById('submitBtn').disabled = false;
            document.getElementById('cancelBtn').disabled = false;
          }
        </script>
      </body>
    </html>
  `;

  // HTMLダイアログを表示
  const HTML = HtmlService.createHtmlOutput(HTML_CONTENT)
    .setWidth(350)
    .setHeight(300);

  SpreadsheetApp.getUi().showModalDialog(HTML, '権限の種類を選択');
}

/**
 * 選択された権限タイプで権限付与処理を実行
 * @param {string} permissionType - 権限タイプ(editor/viewer/commenter)
 * @return {string} - 処理結果メッセージ
 */
function executePermissionGrant(permissionType) {
  const ACTIVE_SPREADSHEET = SpreadsheetApp.getActiveSpreadsheet(); 
  const SHEET = ACTIVE_SPREADSHEET.getSheetByName('シート1');
  const LAST_ROW = SHEET.getLastRow();

  // エラーログ用の配列
  const ERROR_LOG = [];
  let SUCCESS_COUNT = 0;
  let SKIP_COUNT = 0;
  let SKIP_NO_ADDRESS_COUNT = 0;

  // 2行目から最終行まで処理
  for (let i = 2; i <= LAST_ROW; i++) {
    const TARGET_FILE_ID = SHEET.getRange('A' + i).getValue();
    const MAIL_ADDRESSES = SHEET.getRange('D' + i).getValue();

    // A列が空白の場合はスキップ
    if (!TARGET_FILE_ID) {
      continue;
    }

    // D列が空白の場合はカウントしてスキップ
    if (!MAIL_ADDRESSES) {
      SKIP_NO_ADDRESS_COUNT++;
      continue;
    }

    // メールアドレスを配列に変換(カンマ区切り対応)
    const MAIL_ARRAY = MAIL_ADDRESSES.toString().includes(',') 
      ? MAIL_ADDRESSES.toString().split(',').map(mail => mail.trim())
      : [MAIL_ADDRESSES.toString().trim()];

    try {
      const FILE = DriveApp.getFileById(TARGET_FILE_ID);

      // 各メールアドレスに権限付与
      MAIL_ARRAY.forEach(mail => {
        // 既存の権限をチェック
        const EDITORS = FILE.getEditors().map(editor => editor.getEmail());
        const VIEWERS = FILE.getViewers().map(viewer => viewer.getEmail());

        const HAS_PERMISSION = EDITORS.includes(mail) || VIEWERS.includes(mail);

        if (HAS_PERMISSION) {
          // 既に権限を持っている場合はスキップ
          SKIP_COUNT++;
          return;
        }

        // 権限の種類に応じて付与
        switch(permissionType) {
          case 'editor':
            FILE.addEditor(mail);
            break;
          case 'viewer':
            FILE.addViewer(mail);
            break;
          case 'commenter':
            FILE.addCommenter(mail);
            break;
        }

        SUCCESS_COUNT++;
      });
    } catch (error) {
      // エラーが発生した場合、ログに記録
      ERROR_LOG.push({
        row: i,
        fileId: TARGET_FILE_ID,
        emails: MAIL_ADDRESSES,
        errorMessage: error.message
      });
    }
  }

  // 結果メッセージの作成
  let RESULT_MESSAGE = `処理が完了しました。\n\n`;
  RESULT_MESSAGE += `成功: ${SUCCESS_COUNT}件\n`;
  RESULT_MESSAGE += `スキップ(既存権限): ${SKIP_COUNT}件\n`;
  RESULT_MESSAGE += `スキップ(アドレスなし): ${SKIP_NO_ADDRESS_COUNT}件\n`;

  if (ERROR_LOG.length > 0) {
    RESULT_MESSAGE += `エラー: ${ERROR_LOG.length}件\n\n`;
    RESULT_MESSAGE += 'エラー詳細はログを確認してください。';

    // エラーログを出力
    Logger.log('権限付与中にエラーが発生しました:');
    ERROR_LOG.forEach(log => {
      Logger.log(`行: ${log.row}, ファイルID: ${log.fileId}, メール: ${log.emails}, エラー: ${log.errorMessage}`);
    });
  }

  // 結果を返す
  return RESULT_MESSAGE;
}

kaizen4-2

③GAS実行(ファイル情報取得)

一度スプレッドシートをリロード(再読み込み) し、表示されたメニューから「ファイル情報取得」を実行します。
ポップアップが表示されるので、対象のGoogleドライブリンクを入力してOKをクリックします。
kaizen4-4

④権限を付与したいアドレスを入力

スプレッドシートA~C列に情報が入力されているので、D列にそれぞれ権限を付与しようとしているメールアドレスを入力します。
権限を付与しない場合は、行を削除するかD列にメールアドレスを入力せずに空白のまま置いておきます。

kaizen4-5

⑤GAS実行(権限付与)

③同様のメニューから「権限付与」を実行します。
「編集者」「閲覧者」「閲覧者(コメント可)」の権限から、一つ選択後に実行をクリックします。

「処理が完了しました。」と表示されるので
該当のGoogleドライブを確認すると、指定通りに権限が付与されているはずです!

次回からは③~⑤のみの実行で完了するので、かなりの時間短縮になります!

⏱作業時間の比較(100件の場合)

【従来の手動作業】
1件あたり約30秒 × 100件 = 約50分
①権限を1件ずつ付与

【GAS導入後】
• 初回のみ : ①②の設定(約10分)
①ドライブ操作用スプレッドシートの作成
②GASの設定

• 2回目以降: ③④⑤の実行のみ(約3分)
③ GAS実行(ファイル情報取得):約30秒
④ 権限付与アドレスを入力   :約30秒
⑤ GAS実行(権限付与)    :約2分

🎯 2回目以降の効果
50分 → 3分(約94%削減)

最後に

💪 総務が技術を身につける。それが当たり前の環境

「プログラミングなんて無理…」
以前は、そう思っていました。

でも今では、業務の課題を見つけたら、自分で解決策を考え、GASで実装する
それが日常になっています。

🎓 ゼロから始めた学習ステップ

  1. まずはExcel関数から
    VLOOKUP、IF文…基礎的な関数の理解から
  2. 次にスプレッドシートの独自関数
    QUERY、IMPORTRANGE など、Googleならではの機能を習得
  3. そしてGASへ
    「あれ?関数だけじゃ限界かも」と思ったタイミングで挑戦
  4. 実務で試して、改善を繰り返す
    失敗しても大丈夫。少しずつ完成度を上げていく

🌟 IT企業の総務だからこそ、できること

エンジニアが隣にいる環境。
技術ブログが社内に溢れている環境。
「やってみよう」を応援してくれる文化。

この恵まれた環境を活かさない手はありません。

私たちは「総務だから」を言い訳にしない。
「総務だからこそ」現場の課題を技術で解決する。

そんな挑戦を、これからも続けていきます!


次回もお楽しみに!

この記事をシェアする

FacebookHatena blogX

関連記事