Google Apps Scriptで共有ドライブをコントロールする【GAS】

Google Driveをファイルサーバのように扱える「Team Drive」こと「共有ドライブ」という機能があります。チームやプロジェクト単位で共有するドライブで、誰が見ても同じフォルダ・ファイル構成で、オーナーは特定個人ではなく共有ドライブ自身がオーナーになるため、ファイルサーバから移行がしやすい機能ですね。

さて、この共有ドライブですが、Google Apps Scriptから「作成」やファイルの移動が出来るのですが、これまでのようなDriveAppで単純に操作ができません。使えないわけではないのですが、以上のような共有ドライブの特性上そのままだと一部、操作ができないようになっている為です。

今回のまとめは、Google Apps Scriptから共有ドライブを操作する一連の作業となります。

今回使用するスプレッドシート

2023年にGoogle Apps Scriptにて、Drive API v3が利用可能になりました。大規模な変更はないのですが、細かいところで操作方法に差が出ています。例えばドライブの情報の取得などはデフォルトでv3の場合、最低限しか返ってきません。v3を使う場合の手法については以下を参考に装備が必要です。

Google Apps ScriptでDrive API v3にてファイル情報を取得する

事前準備

Drive APIを有効にする

今回のスクリプトはDrive API v2を利用します。よって、以下の手順に従って、Drive APIをONにする必要性があります。

  1. スクリプトエディター画面に於いて、サービス欄の+記号をクリック
  2. Drive APIを探しだして、選択する
  3. 追加ボタンをクリックする

これでDrive APIがGoogle Apps ScriptでDrive APIを使えるようになります。場合によっては、Cloud Console側でもONにし、プロジェクト番号を紐つける必要性もあります。紐付け作業はこちらのページを参考にしてみてください。

図:Drive APIをオンにした

権限一覧を整備する

共有ドライブにメンバーを追加や権限付与する場合には、スプレッドシートの権限一覧シートに於いてメンバーのメアド、権限を選択して整備する必要があります。この時、権限ですが、以下の通りとなります。

  • organizer : 管理者
  • fileOrganizer : コンテンツ管理者
  • writer : 投稿者
  • commenter : 閲覧者(コメント可)
  • reader : 閲覧者

管理者は複数設定できます。但し、1人に与えられる権限は1個です。

ソースコード

新規作成

//共有ドライブを作る
function createTeamDrive() {
  var ui = SpreadsheetApp.getUi();
  
  var ret = ui.prompt("作成する共有ドライブの名称を入力してください。", 
      ui.ButtonSet.OK_CANCEL);

  //押されたボタンによって処理を分岐
  switch(ret.getSelectedButton()){
    //OKボタンを押した時の処理
    case ui.Button.OK:
      var str = ret.getResponseText();
      var teamDrive = Drive.Drives.insert({name:str}, Utilities.getUuid())
      Logger.log(teamDrive);
      ui.alert("作成完了しました。")
      break;
    //キャンセルを押した時の処理
    case ui.Button.CANCEL:
      ui.alert("何もせずに閉じました。");
      break;
    case ui.Button.CLOSE:
      break;
  }
}
  • 共有ドライブを直接作成するコードです。
  • 変数teamDriveには、ドライブのIDなどの情報が返ってきます。
  • ドライブのIDといっても、通常のフォルダと同じで、ドライブ直下のディレクトリのIDと同じです。
  • 名前を指定して作成を実行するだけです。

共有権限一覧取得

//共有ドライブの権限一覧を取得する
function getTeamDrivePermissions(){
  var ui = SpreadsheetApp.getUi();
  
  var ret = ui.prompt("調べる共有ドライブのIDを入力", 
      ui.ButtonSet.OK_CANCEL);

  //押されたボタンによって処理を分岐
  switch(ret.getSelectedButton()){
    //OKボタンを押した時の処理
    case ui.Button.OK:
      var str = ret.getResponseText();
      var id = str;
      break;
    //キャンセルを押した時の処理
    case ui.Button.CANCEL:
      ui.alert("何もせずに閉じました。");
      break;
    case ui.Button.CLOSE:
      break;
  }

  //変数の宣言
  var permissions ;
  var pageToken;
  
  //メンバーと権限の一覧を取得する
  var roles = "";
  do{
    //supportsAllDrives:trueを入れないと動かない。けれどこのオプションはDeprecatedに指定されているという(なのでTrueにしておく)
    permissions = Drive.Permissions.list(id, {maxResults:20,pageToken:pageToken,supportsAllDrives:true}) ;
    if(permissions.items && permissions.items.length > 0){
      for (var i = 0; i < permissions.items.length; i++) {
        //権限情報を取得して変数に格納
        var permission = permissions.items[i];
        roles += permission.emailAddress + " : " + permission.role + "\n";
      }
    }else{
      Logger.log("権限は見つかりませんでした。");
    }
    
    //次のページのpageTokenを取得する
    pageToken = permissions.nextPageToken
  }while(pageToken)
  
  //権限一覧を表示
  ui.alert(roles);
  
}
  • 対象の共有ドライブ上に設定されている権限(メアドと権限名)を返します。

図:権限の一覧がダイアログで表示される

共有ドライブ一覧

