Microsoft 365 OneDrive for Business: Upload File
Uploads files to the specified folder on OneDrive.
Configs: Common
  • Step Name
  • Note
Configs
  • C1: OAuth2 Setting *
  • C2: Data item whose attached files will be uploaded *
  • C3: Folder URL files will be uploaded (Root folder if blank)
  • C4: Replace if the file with the same name already exists
  • C5: Data item to save uploaded file URLs

Notes

  • This add-on is for OneDrive for Business of Microsoft 365. It does not work for OneDrive personal.
  • To get the URL of a file/folder on OneDrive, open the Details window of the file/folder by clicking the Information icon, proceed to “More details”, and click the icon next to “Path”.
    How to get URL of file/folder
  • Files over 4MB will be divided into 10MiB parts. In this case, number of sending requests is expressed by the expression (file size ÷ 10MiB) + 1 (you should round up the result of calculation in parenthesis).

Capture

See also

Script (click to open)
  • An XML file that contains the code below is available to download
    • onedrive-file-upload.xml (C) Questetra, Inc. (MIT License)
      • If you are using Professional, you can modify the contents of this file and use it as your own add-on
    
    // 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 LIMIT_SIZE = 4194304; //File size border of Microsoft Graph
    const PACKET_MAX_SIZE = 10485760; //size of each packet,must be a multiple of 327680(320KiB): 10485760=10MiB
    const GRAPH_URI = "https://graph.microsoft.com/v1.0/";
    
    main();
    function main(){
      //// == 工程コンフィグの参照 / Config Retrieving ==
      const oauth2 = configs.get("conf_OAuth2");
      const folderUrl = retrieveFolderUrl();
      const urlDataDef = configs.getObject("conf_fileUrl");
      const shouldReplace = configs.getObject("conf_shouldReplace");
    
      //// == ワークフローデータの参照 / Data Retrieving ==
      const files = engine.findData( configs.getObject("conf_uploadedFile") );
      if (files === null) {
        setData(urlDataDef,[""]);
        return;
      }
    
      //// == 演算 / Calculating ==
    
      fileCheck(files, urlDataDef, folderUrl);
      const folderInfo = getFolderInfoByUrl( folderUrl, oauth2 );
      let uploadedFileUrl = [];
    
      for(let i = 0; i< files.size(); i++){
        engine.log(`Uploading: ${files.get(i).getName()}`);
        if(files.get(i).getLength() > LIMIT_SIZE){
          //over 4MB
          processLargeFile(oauth2,files.get(i),folderInfo,shouldReplace,uploadedFileUrl);
        }else{
          //under 4MB
          upload(oauth2,files.get(i),folderInfo,shouldReplace,uploadedFileUrl);
        }
      }
      //// == ワークフローデータへの代入 / Data Updating ==
      setData(urlDataDef,uploadedFileUrl);
    }
    
    /**
      * configからフォルダURLの値を読み出す
      * @return {String} configの値
      */
    function retrieveFolderUrl() {
      const folderUrlDef = configs.getObject( "conf_folderUrl" );
      let folderUrl = "";
      if ( folderUrlDef === null ) {
        folderUrl = configs.get( "conf_folderUrl" );
      }else{
        folderUrl = engine.findData( folderUrlDef );
      }
      return folderUrl;
    }
    
    /**
      * アップロードしようとするファイルの名前・数・サイズが適切かどうかチェックする
      * ファイル数、通信制限をチェック
      * 通信数=4MB以下のファイルの数 + <4MBを超えるファイルそれぞれについて>(ceil(ファイルサイズ/パケットの最大サイズ) + 1)
      * その後ファイル名をチェック
      * @param {Array<File>} files  アップロードしようとするファイル
      * @param {ProcessDataDefinitionView} urlDataDef  URL を保存するデータ項目の ProcessDataDefinitionView
      * @param {String} folderUrl  アップロード先フォルダの URL
      */
    function fileCheck(files,urlDataDef,folderUrl){
      const fileNum = files.size(); //number of files
      fileNumCheck(urlDataDef,fileNum);
      let requestNum = 0;
      for (let i = 0; i < fileNum; i++){
        const size = files.get(i).getLength();
        if(size > LIMIT_SIZE){
          requestNum += (Math.ceil(size / PACKET_MAX_SIZE) + 1);
        }else{
          requestNum++;
        }
      }
      if(folderUrl !== "" || folderUrl !== null){
        requestNum++;
      }
      if(requestNum > httpClient.getRequestingLimit()){
        throw "Necessary HTTP requests exceeds the limit.";
      }
      checkFileNameOverlap(files);
    }
    
    /**
      * アップロードするデータが複数で URL 出力先のデータ項目が単一行ならエラーにする
      * @param {ProcessDataDefinitionView} dataDef  データ項目の ProcessDataDefinitionView
      * @param {Number} fileNum  アップロードしようとしているファイルの個数
      */
    function fileNumCheck(dataDef,fileNum){
      if(dataDef !==  null){
        //Multiple Judge
        if(dataDef.matchDataType("STRING_TEXTFIELD") && fileNum > 1){
          throw "Multiple files are set though the Data Item to save the output is Single-line String."
        }
      }
    }
    
    /**
     * アップロードするファイルの中に同じファイル名のものが2つ以上あればエラー
     * @param {Array<File>}  アップロードしようとするファイルの配列
     */
    function checkFileNameOverlap(files) {
      const fileNames = [];
      const fileNum = files.size();
      for (let i = 0; i < fileNum; i++) {
        if (fileNames.includes(files[i].getName())) {
          throw "Two or more files to upload have the same name.";
        }
        fileNames[i] = files[i].getName();
      }
    }
    
    /**
      * フォルダのURLからフォルダ情報(ドライブIDとフォルダID)を取得し、
      * オブジェクトで返す(URLが空の場合はドライブIDをme/drive、フォルダIDをrootにする)
      * @param {String} folderUrl  フォルダのURL
      * @param {String} oauth2  OAuth2 設定情報
      * @return {Object} folderInfo  フォルダ情報 {driveId, folderId}
      */
    function getFolderInfoByUrl( folderUrl, oauth2 ) {
      let folderInfo = {driveId: "me/drive", folderId: "root"};
      if ( folderUrl !== "" && folderUrl !== null ) {
        // 分割代入
        const {
          id,
          parentReference: {
            driveId
          }
        } = getObjBySharingUrl( folderUrl, oauth2 );
        folderInfo = {driveId: `drives/${driveId}`, folderId: id};
      }
      return folderInfo;
    }
    
    /**
      * 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} oauth2  OAuth2 設定情報
      * @return {Object} responseObj  ドライブアイテムのメタデータのJSONオブジェクト
      */
    function getObjBySharingUrl( sharingUrl, oauth2 ) {
      if (sharingUrl === "" || sharingUrl === null) {
        throw "Sharing URL is empty.";
      }
    
      // encoding sharing URL
      const encodedSharingUrl = encodeSharingUrl(sharingUrl);
    
      // preparing for API Request
      const response = httpClient.begin()
        .authSetting(oauth2)
        .get( `${GRAPH_URI}shares/${encodedSharingUrl}/driveItem`);
      const status = response.getStatusCode();
      const responseStr = response.getResponseAsString();
      if (status >= 300) {
        engine.log(`status: ${status}`);
        engine.log(responseStr);
        throw "Failed to get drive item.";
      }
      const responseObj = JSON.parse(responseStr);
      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;
    }
    
    /**
      * ファイルをアップロードする。一回につき一つのみ。
      * @param {String} oauth2  OAuth2 設定情報
      * @param {File} file  アップロードするファイル
      * @param {String,String} driveId,folderId  アップロード先ドライブ、フォルダのID
      * @param {boolean} shouldReplace  上書きするかどうか
      * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
      */
    function upload(oauth2,file,{
      driveId,
      folderId
    },shouldReplace,uploadedFileUrl){
      const url = `${GRAPH_URI}${driveId}/items/${folderId}:/${encodeURIComponent(file.getName())}:/content`;
      let request = httpClient.begin()
        .authSetting(oauth2)
        .body(file);
      if (!shouldReplace) { // デフォルトは replace
        request = request.queryParam("@microsoft.graph.conflictBehavior", "fail");
      }
      const response = request.put(url);
    
      const responseStr = response.getResponseAsString();
      const status = response.getStatusCode();
      
      if (status >= 300) {
        //when error thrown
        engine.log(`Status: ${status}`);
        engine.log(responseStr);
        throw "Failed to upload";
      }
      engine.log("Succeeded to upload.");
      outputDataSet(responseStr,uploadedFileUrl);
    }
    
    /**
      * 4MBを超えるファイルのアップロード処理を行う
      * @param {String} oauth2  OAuth2 設定情報
      * @param {File} file  アップロードするファイル
      * @param {String,String} driveId,folderId  アップロード先ドライブ、フォルダのID
      * @param {boolean} shouldReplace  上書きするかどうか
      * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
      */
    function processLargeFile(oauth2,file,{
      driveId,
      folderId
    },shouldReplace,uploadedFileUrl){
      const upUrl = createSession(oauth2,file.getName(),{
        driveId,
        folderId
      },shouldReplace);
      let range = 0;
      const fileSize = file.getLength();
    
      fileRepository.readFile(file, PACKET_MAX_SIZE, function(packet){
        //upload each fragment of file
        range = uploadLarge(upUrl,range,packet,fileSize,uploadedFileUrl);
      });
    }
    
    /**
      * アップロード用のセッションを作成する
      * @param {String} oauth2  OAuth2 設定情報
      * @param {String} fileName  アップロードするファイルのファイル名
      * @param {String,String} driveId,folderId  アップロード先ドライブ、フォルダのID
      * @param {boolean} shouldReplace  上書きするかどうか
      * @return {String}  アップロード先 URL
      */
    function createSession(oauth2,fileName,{
      driveId,
      folderId
    },shouldReplace){
      const url = `${GRAPH_URI}${driveId}/items/${folderId}:/${encodeURIComponent(fileName)}:/createUploadSession`;
      let request = httpClient.begin()
        .authSetting(oauth2);
      if (!shouldReplace) { // デフォルトは replace
        const body = {
          "item": {
            "@microsoft.graph.conflictBehavior": "fail"
          }
        }
        request = request.body(JSON.stringify(body), "application/json; charset=UTF-8");
      }
      const response = request.post(url);
    
      const status = response.getStatusCode();
      const responseStr = response.getResponseAsString();
      
      if(status >= 300){
        engine.log(`Status: ${status}`);
        engine.log(responseStr);
        throw "Failed to create upload session";
      }
      return JSON.parse(responseStr).uploadUrl;
    }
    
    /**
      * 4MBを超えるファイルについて、各部分のアップロードを実行する
      * @param {String} upUrl  アップロード先 URL
      * @param {Number} range  これまでにアップロードしたサイズ
      * @param {ByteArrayWrapper} packet  アップロードするバイナリ
      * @param {Number} fileSize  アップロードするファイルのサイズ
      * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
      * @return {Number}  新しい range
      */
    function uploadLarge(upUrl,range,packet,fileSize,uploadedFileUrl){
      const packetSize = packet.getLength();
      const rangetxt = `bytes ${range}-${range + packetSize - 1}/${fileSize}`;
      let sending = httpClient.begin()
        .header("Content-Range", rangetxt )
        .body(packet,"application/octet-stream")
        .put(upUrl);
    
      const status = sending.getStatusCode();
      const responseStr = sending.getResponseAsString();
      
      if(status >= 300){
        engine.log(`Status: ${status}`);
        engine.log(responseStr);
        throw "Failed to upload";
      }else if(status === 202){
        range += packetSize;
        return range;
      }else{
        engine.log("Succeeded to upload.");
        outputDataSet(responseStr,uploadedFileUrl);
        return range;
      }
    }
    
    /**
      * アップロードしたデータのURLを配列にセットする。
      * @param {String} responseStr  送信時のレスポンスをテキスト出力したもの
      * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
      */
    function outputDataSet(responseStr,uploadedFileUrl){
      const json = JSON.parse(responseStr);
      uploadedFileUrl.push(json.webUrl);
    }
    /**
      * アップロードしたデータのURLをデータ項目に出力する。
      * @param {ProcessDataDefinitionView} dataDef  データ項目の ProcessDataDefinitionView
      * @param {Array<String>} uploadedFileUrl  アップロードしたファイルのURLを格納する配列
      */
    function setData(dataDef,uploadedFileUrl){
      if(dataDef !==  null){
        engine.setData(dataDef,uploadedFileUrl.join('\n'));
      }
    }
      
    

1 thought on “Microsoft 365 OneDrive for Business: Upload File”

  1. Pingback: Utilising OneDrive from Your Workflow – Questetra Support

Comments are closed.

%d bloggers like this: