
Start: Microsoft 365 OneDrive for Business: File Uploaded
開始: Microsoft 365 OneDrive for Business: ファイルアップロード時
This item starts a process when a file has been uploaded on the specified OneDrive folder.
Basic Configs
- Step Name
- Note
Configs for this Auto Step
- conf_OAuth2
- C1: OAuth2 Setting *
- conf_FolderUrl
- C2: Folder URL to monitor (Root Folder if blank)
- conf_urlData
- C3: Data item to save file URL *
- conf_timestampData
- C4: Data item to save file uploaded datetime
Notes
- This Start Event 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”

- Questetra BPM Suite will periodically poll the OneDrive to check for new uploaded files
- If any files have been uploaded, a process will be started
- A Process will not be started on the first check; only the check will be done
- You can confirm the check status from the “Process Log”
- If a large number of files are uploaded in a short period of time, the processes may not be started for all files
- Approx. 90 processes every 3 minutes ~ 15 minutes
- If there are too many items (files / folders) in the folder (1000 as of December 2023), the check will be aborted
- Control the number of items so that it does not increase too much
Capture

Script (click to open)
- An XML file that contains the code below is available to download
- onedrive-file-uploaded.xml (C) Questetra, Inc. (MIT License)
- Just use it for a reference for the codes
- This file cannot be imported into a Workflow App as an Add-on
// OAuth2 config sample at [OAuth 2.0 Setting]
// - Authorization Endpoint URL: https://login.microsoftonline.com/{your-tenant-id}/oauth2/v2.0/authorize
// - Token Endpoint URL: https://login.microsoftonline.com/{your-tenant-id}/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 DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssX";
const GRAPH_URI = 'https://graph.microsoft.com/v1.0/';
const LIMIT = 1000; // API の制限は明記されていない。1000 件以上のファイル/フォルダがある場合はエラーにする
/**
* configs から必要な情報を取り出す
* @returns {Object} setting 設定
* @returns {string} setting.folderInfo 検索対象のフォルダの情報 {driveId, folderId}
* @returns {AuthSettingWrapper} setting.oauth2 OAuth2 設定
*/
const prepare = () => {
const oauth2 = configs.getObject('conf_OAuth2');
let folderUrl = configs.get('conf_FolderUrl');
const folderInfo = getFolderInfoByUrl(oauth2, folderUrl);
return {
oauth2,
folderInfo
};
};
/**
* フォルダの URL からフォルダ情報(ドライブのパスとフォルダ ID)を取得し、
* オブジェクトで返す(URL が空の場合はドライブのパスを me/drive、フォルダ ID を root にする)
* @param {AuthSettingWrapper} oauth2 OAuth2 認証設定
* @param {String} folderUrl フォルダのURL
* @return {Object} folderInfo フォルダ情報
* @return {String} folderInfo.drivePath ドライブのパス
* @return {String} folderInfo.folderId フォルダ ID
*/
const getFolderInfoByUrl = (oauth2, folderUrl) => {
if (folderUrl === '' || folderUrl === null) {
return {
drivePath: 'me/drive',
folderId: 'root'
};
}
const driveItem = getObjBySharingUrl(oauth2, folderUrl);
if (driveItem.folder === undefined) {
throw 'The specified URL is not a folder.';
}
return {
drivePath: `drives/${driveItem.parentReference.driveId}`,
folderId: driveItem.id
};
};
/**
* OneDriveのドライブアイテム(ファイル、フォルダ)のメタデータを取得し、JSONオブジェクトを返す
* APIの仕様: https://docs.microsoft.com/ja-jp/onedrive/developer/rest-api/api/shares_get?view=odsp-graph-online
* @param {AuthSettingWrapper} oauth2 OAuth2 認証設定
* @param {String} sharingUrl ドライブアイテムの共有 URL
* @return {Object} responseObj ドライブアイテムのメタデータのJSONオブジェクト
*/
const getObjBySharingUrl = (oauth2, sharingUrl) => {
const encodedSharingUrl = encodeSharingUrl(sharingUrl);
const response = httpClient.begin() // HttpRequestWrapper
.authSetting(oauth2) // Request HEADER (OAuth2 Token)
.queryParam('select', 'id,parentReference/driveId,folder')
.get(`${GRAPH_URI}shares/${encodedSharingUrl}/driveItem`); // HttpResponseWrapper
const httpStatus = response.getStatusCode();
const responseStr = response.getResponseAsString();
if (httpStatus >= 300) {
const accessLog = `---GET request--- ${httpStatus}\n${responseStr}\n`;
engine.log(accessLog);
throw `Failed to get drive item. status: ${httpStatus}`;
}
return JSON.parse(response.getResponseAsString());
};
/**
* 共有URLをunpadded base64url 形式にエンコードする
* @param {String} sharingUrl 共有URL
* @return {String} encodedSharingUrl エンコードされた共有URL
*/
const encodeSharingUrl = (sharingUrl) => {
let encodedSharingUrl = base64.encodeToUrlSafeString(sharingUrl);
while (encodedSharingUrl.slice(-1) === '=') {
encodedSharingUrl = encodedSharingUrl.slice(0, -1);
}
return `u!${encodedSharingUrl}`;
};
/**
* ファイルの検索
* @param {number} limit ファイル数の上限
* @param {timestamp} timestampLowerLimit timestamp の下限
* @returns {Array} files ファイル一覧
* @returns {string} files[].id ファイル ID
* @returns {timestamp} files[].timestamp ファイルアップロード時刻
*/
const list = (limit, timestampLowerLimit) => {
const {
oauth2,
folderInfo
} = prepare();
const driveItems = getChildren(oauth2, folderInfo);
let files = driveItems
.filter(driveItem => driveItem.file !== undefined && !engine.isProcessStarted(driveItem.id)) // ファイルのみに絞り込み、かつ未開始のファイル
.map(formatFile) // 必要な情報のみ抜き出し、整形
.filter(file => !file.timestamp.before(timestampLowerLimit)) // timestampLowerLimit 以降のデータのみに絞り込み
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); // 新しい順に並べ替え
// 先頭から limit で切る
files = files.slice(0, limit);
logFiles(files);
return files;
};
/**
* 指定フォルダ内のドライブアイテムの一覧を取得する
* フォルダ内のドライブアイテム (ファイル+フォルダ) 数が 1000 を超える場合、エラー
* @param {AuthSettingWrapper} oauth2 OAuth2 設定
* @param {Object} folderInfo 検索対象のフォルダ情報 {drivePath, folderId}
* @returns {Array} driveItems ドライブアイテム一覧
* @returns {string} driveItems[].id ドライブアイテム ID
* @returns {timestamp} driveItems[].createdDateTime ドライブアイテム作成日時
* @returns {Object} driveItems[].file ドライブアイテムのファイル情報(ファイルかどうかを判定するために使用)
* @returns {string} driveItems[].webUrl ドライブアイテムの URL
*/
const getChildren = (oauth2, folderInfo) => {
const url = `${GRAPH_URI}${folderInfo.drivePath}/items/${folderInfo.folderId}/children`;
const response = httpClient.begin()
.authSetting(oauth2)
.queryParam('$top', `${LIMIT}`)
.queryParam('$select', 'id,createdDateTime,file,webUrl')
.get(url);
const status = response.getStatusCode();
const responseStr = response.getResponseAsString();
if (status !== 200) {
engine.log(responseStr);
throw `Failed to get children of the folder. status: ${status}`;
}
const json = JSON.parse(responseStr);
// 次のページを取得するリンクがある場合は、フォルダにまだファイル/フォルダがあるとみなして、エラーにする
const nextLink = json['@odata.nextLink'];
if (nextLink !== undefined) {
throw `More than ${LIMIT} items are in the specified folder.`;
}
return json.value;
}
/**
* OneDrive のファイルデータから、必要な部分のみ抜き出す
* @param file ファイルデータ
* @returns {Object} file ファイル
* @returns {string} file.id ファイル ID
* @returns {timestamp} file.timestamp ファイルアップロード時刻
*/
const formatFile = (file) => {
const {
id,
createdDateTime,
webUrl
} = file;
return {
id,
timestamp: dateFormatter.parse(DATETIME_FORMAT, createdDateTime),
url: webUrl
};
};
/**
* ファイルのログ出力
* @param {Array} files ファイル一覧
*/
const logFiles = (files) => {
if (files.length === 0) {
engine.log('no files');
return;
}
const replacer = (key, value) => value instanceof java.sql.Timestamp ? dateFormatter.format(DATETIME_FORMAT, value) : value;
files.forEach(file => engine.log(JSON.stringify(file, replacer)));
};