//共有ドライブの一覧を取得する(ID含む)
function adminListAllTeamDrives(){
  //変数の宣言
  var pageToken;
  var teamDrives;
  
  //管理者リストを取得する
  var drives = "";
  do{
    teamDrives = Drive.Drives.list({pageToken:pageToken,maxResults:50,useDomainAdminAccess:true})
    if(teamDrives.items && teamDrives.items.length > 0){
      for (var i = 0; i < teamDrives.items.length; i++) {
        //ドライブ一覧情報を変数に格納する
        var teamDrive = teamDrives.items[i];
        drives += teamDrive.name + " : " + teamDrive.id + "\n";
      }
    }else{
      Logger.log("権限が見つかりませんでした。");
    }
    
    //次のページのpageTokenを取得する
    pageToken = teamDrives.nextPageToken
  }while(pageToken)
  
  //共有ドライブ一覧表示
  SpreadsheetApp.getUi().alert(drives);

}
  • 現在用意されている共有ドライブの一覧(名前とID)を返します。

共有ドライブ管理者一覧

//共有ドライブの管理者権限持ってる人リストを取得する
function adminGetTeamDrivePermissions(){
  var ui = SpreadsheetApp.getUi();
  
  var ret = ui.prompt("調査する共有ドライブのIDを入れてください。", 
      ui.ButtonSet.OK_CANCEL);

  //押されたボタンによって処理を分岐
  switch(ret.getSelectedButton()){
    //OKボタンを押した時の処理
    case ui.Button.OK:
      var id = ret.getResponseText();
      break;
    //キャンセルを押した時の処理
    case ui.Button.CANCEL:
      ui.alert("何もせずに閉じました。");
      break;
    case ui.Button.CLOSE:
      break;
  }
  
  //変数の宣言
  var permissions ;
  var pageToken;
  
  //管理者一覧を取得する
  var admins = "";
  do{
    permissions = Drive.Permissions.list(id, {maxResults:20,pageToken:pageToken,supportsAllDrives:true,useDomainAdminAccess:true}) ;
    if(permissions.items && permissions.items.length > 0){
      for (var i = 0; i < permissions.items.length; i++) {
        //管理者権限一覧を変数に格納する
        var permission = permissions.items[i];
        admins += permission.emailAddress + " : " + permission.role + "\n";
      }
    }else{
      Logger.log("権限が見つかりませんでした。");
    }
    
    //次のページのpageTokenを取得する
    pageToken = permissions.nextPageToken;
  }while(pageToken)
  
  //権限一覧を表示する
  ui.alert(admins);
  
}
  • 対象の共有ドライブに管理者権限を持っている人の一覧を返します。

共有ドライブの設定変更をする

共有ドライブの作成時やあとから設定を一括で変更したい場合に使うメソッドです。右クリック=>共有ドライブの設定で出てくる4つの項目を変更します。

//共有ドライブの設定変更を行う
function sharedDriveSettingChg(){
  let driveId = "ここに共有ドライブの直下のIDを入れる"

  var updateOptions = {
    "restrictions": {
      "domainUsersOnly": false,
      "driveMembersOnly": false,
      "copyRequiresWriterPermission" : false,
      "sharingFoldersRequiresOrganizerPermission":false
    }
  };

  var updateResources = {
    "supportsAllDrives":true,
    "useDomainAdminAccess": true
  }; 

  //設定変更実行
  let updateDrive = Drive.Drives.update(
    updateOptions,
    driveId,
    updateResources
  );
}
  • Drive.Drives.Updateにて設定を変更します。
  • domainUsersOnlyが「officeforest 外のユーザーにファイルへのアクセスを許可する」の設定になります。
  • driveMembersOnlyが「共有ドライブのメンバー以外のユーザーにファイルへのアクセスを許可する」の設定になります。
  • copyRequiresWriterPermissionが「閲覧者およびコメント可の閲覧者にファイルのダウンロード、印刷、コピーを許可する」の設定になります。
  • sharingFoldersRequiresOrganizerPermissionが「コンテンツ管理者にフォルダの共有を許可する」の設定になります。
  • ただしややこしいのですが、許可するという項目なのにoptionの指定が例えばドメインのメンバーのみという形で指定方法が逆になってるので、true/falseの指定に関しては要注意です。

図:共有ドライブの設定が変更された

共有ドライブに権限付与

共有ドライブへのアクセス権限の新規追加と更新の構文です。削除は後述の「権限の削除」と全く同じ構文になるので、そちらを参照する。

アクセス権限を新規追加

//共有ドライブにメンバーと権限を追加する
function setPermissions(){
  var ui = SpreadsheetApp.getUi();
  
  var ret = ui.prompt("権限追加する共有ドライブのIDを入力してください。", 
      ui.ButtonSet.OK_CANCEL);

  //押されたボタンによって処理を分岐
  switch(ret.getSelectedButton()){
    //OKボタンを押した時の処理
    case ui.Button.OK:
      var id = ret.getResponseText();
      break;
    //キャンセルを押した時の処理
    case ui.Button.CANCEL:
      ui.alert("何もせずに閉じました。");
      break;
    case ui.Button.CLOSE:
      break;
  }

  //シートの権限一覧からrolesを生成
  var roles = {};
  var temparr = [];
  var list = ["organizer","fileOrganizer","writer","commenter","reader"];
  
  //スプレッドシートのデータを取得
  var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("権限一覧").getRange("A2:B").getValues();
  var length = ss.length;
    
  for(var i = 0;i<list.length;i++){
    //一時配列初期化
    temparr = [];
  
    //キー名を取得
    var keyman = list[i];
    
    //スプレッドシートデータを探索
    for(var j = 0;j<length;j++){
      if(ss[j][1] == ""){
        //権限が付与されていないレコードをスルー
        continue;
      }
      
      //対象のレコードが対象の権限名ならばpushする
      if(ss[j][1] == keyman){
        //配列にpushする
        temparr.push(ss[j][0]);
      }
    }
    
    //一時配列のlengthが0だったら処理をスルー
    if(temparr.length > 0){
      //rolesにkeyと配列を追加
      roles[keyman] = temparr;
    }else{
      //処理をスルーする
      continue;
    }
  }

  //ロールの分だけループを回してキーを取得
  for(var key in roles){
    var role = roles[key];
    
    //キーに指定したメンバー分設定を付ける
    role.forEach(function (email){
      var resource = {
        role: key,
        type:"user",
        value:email
      }
      
      //commenterというキーの時は、reader権限も付与する(でないとコメントできないからね)
      if(key == "commenter"){
        resource.role = "reader";
        resource.additionalRoles = ["commenter"]
      }
      
      //本人に通知なしで共有設定を追加する
      Drive.Permissions.insert(resource, id, {sendNotificationEmails:false,supportsAllDrives:true,useDomainAdminAccess:false});
    });
  }
  
  //メッセージを表示
  ui.alert("権限付与が完了しました。")
  
}
  • 対象の共有ドライブに権限付与・新規メンバーの追加を行います。
  • スプレッドシートの権限一覧シートから設定を行います。

アクセス権限を更新する

function updatepermission(){
    //設定する権限を指定
    let role = "reader"

    //設定変更する対象メアド
    let mail = "hoge@hogehoge.com"

    //Drive IDを指定
    let driveid = "xxxxxx"

    //メールからパーミッションIDを取得
    var permissionId = Drive.Permissions.getIdForEmail(mail);
 
    //リクエストリソースを構築する
    var resource = {
        role: role
    }

    //権限変更を実行
    Drive.Permissions.update(
        resource,
        driveid,
        permissionId.id,
        {
            sendNotificationEmails:false,
            supportsAllDrives:true,
            useDomainAdminAccess:false
        }
    );
}
  • メールで指定するのではなく、メールからgetIdForEmailでPermissionIdを取得して、これをもって更新を指定する
  • roleに変更するロールを指定する
  • あとはPermission.insertと同じような構文で実行する

共有ドライブの特定フォルダに権限を付与

権限を追加する

前述までは、Drive APIを利用してチームドライブのメンバーとして追加を行っていました。しかし、一部のフォルダに対してだけ外部共有を許可したい場合には、事前に外部共有の設定は必要ですが、DriveAppで外部メンバーを追加する事が可能です。

アップロードを許可するならば投稿者権限が必要ですが、これは通常のマイドライブの「編集者」と同じであるため、以下の構文で追加が可能です。

function insertUser(user,folderid){
  //フォルダを取得する
  let folder = DriveApp.getFolderById(folderid);

  //投稿者権限を追加する
  folder.addEditor(user);

  //結果を返す
  return 0;
}

Google Workspaceで安全に外部とファイルを共有する方法

権限を削除する

ところが、ユーザの削除でremoveEditorをしてみたところ「そんなユーザーは居ない」と怒られたので、こちらはDrive APIを使って削除をしてみます。しかし、Drive APIでの共有権限の削除はユーザのメールアドレスではなく権限リストについてくるpermissionIdというものが必要であるため、少々手間が掛かります。

//共有ユーザを削除する
function deleteUser(user,folderid){
  try{
    var shareuser = Drive.Permissions.list(folderid, {maxResults:100,supportsAllDrives:true}) ;
    var userlist = shareuser.items;

    //permissionIdを取得する
    var permissionId = "";

    for(var i = 0;i<userlist.length;i++){
      //レコードを一個取り出す
      let rec = userlist[i];

      //メアドが一致するかどうか?
      if(user == rec.emailAddress){
        permissionId = rec.id;
        break;
      }
    }

    if(permissionId == ""){
      //ユーザが見つからなかった
      return "対象のユーザが見つかりませんでした。"
    }else{
      //deleteで削除
      Drive.Permissions.remove(folderid, permissionId,{supportsAllDrives:true});

      //結果を返す
      return 0;
    }
  }catch(e){
    return e.message;
  }
}
  • まずは、permission.listで対象のディレクトリの権限一覧を取得
  • ユーザのメアドと一致するデータを見つけたら、idを取得(これがpermissionIdとなる
  • Drive.Permissions.removeで対象の権限を削除する

共有ドライブにファイル作成

//新規にファイルを作ることは出来る。
function creatNewFile() {
  var ui = SpreadsheetApp.getUi();
  
  var ret = ui.prompt("作成するフォルダのIDを入力", 
      ui.ButtonSet.OK_CANCEL);

  //押されたボタンによって処理を分岐
  switch(ret.getSelectedButton()){
    //OKボタンを押した時の処理
    case ui.Button.OK:
      var folderid = ret.getResponseText();
      break;
    //キャンセルを押した時の処理
    case ui.Button.CANCEL:
      ui.alert("何もせずに閉じました。");
      break;
    case ui.Button.CLOSE:
      break;
  }

  //新規にからのスプレッドシートを作成
  var ss = SpreadsheetApp.create("すぷれっどしーと新規SS").getId();
  
  //ファイルのIDとフォルダを取得する
  var file = DriveApp.getFileById(ss);
  var targetFolder = DriveApp.getFolderById(folderid);
  
  //共有ドライブに移動する
  targetFolder.addFile(file);
  
  //終了メッセージ
  ui.alert("ファイルが作成されました。");
}
  • 通常のドライブと同じく、DriveAppにて新規ファイル作成が可能です。
  • ただし、ファイルのremoveaddは使えません。あくまでも新規作成だけです。

共有ドライブにフォルダコピー

//Google Driveから共有ドライブへコピーする
function moveTeamDrive() { 
  var ui = SpreadsheetApp.getUi();
  
  var ret = ui.prompt("移動元のGoogle DriveのフォルダのIDを入れてください", 
      ui.ButtonSet.OK_CANCEL);

  //押されたボタンによって処理を分岐
  switch(ret.getSelectedButton()){
    //OKボタンを押した時の処理
    case ui.Button.OK:
      var sourceFolder = ret.getResponseText();
      break;
    //キャンセルを押した時の処理
    case ui.Button.CANCEL:
      ui.alert("何もせずに閉じました。");
      break;
    case ui.Button.CLOSE:
      break;
  }

  var ret = ui.prompt("移動先の共有ドライブのフォルダのIDを入れてください。", 
      ui.ButtonSet.OK_CANCEL);

  //押されたボタンによって処理を分岐
  switch(ret.getSelectedButton()){
    //OKボタンを押した時の処理
    case ui.Button.OK:
      var targetFolder = ret.getResponseText();
      break;
    //キャンセルを押した時の処理
    case ui.Button.CANCEL:
      ui.alert("何もせずに閉じました。");
      break;
    case ui.Button.CLOSE:
      break;
  }
  
  //フォルダを取得
  var source = DriveApp.getFolderById(sourceFolder);
  var target = DriveApp.getFolderById(targetFolder);

  //コピー開始
  copyFolder(source, target);
  
  //終了メッセージ
  ui.alert("コピー完了しました");
  
}

//コピーを作るメインルーチン(サブフォルダまで対応)
function copyFolder(source, target) {
  var folders = source.getFolders();
  var files   = source.getFiles();

  while(files.hasNext()) {
    var file = files.next();
    file.makeCopy(file.getName(), target);
  }

  while(folders.hasNext()) {
    var subFolder = folders.next();
    var folderName = subFolder.getName();
    var targetFolder = target.createFolder(folderName);
    copyFolder(subFolder, targetFolder);
  }  
}
  • これがちょっとややこしく、素直にDriveAppで.addメソッドで行おうとすると失敗します。
  • makeCopyメソッドで複製させます。
  • 移動の場合は、複製後に移動元のファイルをremoveすればOK
  • 今回のメソッドはフォルダ構造維持してまるごとコピーします。

共有ドライブにファイル移動や削除

GASだけで共有ドライブの間でファイルの移動ができるか?といったら答えはNoです。新規に作る分にはDriveAppが使えますが。共有ドライブのファイルの編集自体はDrive APIを介して行う事ができます。

//共有ドライブ内のファイルの削除
Drive.Files.remove(fileid, {supportsTeamDrives:true});

//共有ドライブへファイルを移動
function moveFileToFolder(fileid, targetfolderid) {  
  var file = Drive.Files.get(fileid, {supportsTeamDrives: true});

  Drive.Files.patch(file, fileid, {
    removeParents: file.parents.map(function(f) { return f.id; }),
    addParents: [targetfolderid],
    supportsTeamDrives: true
  });
}

//ゴミ箱に直行する
Drive.Files.trash(folderid,{
  'supportsAllDrives':true
})
  • この方法では、コピーではなく移動ができています。なので、ファイルのIDが変更されることがありません
  • ファイルの削除も編集作業なので、Drive APIを用いて行います。
  • 共有ドライブ間のファイルの移動も可能です。
  • targetfolderidが移動先のフォルダのID、fileidが移動させるファイルのIDになります。

共有ドライブからフォルダを移動

2022年4月に共有ドライブに対してのフォルダの移動に関して、GUIでは簡単に移動させることができるようになりました。またこれまでDriveAppで使っていたようなフォルダ内部のファイルを移動先に登録し、移動元から削除するといったような冗長的なコードは不要になります。

しかし、2022年当時では共有ドライブのフォルダに対してmoveToで移動を指定すると、エラーが生じるという報告がなされていました。今回コメント欄に同様の話題を頂いたため、改めて以下の設定の通りにし、コードを実行したところ、共有ドライブA⇒共有ドライブBに移動ができました。再帰処理など使用していないので、スクリプトがタイムアウトすることなく処理が可能です。

設定は以下の通りです。

  • 共有ドライブの設定は特にチェックなどをしない
  • 移動を実行するユーザの権限は「管理者」としてる。コンテンツ管理者にも許可する場合は前術の設定で各ロールの権限の「コンテンツ管理者にフォルダの共有を許可する」のチェックが必要
  • 移動元、移動先それぞれは共有ドライブの特定のフォルダのIDを指定する

実行したコードは以下のとおりです。

function myFunction() {
  var dest = "移動先のフォルダのID"
  var source = "移動元のフォルダのID"

  //移動元フォルダを取得
  var folder = DriveApp.getFolderById(source);

  //移動先にフォルダを取得
  var destfolder = DriveApp.getFolderById(dest);

  //フォルダを移動先に移動
  folder.moveTo(destfolder)
}

実際にコードを実行してみましたが、エラーが発生することなく、共有ドライブ間でフォルダを移動することができました。もちろん、フォルダの中身もそのまま移動してるので、タイムアウトなども発生せずに一括で処理が可能です。

ファイルの他のユーザとの共有設定をオンオフ

ファイル共有には「他のユーザとの共有設定」というものがあり、ここのチェックはチェックすると閲覧権限者に対してコピー、ダウンロード、印刷を許可するかどうかという特殊なオプションが働きます。これが有効な場合は閲覧者はコピーやダウンロードが出来るわけです。

このチェックのオンオフを共有ドライブ内のファイルに対して行おうというコードになります。現状の設定を取得して逆のフラグを割り当てるようにしているので、クリックひとつでオンオフ切り替えになります。Drive APIを介して行う事ができます。

var fileId = "共有ドライブ内の対象のファイルのIDをここに入れる"

//ファイルに対して閲覧者向けファイルダウンロード等オプション表示をオンオフ
function copywritechange(){
  var ui = SpreadsheetApp.getUi();

  //現状のパーミッション状態を取得する
  var test = Drive.Files.get(
    fileId,
    {
      supportsAllDrives : true
    }
  ).copyRequiresWriterPermission

  //flg判定を逆転させる
  let flg = "";
  if(test == true){
    flg = false;
  }else{
    flg = true;
  }
  
  //パーミッション設定変更
  Drive.Files.update(
    {copyRequiresWriterPermission:flg},
    fileId,
    null,
    {supportsAllDrives:true}
  );

  //終了メッセージ
  ui.alert("ファイルの権限設定を" + flg + "にしたよ")
}
  • Drive.Files.Updateの第一引数に変更オプションであるcopyRequiresWriterPermission:trueなどの項目を入れます
  • こちらの動作のチェックは、公式ドキュメント上でも確認が可能です。
  • testという変数にまず、現在のチェックの状態を取得し、それを反転、パーミッション設定変更で割り当てています。
  • 当然、supportsAllDrivesのオプション設定は必要です。

図:この設定を変更したい

共有ドライブのゴミ箱を空にする

通常のマイドライブのゴミ箱は自分自身でゴミ箱を空にすることが可能です。しかし、共有ドライブは自分自身の所有ではなく、このゴミ箱を空にできる人は権限的には「管理者権限」を持つ人だけです。また、共有ドライブは最大50万個という制限があるため、ファイルを捨てて減らそうとしても、ゴミ箱を空に出来ないと、カウントは減ることがありません。

とはいえ、全員に管理者権限を与えてしまうと、あっさり削除されてしまい事故が起きかねません。故にこういった仕様になっています。そこでウェブアプリを作って管理者権限で動かし、管理者権限を持たない一部の人にだけ実行を許可する仕組みを作って自分は対処しています。そのゴミ箱を空にする手法が以下のとおりです。

function drivecleaner() { 
  var optionalArgs={'driveId':'ここに共有ドライブのID', 'includeItemsFromAllDrives':true, 'corpora': 'drive', 'supportsAllDrives': true, 'q':'trashed = true' }  
  var trashed=Drive.Files.list(optionalArgs).items; 
  for(var i=0;i<trashed.length;i++){
    Drive.Files.remove(trashed[i].id, {'supportsAllDrives':true}) 
  }
}
  • DriveAPIでゴミ箱フラグのついてるものだけをリストアップ
  • driveIdは、共有ドライブを開いた直下のフォルダIDを指します。これを指定する
  • 共有ドライブのゴミ箱はそれぞれの共有ドライブに入ってゴミ箱を開かないと中身が出てこない仕様です。
  • ピックアップして、Drive.Files.removeでゴミ箱を空にしてしまいます。
  • これをウェブアプリで作ったUIから管理者権限で実行し、操作してる人のメアドがスプレッドシートに登録されてる人だったら、関数を実行するように組めば、特定の人だけに使わせる事が可能です。
  • いちいちゴミ箱を空にするために現場の人に管理者権限を与えずに特定の作業だけをこれで許可する事が可能です。

図:これが管理者じゃないと出てこない

Google Apps Scriptでアクセスしてるユーザを元に処理をする方法【GAS】

ファイルの個数を調べたい

Google Apps Scriptで調べる方法

Drive API v2

共有ドライブの制限の1つとして、「1共有ドライブ内のファイルは50万個までのファイルしか格納出来ない」というものがあります。そのため、長い間利用していて制限に達してしまうとファイルを作ったり追加する事ができなくなります。このドライブの使用割合はadminコンソールからは共有ドライブの管理からは残り何%か確認出来ます。

しかし、この割合や現在のドライブのファイル個数は現在スクリプト等からも取得出来ず、Admin SDKDrive API V3Reports APIにも該当のメソッドが存在しません。リクエストは出ているようですが・・・Google Cloud Console上で取得するPythonスクリプトはあるようですが、これでは誰でも確認出来ない。

ということで、力技ですが、以前作成したドライブの探索スクリプトをDrive API V2で実装し直したものを作成しました。

//一番根っこのルートフォルダを指定
var folderid = "ここに共有ドライブのルートのフォルダIDを入れる"

//folderのIDを格納する配列
var folarray = [];
var totalcount = 0;
var quota = 380000;  //共有ドライブのファイルの個数上限閾値(制限は500000個)

//ルートフォルダ以下のサブフォルダを列挙する
function chkFolderList(){
  let token = null;
  var folinfo = [];
  let ui = SpreadsheetApp.getUi();

  //フォルダだけを調査するクエリ(ゴミ箱内のファイルも含む)
  var query = 'mimeType = "application/vnd.google-apps.folder" and ' + "'" + folderid + "' in parents"

  //フラグ類
  var subfolderflag = 0;

  //ループでフォルダ数を調べる(まずはルートフォルダから)
  var tempfolcount = 0;
  do {
    var fileman = Drive.Files.list({
      includeTeamDriveItems: true,
      supportsTeamDrives: true,
      maxResults: 1000,
      q: query,
      pageToken: token,
    });

    //ページトークンを取得する
    token = fileman.nextPageToken

    //ファイルカウントを加算する
    tempfolcount = tempfolcount + fileman.items.length

    //folinfoにfolderidをpushする
    if(tempfolcount == 0){
      //ループを脱出
      break
    }else{
      for(var i = 0;i<tempfolcount; i++){
        //フォルダのIDを取得する
        let tempid = fileman.items[i].id;

        //folinfoに格納
        folinfo.push(tempid)

        //folarrayに格納
        folarray.push(tempid)
      }
    }
  } while (token != null)

  if(tempfolcount == 0){
    //ルートフォルダ内のファイルの数だけカウントして終了する
    let filecnt = countFilesInFolder(folderid)
    ui.alert("ファイルの数は" + filecnt + "でした。")
    return;
  }else{
    //ルートフォルダのIDをfolarrayに追加しておく
    folarray.push(folderid)

    //サブフォルダフラグを立てる
    subfolderflag = 1;

    //サブフォルダ以降のフォルダの探索ルーチン
    while (subfolderflag == 1) {
      var nowponyo = folinfo.length;
      for(var i = 0;i<nowponyo;i++){
        //folinfoが空ならば終了
        if(folinfo.length == 0){
          break;
        }

        //0番目の配列のkeyを取得する
        key = folinfo[0];

        //そのkeyのフォルダにぶら下がるフォルダをリストアップ
        query = 'mimeType = "application/vnd.google-apps.folder" and ' + "'" + key + "' in parents"

        //変数を初期化
        tempfolcount = 0;
        token = null;

        do {
          var fileman = Drive.Files.list({
            includeTeamDriveItems: true,
            supportsTeamDrives: true,
            maxResults: 1000,
            q: query,
            pageToken: token,
          });

          //ページトークンを取得する
          token = fileman.nextPageToken

          //ファイルカウントを加算する
          tempfolcount = tempfolcount + fileman.items.length

          //folinfoにfolderidをpushする
          for(var i = 0;i<tempfolcount; i++){
            //フォルダのIDを取得する
            let tempid = fileman.items[i].id;

            //folinfoに格納
            folinfo.push(tempid)

            //folarrayに格納
            folarray.push(tempid)
          }
        } while (token != null)

        //配列0番目を削除と詰める
        folinfo.splice(0,1);
      }

      //folderlistが空ならsubfolderflagを0にする
      if(folinfo.length == 0){
        subfolderflag = 0;
      }
    }

    //folarrayに基づいてファイルの数を一括でカウント
    for(var i = 0;i<folarray.length;i++){
      let fcnt = countFilesInFolder(folarray[i]);

      console.log(folarray[i] + " = " + fcnt)

      totalcount = totalcount + fcnt;
    }

    //閾値を超えてるかどうか?
    if(totalcount <= quota){
      ui.alert("ファイルの数は" + totalcount + "でした。")
    }else{
      //現在のファイル個数の上限値に対する割合を計算
      var limitman = totalcount / 400000 * 100

      ui.alert("ファイルの数は" + totalcount + "でしたが、共有ドライブのファイル個数制限に近くなっています。現在" + limitman + "%です")
    }
  }
}

//指定フォルダ内のファイルの数だけをカウントする関数
function countFilesInFolder(folid) {
  let token = '';
  let filecnt = 0;

  //フォルダを除外したファイルだけの数をカウントする
  var query = 'not mimeType = "application/vnd.google-apps.folder" and ' + "'" + folid + "' in parents"
  
  //ループでファイルをカウントする
  do {
    var fileman = Drive.Files.list({
      includeTeamDriveItems: true,
      supportsTeamDrives: true,
      maxResults: 1000,
      q: query,
      pageToken: token
    });

    //ページトークンを取得する
    token = fileman.nextPageToken

    //ファイルカウントを加算する
    filecnt = filecnt + fileman.items.length
  } while (token != null)

  //フォルダ内ファイルの数を返却
  return filecnt
};

このスクリプトを使うに当たってはいくつかの注意点があります。

  • Drive APIのdrive.files.listは1度に取得できるファイル・フォルダのリストは1000個までなので、pageTokenを使ってループで何度も回す必要があります。
  • quotaに380000を指定してるのは95%に到達でメッセージを出すようにしています。
  • フォルダのみを取り出すqueryとファイルのみを取り出すqueryの2つを使います。サブフォルダまで含めたフォルダのIDの配列を作成するchkFolderList関数を回し、その配列を用いて、ファイルのみを取り出すcountFilesInFolder関数で個数を調べています。
  • 但し、50万個近くあるファイルの場合、ひょっとしたらGASの実行時間制限の6分を超える可能性がある・・・

スマートな手法ではない&時間が掛かる為、複雑なフォルダ構成になってる場合には、フォルダのリストは1度スプレッドシートに出力し、単純に各フォルダIDのファイル個数をカウントする関数に処理を分離して、それぞれを別個に実行するようにするなど、工夫が必要と思います。

Google Apps Scriptで6分の壁(タイムアウト)を突破する - 番外編【GAS】

Google Apps Scriptでファイルフォルダの探索【GAS】

図:ここの値が簡単に取れないという・・・

Drive API v3

Drive API v3がGASで利用できるようになったのでコードを改変してみましたが、基本的な条件は殆ど変わっていないので、6分を超えるテクニック等を使う必要性はあります。10000ファイル調べるのに40秒ほど掛かっているので、10万ファイルで6分を超えてしまいます。

//共有ドライブ内のファイルをカウントする
function listcountShareddrive(){
  //共有ドライブのID
  let driveid = "共有ドライブのIDを入力";

  //pageToken用変数
  let pageToken;

  //ファイルカウンタ
  let filecnt = 0;

  //フォルダを除外してファイルのリストを取得する
  do{
    let result = Drive.Files.list({
      corpora:"drive",
      driveId:driveid,
      includeItemsFromAllDrives:true,
      q:"mimeType != 'application/vnd.google-apps.folder'",
      supportsAllDrives:true,
      pageSize:1000,
      pageToken:pageToken,
    });

    //ファイルをカウント
    let tempcnt = result.files.length;
    filecnt = filecnt + Number(tempcnt);

    //pageTokenを取得する
    pageToken = result.nextPageToken;

  }while(pageToken)

  console.log(filecnt)

}

VBAとパソコン版Google Driveを利用する方法

Windowsのパソコン限定ですが、パソコン版Google Driveをインストールし、認証してドライブが見える状態になっている場合には、VBAを利用して対象の共有ドライブ以下のファイルの総数をサブフォルダまで含めて調査することが結構簡単に短時間で行う事が可能です。VBSでも行けるのではないかと。

FileSystemObjectを利用して、サブフォルダまで再帰的にファイル数を取得して最後に数を返すコードになります。こちらのほうが時間の制限も無く、GASで行うよりも短時間で調査が可能で、Excelのシートに共有フォルダのパスをすべて記述しておいて、順番に読み出して調査するといった事ができるため、オススメです。

'調査する親のフォルダ
Public folderpath As String
Public filecnt As Long

'調査する関数
Public Function startcheck()
    '初期のスタートフォルダを指定
    folderpath = "G:\共有ドライブ\共有ドライブの名称"

    'スタートする
    Call checkfilecount
    
    '最後のファイル数
    MsgBox filecnt & "のファイルが検出されました。"
End Function

'フォルダ内のファイルの総数をカウントする
Public Function checkfilecount() As Integer
    'FileSystemObjectを利用する
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")

    'fso用の変数
    Dim objFiles As Object
    Dim objSubFols As Object
    Dim objSubFol As Object
        
    '対象フォルダのオブジェクトをまず取得する
    Set objFiles = fso.GetFolder(folderpath).Files
    
    'ファイルのカウントを行う
    filecnt = filecnt + objFiles.Count
    
    'サブフォルダをセットする
    Set objSubFols = fso.GetFolder(folderpath).SubFolders
    
    'サブフォルダ以下を再帰処理
    For Each objSubFol In objSubFols
        
        'サブフォルダがなければスルーする
        If objSubFol.Path <> "" Then
            'folderpathを書き換える
            folderpath = objSubFol.Path
            
            '再帰的にファイルチェックを行わせる
            Call checkfilecount
        End If
         
    Next objSubFol
    
    checkfilecount = filecnt
End Function

PC版Google Driveを使う上での注意点

公式に容量リミットのバナーが装備

ようやくというか、2024年3月15日に対象の共有ドライブに対して編集権限を持つ人に対して、ドライブの個数制限400,000個に対して、残が20%以下になった場合に、容量のリミットが来ていることを知らせるバナーが装備されました。

実際どういう表示になるのかはわからないのですが、Business Standard以上に於いて対応しており、これによりリミットが迫ってることをユーザが認識することが可能になります。これまではリミットに達してるのかどうかは管理者画面からしかわからず、達してる場合にファイルを追加アップしてもエラーとなるだけで、リミットに達してるかどうかはわからないという状態でしたので、情シスへの問い合わせもこれで減るのではないかと思います。

共有ドライブを削除する

共有ドライブはもちろんフォルダIDを与えられてはいるものの、特殊なフォルダ扱いであるため簡単には削除が出来ません。例えば手動で削除するにしても

  • 共有ドライブ内のファイルは全て空にしておく必要性がある
  • ごみ箱に移動したファイルについても同様に空にしておく必要がある

といったように、ファイルを一切残していない状態でなければ削除が出来ません。故にDriveAppなどでフォルダとして削除が出来ないのですが、Drive APIを用いた場合は、ファイルがある状態であっても一発でドライブ毎削除が可能です(それゆえに運用は慎重に行わないと本当に消えます)

//共有ドライブを削除する
var ret = Drive.Drives.remove(
  driveid,
  {
    useDomainAdminAccess:true,
    allowItemDeletion:true
  }
)
  • allowItemDeletionをTrueにすることで、ファイルがある状態でもドライブを削除が可能
  • 但し、このオプションを有効にするにはuseDomainAdminAccessもTrueにする必要性があるので管理者権限が必要です。
  • ドライブのIDを指定して実行すれば即時に削除されますので、別途ファイルやフォルダの削除、ゴミ箱を空にするコードは不要です。

尚、以前使っていたファイルやフォルダを全部きれいにゴミ箱行きにするコードは以下のものです。参考までに。

//指定共有ドライブ内のファイルを全て削除
const getAllFiles = folder => {
  const files = folder.getFiles();
  while (files.hasNext()) {
    const file = files.next();
    console.log(file.getName());
    file.setTrashed(true);
  }
  const folders = folder.getFolders();
  while (folders.hasNext()) getAllFiles(folders.next());
}

var folder = DriveApp.getFolderById(driveid);
getAllFiles(folder);

//指定共有ドライブ内のフォルダを全て削除
const getAllFolders = folder => {
  const folders = folder.getFolders();
  while (folders.hasNext()) {
    const folder = folders.next();
    console.log(folder.getName())
    folder.setTrashed(true);
  }
  const folders2 = folder.getFolders();
  while (folders2.hasNext()) getAllFolders(folders2.next());
}

var folder = DriveApp.getFolderById(driveid);
getAllFolders(folder);

//ウェイトを入れる
Utilities.sleep(2000)

//指定共有ドライブのごみ箱を空にする
var optionalArgs={'driveId':driveid, 'includeItemsFromAllDrives':true, 'corpora': 'drive', 'supportsAllDrives': true, 'q':'trashed = true' }  
var trashed=Drive.Files.list(optionalArgs).items; 
console.log(trashed.length)

for(var i=0;i<trashed.length;i++){
  console.log(trashed[i].id)
  Drive.Files.remove(trashed[i].id, {'supportsAllDrives':true}) 
}

関連リンク

Google Apps Scriptで共有ドライブをコントロールする【GAS】” に対して8件のコメントがあります。

  1. hajimechan より:

    チームドライブ内の特定のフォルダから、チームドライブ内の別のフォルダに、
    特定のスプレッドシートを移動させることは可能でしょうか。

    1. akanemaru2017 より:

      記事に、チームドライブ内のファイルの削除およびチームドライブ間ファイルの移動のコードを追加しておきました。
      こちらもDriveAppでは操作ができません。Drive APIを利用する事になります。

      1. hajimechan より:

        ありがとうございます!試してみます。

      2. hajimechan より:

        試したところ、成功しました。ありがとうございます。

        ただ、Drive APIを有効にする部分もGAS内で済ませたく「UrlFetchApp」を使って認証させようとしたのですが、うまくいかず困ってます。

        GASのスクリプトからDrive APIを有効化させることは可能でしょうか?

        1. akanemaru2017 より:

          Cloud Console側は、コードを記述するだけでDrive APIは有効になるようです。ですが、GAS側は手動で行わないと出来ないようです。
          https://qiita.com/tanaike/items/1d49f73ab05403d0fe4c

          ですが、Drive APIは様式さえ守れば、色々なアプリからHTTP通信で叩けるので、以下のサイトのようにUrlFetchAppにて直接APIを叩くことで同様のことは実現できます。
          ただし、コードがかなり冗長にはなるかと思います。
          https://stackoverflow.com/questions/48310761/how-to-make-a-drive-api-batch-request-with-urlfetchapp-in-google-apps-script

  2. tichikawa より:

    参考にさせてもらって助かりました。
    ところで自分の環境では権限の中に permission.deleted が trueなものがあって、
    それを除いて取得する必要がありました。

    あと、共有ドライブの『設定』は
    teamDrive.restrictions.xxx で見えるものっぽいですが、
    Drive.Drives.list({~, fields:’*’ }) とかも付けておかないと取れないもののようですね。

  3. 片山朋子 より:

    大変参考にさせていただいています。

    共有ドライブのファイルを特定の共有ファイルに異動させることは、記載いただいているソースで可能なようですが、
    共有ドライブの特定フォルダ(下層フォルダまで)を別の共有ドライブの特定のフォルダに移動することは、まだできないのでしょうか?
    下記ソース(1部です、フォルダ移動の部分のみ抜粋)で、私のソースに間違いがある可能性も多々ありですが、このmoveFolderで下記エラーとなります。
    Exception: 共有ドライブ アイテムではこの操作を利用できません。
    id:移動元フォルダID
    dstFoldername:移動先フォルダ名
    dstFolderID:移動先フォルダID
    moveFolder(id, dstFolderName,dstFolderID) {
    const folder = DriveApp.getFolderById(id);//移動元フォルダー
    let dstFolder;

    if (dstFolderName === “”) {
    Logger.log(“エラーdstFolderName=”+dstFolderName);
    } else {
    if (checkFolderExists(dstFolderID, dstFolderName) ) {
    // dstFolder = DriveApp.getFoldersById(dstFolderID).next();
    dstFolder = DriveApp.getFolderById(dstFolderID);
    } else {
    //dstFolder = DriveApp.createFolder(dstFolderName);
    Logger.log(“エラーdstFolderName=”+dstFolderName);
    Logger.log(“エラーid=”+dstFolderID);
    }
    }

    folder.moveTo(dstFolder);
    const resultStr = `【成功】移動先フォルダURL:${dstFolder.getUrl()}`;
    return resultStr;
    }

    //共有ドライブ内検索
    function checkFolderExists(driveId, folderName) {
    var folders = DriveApp.getFolderById(driveId).getFolders();
    while (folders.hasNext()) {
    var folder = folders.next();
    if (folder.getName() === folderName) {
    return true;
    }
    }
    return false;
    }
    ————————————————-
    今直近でやろうとしている、移動元の共有ドライブと移動先の共有ドライブのアクセス権限は
    同じです。

    これで、実行すると
    Exception: 共有ドライブ アイテムではこの操作を利用できません。となります。

    1. officeの杜 より:

      Issue Trackerを検索してみたところ、moveToでの移動に関してはバグがあり、以前は共有ドライブの場合はフォルダの移動ができないようです。
      https://issuetracker.google.com/issues/163080678

      しかし、実際に検証してコードと説明を「共有ドライブからフォルダを移動」に追加しましたが、こちらのコードでは問題なく移動することができました。
      対象の共有ドライブに対して、コードを実行するユーザの権限が管理者もしくは許可を与えた状態のコンテンツ管理者であるならば移動ができると思います。

akanemaru2017 へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)