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
Script (click to open)

// 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'));
  }
}
  

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

Appendix

  • 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

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: