Microsoft 365 OneDrive for Business: ファイル / フォルダコピー (Microsoft 365 OneDrive for Business: Copy File / Folder)
OneDrive 上のファイル / フォルダを複製し、指定フォルダに新規保存します。
2020-07-28 (C) Questetra, Inc. (MIT License)
Configs
  • C1: OAuth2 設定名 *
  • C2: コピー元ファイル / フォルダの URL *
  • C3: 保存先フォルダの URL (空白の場合、元ファイル / フォルダと同じ場所にコピーされます)
  • C4: 新しいファイル / フォルダの名前 * #{EL}
  • C5: 新しいファイル / フォルダの URL を保存する文字型データ項目
Script
  // OAuth2 config sample at [OAuth 2.0 Setting]
  // - Authorization Endpoint URL: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
  // - Token Endpoint URL: https://login.microsoftonline.com/common/oauth2/v2.0/token
  // - Scope: https://graph.microsoft.com/Files.ReadWrite.All offline_access
  // - Consumer Key: (Get by Microsoft Azure Active Directory)
  // - Consumer Secret: (Get by Microsoft Azure Active Directory)

  // グローバル変数
  const GRAPH_URI = "https://graph.microsoft.com/v1.0/";

  main();
  function main(){
    //// == Config Retrieving / 工程コンフィグの参照 ==
    const oauth2 = configs.get( "conf_OAuth2" );
    const sourceUrl = retrieveSourceUrl();
    const destUrl = retrieveDestUrl();
    const newName = retrieveNewName();
    const saveUrlDataDef = configs.getObject( "conf_dataForUrl" );

    //// == Calculating / 演算 ==
    // checking the HTTP requesting limit
    checkHttpRequestingLimit( destUrl );
    // preparing token for API Requests
    const token  = httpClient.getOAuth2Token( oauth2 );
    // getting itemInfo for Requesting Copy and Updating Data
    const sourceInfo = getItemInfoByUrl( sourceUrl, token );
    const destInfo = getItemInfoByUrl( destUrl, token );
    // sending Copy Request
    const copyResponse = sendCopyRequest( sourceInfo, destInfo, newName, token );

    // コピー状況を確認し、ドライブアイテム ID を取得
    const newItemId = getNewItemId( copyResponse );

    // ワークフローデータへの代入データの作成
    const newItemUrl = getNewItemUrl( sourceInfo.driveId, destInfo.driveId, newItemId, token );

    //// == Data Updating / ワークフローデータへの代入 ==
    if ( saveUrlDataDef !== null ){
      engine.setData( saveUrlDataDef, newItemUrl );
    }
  }

  /**
    * configから値を読み出し、必要に応じて値チェックを行った後、値を返す
    * @return {String} configの値
    */
  function retrieveSourceUrl() {
    const sourceUrlDef = configs.getObject( "conf_sourceUrl" );
    let sourceUrl = configs.get( "conf_sourceUrl" );
    if ( sourceUrlDef !== null ) {
      sourceUrl = engine.findData( sourceUrlDef );
    }
    if ( sourceUrl === "" || sourceUrl === null ) {
      throw `Source file / folder URL is empty.`;
    }
    return sourceUrl;
  }

  function retrieveDestUrl() {
    const destUrlDef = configs.getObject( "conf_destUrl" );
    let destUrl = configs.get( "conf_destUrl" );
    if ( destUrlDef !== null ) {
      destUrl = engine.findData( destUrlDef );
    }
    return destUrl;
  }

  function retrieveNewName() {
    const newName = configs.get( "conf_newName" );
    if ( newName === "" || newName === null ) {
      throw `New file / folder name is empty.`;
    }
    return newName;
  }

  /**
    * HTTPリクエストの上限を超えないか確認する
    * @param {String} destUrl  コピー先フォルダのURL
    */
  function checkHttpRequestingLimit( destUrl ) {
    const reqLimit = httpClient.getRequestingLimit();
    if ( destUrl !== "" && destUrl !== null ) {
      if ( reqLimit < 5 ) {
        throw `HTTP requesting limit is fewer than necessary requests.`;
      }
    } else if ( reqLimit < 4 ) {
      throw `HTTP requesting limit is fewer than neceessary requests.`;
    }
  }

  /**
    * ドライブアイテムのURLからアイテム情報(ドライブIDとアイテムID)を取得し、
    * オブジェクトで返す(URLが空の場合はドライブIDもアイテムIDも空文字列)
    * @param {String} driveItemUrl  ドライブアイテム(ファイル、フォルダ)のURL
    * @param {String} token  OAuth2 トークン
    * @return {Object} itemInfo  ドライブアイテム情報 {driveId, id}
    */
  function getItemInfoByUrl( driveItemUrl, token ) {
    let itemInfo = {driveId: "", id: ""};
    if ( driveItemUrl !== "" && driveItemUrl !== null ) {
      // 分割代入
      const {
        id: id,
        parentReference: {
          driveId: driveId
        }
      } = getObjBySharingUrl( driveItemUrl, token );
      itemInfo = {driveId: driveId, id: id};
    }
    return itemInfo;
  }

  /**
    * OneDriveのドライブアイテム(ファイル、フォルダ)のメタデータを取得し、JSONオブジェクトを返す
    * APIの仕様:https://docs.microsoft.com/ja-jp/onedrive/developer/rest-api/api/shares_get?view=odsp-graph-online
    * @param {String} sharingUrl  ドライブアイテムの共有URL
    * @param {String} token  OAuth2 トークン
    * @return {Object} responseObj  ドライブアイテムのメタデータのJSONオブジェクト
    */
  function getObjBySharingUrl( sharingUrl, token ) {
    if (sharingUrl === "" || sharingUrl === null) {
      throw `Sharing URL is empty.`;
    }

    // encoding sharing URL
    const encodedSharingUrl = encodeSharingUrl(sharingUrl);

    // preparing for API Request
    let apiRequest = httpClient.begin(); // HttpRequestWrapper
    // com.questetra.bpms.core.event.scripttask.HttpClientWrapper
    // Request HEADER (OAuth2 Token, HTTP Basic Auth, etc)
    apiRequest = apiRequest.bearer( token );
    // Access to the API (POST, GET, PUT, etc)
    let response = apiRequest.get( `${GRAPH_URI}shares/${encodedSharingUrl}/driveItem` ); // HttpResponseWrapper
    const httpStatus = response.getStatusCode();
    const accessLog = `---GET request--- ${httpStatus}\n${response.getResponseAsString()}\n`;
    engine.log(accessLog);
    if (httpStatus >= 300) {
      const error = `Failed to get drive item. status: ${httpStatus}`;
      throw error;
    }
    const responseObj = JSON.parse( response.getResponseAsString() );
    return responseObj;
  }

  /**
    * 共有URLをunpadded base64url 形式にエンコードする
    * @param {String} sharingUrl  共有URL
    * @return {String} encodedSharingUrl  エンコードされた共有URL
    */
  function encodeSharingUrl( sharingUrl ) {
    let encodedSharingUrl = base64.encodeToUrlSafeString( sharingUrl );
    while ( encodedSharingUrl.slice(-1) === '=' ) {
      encodedSharingUrl = encodedSharingUrl.slice(0,-1);
    }
    encodedSharingUrl = "u!" + encodedSharingUrl;
    return encodedSharingUrl;
  }

  /**
    * copyリクエストをPOSTし、レスポンスを返す
    * @param {String} sourceInfo  コピー元アイテム情報 {driveId, id}
    * @param {String} destInfo  コピー先フォルダ情報 {driveId, id}
    * @param {String} newName  新しいファイル / フォルダの名前
    * @param {String} token  OAuth2 トークン
    * @return {HttpResponseWrapper} response  レスポンス
    */
  function sendCopyRequest( sourceInfo, destInfo, newName, token ) {
    // preparing token for API Requests
    let apiRequest = httpClient.begin(); // HttpRequestWrapper
    // com.questetra.bpms.core.event.scripttask.HttpClientWrapper
    // Request HEADER (OAuth2 Token, HTTP Basic Auth, etc)
    apiRequest = apiRequest.bearer( token );
    // Request PATH
    const apiUri = `${GRAPH_URI}drives/${sourceInfo.driveId}/items/${sourceInfo.id}/copy`;
    // Request BODY (JSON, Form Parameters, etc)
    const requestBody = generateCopyRequestBody( destInfo, newName );
    apiRequest = apiRequest.body( requestBody, "application/json" );
    // Access to the API (POST, GET, PUT, etc)
    let response = apiRequest.post( apiUri ); // HttpResponseWrapper
    const httpStatus = response.getStatusCode() + "";
    const accessLog = `---POST request--- ${httpStatus}\n${response.getResponseAsString()}\n`;
    engine.log(accessLog);
    if (httpStatus >= 300) {
      const error = `Failed to copy. status: ${httpStatus}`;
      throw error;
    }
    return response;
  }

  /**
    * copyリクエストのBODYを生成し、JSON文字列で返す
    * @param {Object} destInfo  コピー先フォルダ情報 {driveId, id}
    * @param {String} newName  新しいファイル / フォルダの名前
    * @return {JSON String} requestBody  リクエストBODY
    */
  function generateCopyRequestBody( destInfo, newName ) {
    let requestBodyObj = {};
    if ( destInfo.driveId !== "" ) {
      requestBodyObj.parentReference = {
        driveId: destInfo.driveId,
        id: destInfo.id
      };
    }
    if ( newName !== "" && newName !== null ) {
      requestBodyObj.name = newName;
    }
    const requestBody = JSON.stringify( requestBodyObj );
    return requestBody;
  }

  /**
    * コピーAPIのレスポンスからコピーの完了状態を確認し、新しいドライブアイテムのIDを返す
    * @param {HttpResponseWrapper} copyResponse  コピーAPIのレスポンス
    * @return {String} newItemId  新しいドライブアイテムのID
    */
  function getNewItemId( copyResponse ) {
    const location = copyResponse.getHeaderValues("Location").get(0);
    let monitorResponseObj = getMonitorResponseObj( location );
    let copyStatus = monitorResponseObj.status;
    let newItemId = "";
    if ( copyStatus === "notStarted" || copyStatus === "inProgress" ) {
      // 未開始または進行中の場合、newItemId は空文字列のまま
      engine.log(`Copy status: ${copyStatus}\nTo retrieve copy status, GET ${location}\n`);
    } else if ( copyStatus === "completed" ) {
      // 完了の場合、ドライブアイテムIDを取得
      engine.log(`Copy status: ${copyStatus}\n`);
      newItemId = monitorResponseObj.resourceId;
    } else {
      // 不明なステータスの場合はエラー
      const error = `Copy is not in progress nor completed.\n status: ${copyStatus}`;
      engine.log(`error: ${JSON.stringify( monitorResponseObj.error )}\n`);
      throw error;
    }
    return newItemId;
  }

  /**
    * copyの完了状態レポートを取得し、JSONオブジェクトを返す
    * @param {String} location  copy応答のLocationヘッダの値(コピー操作の現在の状況を返すサービスの URL)
    * @return {Object} responseObj  copy完了状態レポートのJSONオブジェクト
    */
  function getMonitorResponseObj( location ) {
    // preparing for API Request
    let apiRequest = httpClient.begin(); // HttpRequestWrapper
    // Access to the API (POST, GET, PUT, etc)
    let response = apiRequest.get( location ); // HttpResponseWrapper
    const httpStatus = response.getStatusCode() + "";
    const accessLog = `---GET request--- ${httpStatus}\n${response.getResponseAsString()}\n`;
    engine.log(accessLog);
    if (httpStatus >= 300) {
      const error = `Failed to get monitor. status: ${httpStatus}`;
      throw error;
    }
    const responseObj = JSON.parse( response.getResponseAsString() );
    return responseObj;
  }

  /**
    * 新しいファイル / フォルダのURLを返す
    * @param {String} driveIdOfSource  コピー元アイテムのドライブのID(フォルダのドライブIDが空文字列の場合に使用)
    * @param {String} driveIdOfDest  コピー先フォルダのドライブID
    * @param {String} newItemId  新しいファイル/ フォルダのID
    * @param {String} token  OAuth2 トークン
    * @return {String} newItemUrl  新しいファイル / フォルダのURL
    */
  function getNewItemUrl( driveIdOfSource, driveIdOfDest, newItemId, token ) {
    let newItemUrl = "";
    // ドライブIDを決める(コピー先フォルダのドライブIDが空文字列でなければフォルダのドライブID)
    let driveId = driveIdOfSource;
    if ( driveIdOfDest !== "" ) {
      driveId = driveIdOfDest;
    }
    if ( newItemId !== "" ) {
      newItemUrl = getItemUrlById( driveId, newItemId, token );
    }
    return newItemUrl;
  }

  /**
    * OneDriveのドライブアイテムのメタデータを取得し、URLを返す
    * @param {String} driveId  ドライブID
    * @param {String} itemId  アイテムID
    * @param {String} token  OAuth2 トークン
    * @return {String} itemUrl  ドライブアイテムのURL
    */
  function getItemUrlById( driveId, itemId, token ) {
    if (itemId === "" || itemId === null) {
      throw `DriveItem ID is empty.`;
    }
    if (driveId === "" || driveId === null) {
      throw `Drive ID is empty.`;
    }

    // preparing for API Request
    let apiRequest = httpClient.begin(); // HttpRequestWrapper
    // com.questetra.bpms.core.event.scripttask.HttpClientWrapper
    // Request HEADER (OAuth2 Token, HTTP Basic Auth, etc)
    apiRequest = apiRequest.bearer( token );
    // Access to the API (POST, GET, PUT, etc)
    let response = apiRequest.get( `${GRAPH_URI}drives/${driveId}/items/${itemId}` ); // HttpResponseWrapper
    const httpStatus = response.getStatusCode() + "";
    const accessLog = `---GET request--- ${httpStatus}\n${response.getResponseAsString()}\n`;
    engine.log(accessLog);
    if (httpStatus >= 300) {
      const error = `Failed to get file. status: ${httpStatus}`;
      throw error;
    }
    const responseObj = JSON.parse( response.getResponseAsString() );
    const itemUrl = responseObj.webUrl;
    return itemUrl;
  }

Download

Capture

Notes

  1. Microsoft 365 の OneDrive for Business で使用できるアドオンです。個人用の OneDrive では使用できません。
  2. ファイルやフォルダの URL は、OneDrive でファイルやフォルダの詳細ウィンドウ(右上のiのアイコン)から「その他の詳細」へ進み、「パス」の隣のアイコンから取得します。(上部メニューの「共有」や「リンクのコピー」から取得した URL も使用できます)
    ファイル、フォルダURLの取得方法
%d人のブロガーが「いいね」をつけました。