Microsoft 365 OneDrive for Business: Upload File

Microsoft 365 OneDrive for Business: ファイルアップロード

This item uploads files to the specified folder on OneDrive.

Auto Step icon
Configs for All Step
Step Name
Note
Configs for this Auto Step
conf_OAuth2
C1: OAuth2 Setting *
conf_uploadedFile
C2: Data item whose attached files will be uploaded *
conf_folderUrl
C3: Folder URL files will be uploaded (Root folder if blank)
conf_shouldReplace
C4: Replace if the file with the same name already exists
conf_fileUrl
C5: Data item to save uploaded file URLs

Notes

  • This Auto Step 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 10MB parts
    • In this case, number of sending requests is expressed by the expression (file size ÷ 10MB) + 1 (you should round up the result of calculation in parenthesis)
    • 1MB = 1,048,576 (1024 * 1024) byte

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 auto step

// 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);
    const uploadedFileUrl = [];

    files.forEach(file => {
        engine.log(`Uploading: ${file.getName()}`);
        if (file.getLength() > LIMIT_SIZE) {
            //over 4MB
            processLargeFile(oauth2, file, folderInfo, shouldReplace, uploadedFileUrl);
        } else {
            //under 4MB
            upload(oauth2, file, 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) {
    fileNumCheck(urlDataDef, files.size());
    let requestNum = 0;
    files.forEach(file => {
        const size = file.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 = new Set();
    files.forEach(file => {
        if (fileNames.has(file.getName())) {
            throw "Two or more files to upload have the same name.";
        }
        fileNames.add(file.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.";
    }
    return JSON.parse(responseStr);
}

/**
  * 共有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);
    }
    return `u!${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, "application/octet-stream");
    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}`;
    const response = httpClient.begin()
        .header("Content-Range", rangetxt)
        .body(packet, "application/octet-stream")
        .put(upUrl);

    const status = response.getStatusCode();
    const responseStr = response.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