SaaS

GWS × GAS:共有ドライブファイル/フォルダ一覧と共有状態を出力

※2023/10/11 追記:タイムアウトによる再実行処理が正しく動作していなかったため、改修しています。すみません。

はじめに

GWS管理者が頭を悩ませることでおなじみ、GWSの共有ドライブの権限管理わからん問題。

権限状態をぱっと見るための一覧出力GASを作りました。

利用イメージ

Google 共有ドライブのフォルダとファイルの一覧を出力!

使い方

必要なもの

  • 対象共有ドライブの管理権限または編集権限
  • Googleスプレッドシート

1. Googleスプレッドシートの準備

  1. 新しいGoogleスプレッドシートを作成し、名前を設定する。
    • (任意)スクリプトの中のシート1部分を、スプレッドシートのシート名に変更する。

2. Google Apps Scriptのプロジェクト作成

  1. Google Apps Scriptにアクセスし、ログインする。
  2. 「+ New Project」をクリックして新しいプロジェクトを作成する。

3.スクリプトの追加

  1. 下記スクリプトをコピーする。
  2. Google Apps Scriptエディタにペーストする。
let allData = [];
const SPREADSHEET_ID = 'Sheet_ID';//Sheet_ID をスプレッドシートのIDに変更
const SHARED_DRIVE_ID = 'Drive_ID';//Drive_ID を共有ドライブのIDに変更
const SHEET_NAME = 'シート1';
let DATA_TYPE = 'BOTH';

let startTime = new Date().getTime();
const MAX_EXECUTION_TIME = 1740000;
const scriptProperties = PropertiesService.getScriptProperties();

function checkExecutionTime() {
    return (new Date().getTime() - startTime) >= MAX_EXECUTION_TIME;
}

function processFolder(folder, folderPath, parentEditors, sheet, sharedDriveMembers) {
    handleFolderData(folder, folderPath, parentEditors, sharedDriveMembers);
    handleFilesInFolder(folder, folderPath, sharedDriveMembers);
    processSubFolders(folder, folderPath, parentEditors, sheet, sharedDriveMembers);
}

function handleFolderData(folder, folderPath, parentEditors, sharedDriveMembers) {
    let folderEditors = folder.getEditors().map(user => user.getEmail());
    let combinedEditors = [...new Set([...folderEditors, ...sharedDriveMembers])].join(', ');
    let viewers = folder.getViewers().map(user => user.getEmail()).join(', ');
    viewers = viewers ? viewers : "-";
    
    if (DATA_TYPE === 'FOLDERS' || DATA_TYPE === 'BOTH') {
        allData.push([folderPath, folder.getName(), 'フォルダ', folder.getUrl(), combinedEditors, viewers, folder.getAccess(DriveApp.Access.ANYONE) !== DriveApp.Permission.NONE ? '公開中' : '非公開']);
    }
}


function handleFilesInFolder(folder, folderPath, sharedDriveMembers) {
    var files = folder.getFiles();
    const lastProcessedFileName = scriptProperties.getProperty('last_processed_file');
    let processFile = !lastProcessedFileName;  // If there's no last processed file, start processing immediately

    while (files.hasNext() && (DATA_TYPE === 'FILES' || DATA_TYPE === 'BOTH')) {
        var file = files.next();
        
        if (processFile) {
            let fileEditors = file.getEditors().map(user => user.getEmail());
            let combinedFileEditors = [...new Set([...fileEditors, ...sharedDriveMembers])].join(', ');
            let fileViewers = file.getViewers().map(user => user.getEmail()).join(', ');
            fileViewers = fileViewers ? fileViewers : "-";
            allData.push([folderPath, file.getName(), 'ファイル', file.getUrl(), combinedFileEditors, fileViewers, file.getAccess(DriveApp.Access.ANYONE) !== DriveApp.Permission.NONE ? '公開中' : '非公開']);
        }
        
        // Update the processFile flag when the last processed file is encountered
        if (file.getName() === lastProcessedFileName) {
            processFile = true;
        }
        
        // Check execution time to ensure the script doesn't time out
        if (checkExecutionTime()) {
            scriptProperties.setProperty('last_processed_folder', folder.getId());
            scriptProperties.setProperty('last_processed_file', file.getName());
            return;
        }
    }
    scriptProperties.deleteProperty('last_processed_file');  // Reset for the next folder
}


function processSubFolders(folder, folderPath, parentEditors, sheet, sharedDriveMembers) {
    var subFolders = folder.getFolders();
    while (subFolders.hasNext()) {
        if (checkExecutionTime()) {
        scriptProperties.setProperty('last_processed_folder', folder.getId());
        return;
    } else {
        scriptProperties.deleteProperty('last_processed_file');
    }
        var subFolder = subFolders.next();
        var subFolderPath = folderPath + (folderPath === '/' ? '' : '/') + subFolder.getName();
        processFolder(subFolder, subFolderPath, parentEditors, sheet, sharedDriveMembers);
    }
}

function listFilesInSharedDrive() {
    const sheet = getOrCreateSheet();
    const sharedDriveMembers = getSharedDriveMembers(SHARED_DRIVE_ID).members;
    const rootFolder = getRootFolder();

    processFolder(rootFolder, '/', rootFolder.getEditors().map(user => user.getEmail()), sheet, sharedDriveMembers);
    writeDataToSheet(sheet);
}

function getOrCreateSheet() {
    const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
    if (!scriptProperties.getProperty('last_processed_folder')) {
        initializeSheet(sheet);
    }
    return sheet;
}

function initializeSheet(sheet) {
    sheet.clear();
    sheet.appendRow(['共有ドライブ名', getSharedDriveMembers(SHARED_DRIVE_ID).name]);
    sheet.appendRow(['メンバー', getSharedDriveMembers(SHARED_DRIVE_ID).members.join(', ')]);
    sheet.appendRow(['', '']);
    sheet.appendRow(['パス', 'ファイル/フォルダ名', 'タイプ', 'URL', '編集権限ユーザー', '閲覧権限ユーザー', '公開状態']);
}

function getRootFolder() {
    const lastProcessedFolderId = scriptProperties.getProperty('last_processed_folder');
    return lastProcessedFolderId ? DriveApp.getFolderById(lastProcessedFolderId) : DriveApp.getFolderById(SHARED_DRIVE_ID);
}

function writeDataToSheet(sheet) {
    if (!checkExecutionTime()) {
        const lastRow = sheet.getLastRow();
        const range = sheet.getRange(lastRow + 1, 1, allData.length, allData[0].length);
        range.setValues(allData);
        sheet.appendRow(["", "処理が完了しました", "", "", "", "", ""]);
        scriptProperties.deleteProperty('last_processed_folder');
    } else {
        const lastRow = sheet.getLastRow();
        const range = sheet.getRange(lastRow + 1, 1, allData.length, allData[0].length);
        range.setValues(allData);
        scriptProperties.setProperty('last_processed_folder', getRootFolder().getId());
    }
}

function getSharedDriveMembers(driveId) {
    try {
        const drive = Drive.Drives.get(driveId);
        const permissions = Drive.Permissions.list(driveId, { supportsAllDrives: true });
        const members = permissions.items.map(permission => permission.emailAddress);
        return {
            name: drive.name,
            members: members
        };
    } catch (error) {
        console.error(error);
        // Handle error accordingly
    }
}

function resetProcessProperties() {
    scriptProperties.deleteProperty('last_processed_folder');
    scriptProperties.deleteProperty('last_processed_file');
}

4.スクリプトの設定

変数の値を変更する

1.const SPREADSHEET_ID = 'Sheet_ID'; //Sheet_ID をスプレッドシートのIDに変更

  • ID取得方法
    • スプレッドシートのURLから、d/[id]/edit のid部分を取得

2.const SHARED_DRIVE_ID = 'Drive_ID'; //Drive_ID を共有ドライブのIDに変更

  • ID取得方法
    • 一覧を取得したい共有ドライブのページから、URLの末尾文字列を取得

3.(任意)var SHEET_NAME = 'シート1';

  • シート1 を任意のシート名に変更

4.任意)var DATA_TYPE = 'FOLDERS';

  • FILES:ファイル一覧を取得
  • FOLDERS:フォルダ一覧を取得
  • BOTH:ファイル、フォルダの一覧を取得

DriveAPIの有効化

  1. Google Apps Scriptエディタで、左部のサービス[+] をクリックする。
  2. Drive API を選択して[追加] をクリックする。

5. スクリプトの実行

  1. Google Apps Scriptエディタで、上部のプルダウンメニューから関数listFilesInSharedDriveを選択する。
  2. 左側の再生ボタン(▶️)をクリックして実行する。
  3. 必要に応じて、Google Apps Scriptに必要な権限を付与する。

※ファイル、フォルダ件数が大きい場合は時間がかかります。
参考として、ファイル+フォルダ合算件数:12000件で5~10分ほど。

6. 結果の確認

  1. Googleスプレッドシートを開いて、フォルダ/ファイル一覧が正しく書き込まれていることを確認する。
    • 最終行に処理が完了しましたと記載されていない場合、再度関数listFilesInSharedDriveを実行する。

GASの実行時間上限(6分 or 30分)を超える場合、処理途中の変数等を保存する仕組みにしています。
処理が中断されたら、再度listFilesInSharedDrive を実行してください。

おわりに

車輪の再発明、n番煎じっぽい記事ですが、特定共有ドライブの配下フォルダ/ファイルの全件取得してるGASを書いてる記事はググってもあまり見当たらなかったので書いてみました。

トップフォルダのみまたは複数の共有ドライブ一覧取得などについては、色々な方が記事を書いてくださっていますので以下の記事を参考にしてください。

GASでGoogle Driveの共有ドライブと所属ユーザ一の一覧を出力する – Qiita

Google共有ドライブの運用に便利なGAS|吉田航 (note.com)

【GAS】Googleドライブ:共有ドライブ(メンバー・権限)一覧出力 – Qiita

ではでは。

ばるす

パチンコ屋→焼き肉屋→情シスを経てクラウドネイティブへ入社。
趣味はギター,キーボード,アウトプット,散歩,読書など。
苦手なものは朝と事務作業。得意分野は眠ること。