Google Apps ScriptとGraph APIでOneDriveアップローダを作る【GAS】
会社としてのファイルストレージはBoxを利用しつつも、外部共有用にということでOneDriveを使うといったような使い分けをしてる事例は多いかと思います。Boxではなるべく外部共有をせず、またOneDriveには会社の情報を置かず、外部の企業とやり取りする専用として使うといったような事例になります。
この時、いちいちOneDriveを開いてアップして共有リンクを取得して相手にメールを送るというのは非常に億劫なので、OneDriveとGoogle Apps Scriptを利用して、アップローダを作れないか?ということで実験してみました。
今回使用するスプレッドシート等
- OneDrive Uploader - Google Spreadsheet
- OneDrive UploaderをVueで実装したフルバージョン
GASはスクリプト実行が最大6分の制限がある為、GASでアップロードとなるとファイルサイズが限られる上に回線状況によってアップできないといったことが出る可能性があります。故にHTML側で稼働するGraph APIでアップロードをさせて共有リンクを得るのが今回の目的。
GAS側は認証とAccess Tokenの処理を中心に実装します。過去にBox file Pickerで実装したことがあります。
OneDrive Pickerという仕組みもあるのですが、GASでは動作しませんでした。
事前準備
利用するためには事前にAzure ADでアプリプロジェクトを作成し、認証を経てAccess Tokenを取得する必要性があります。実際にアップロードを行うのはHTML側となるため、役割分担をはっきりさせています。
GAS側の事前準備
ライブラリの追加
以下の手順でOAuth2 for Apps Scriptライブラリを追加しましょう。
- スクリプトエディタを開きます。
- メニューより「リソース」⇒「ライブラリ」を開きます。
- ライブラリを追加欄に「1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF」を追加します。
- 今回はバージョンは43を選択してみます。
- 保存ボタンを押して完了
これで、OAuth2.0認証にまつわる様々な関数を手軽に利用できるようになります。
図:ライブラリを追加した様子
コールバックURLを取得する
コールバックURLとは、認証を完了しAccess Tokenを取得したら戻るべきURLを指定するものです。これは、スクリプトIDをもとに作られているので、スクリプトIDを取得して組み立てます。
- スクリプトエディタのメニューより「ファイル」⇒「プロジェクトのプロパティ」を開く
- 情報の中にある「スクリプトID」を控えておく。
- https://script.google.com/macros/d/スクリプトID/usercallback として組み立てる。これがコールバックURLとなる。
図:スクリプトIDはファイル毎に異なるのです。
Azureでプロジェクトを作成
- アプリの登録にて登録を開始する
- 新規登録をクリックする
- 名前を入力(今回はonedriveuploadと入力しました)、リダイレクトURIは「webを選択しhttps://script.google.com/macros/d/スクリプトID/usercallback」を入力。
- 登録ボタンをクリックする
- 出てきた中で、「アプリケーション(クラと書かれているのがクライアントID」なので、このコードをメモしておく
- 左サイドバーより、「証明書とシークレット」をクリック
- 「新しいクライアントシークレット」をクリックする
- 今回は12か月期限で追加をクリック
- これで値に「クライアントシークレット」が生成されて手に入りました(値の項目がそれになる)。このシークレットはこの時だけしか表示されないので、注意してください。
- つづけて、左サイドバーより「APIのアクセス許可」をクリックする
- Microsoft APIの中にある「Microsoft Graph」をクリックする。
- 「委任されたアクセス許可」をクリックする
- アクセス許可の追加をクリックする
- 管理者の承認が必要なものの場合、追加出来たら、xxxxxに管理者の同意を与えますをクリックします。すると、状態が緑色になります。
- デフォルトでUser.ReadがすでにONなので、今回はopenid、offline_access、Files.ReadWrite.All、Sites.Read.Allを検索してONにしましょう。(Files.ReadWrite.Allにしないとアップロードが出来ません)
- 次に左サイドバーより「認証」をクリック
- 暗黙の付与にて、「アクセストークン」にチェックを入れる
- サポートされているアカウントの種類に於いては、「マルチテナント」にしておきました。
- 保存をクリック
- 概要のエンドポイントをクリックすると、いろいろなエンドポイントURLが出る。
- 概要のディレクトリ(テナントの数値はメモっておきます。あとでプログラム中で使用します。
図:追加したスコープ一覧
図:Access Tokenを取得して使えるようにする
OneDriveにフォルダを作っておく
今回はOneDriveのルート直下に「テストマン」というフォルダを作っておき、そこにアップロードするという仕様にしています。ユーザが自由にフォルダを作ってアップロードさせるという場合には、OneDriveにフォルダを作るAPIを事前に叩いてから、そのフォルダ名でもってアップロードするという仕組みが別途必要です。
注意点
GASでファイルのアップロードを行わせるとなると、色々と制限事項に引っかかってしまいます。主な制限事項は
- GASの実行最大時間は6分まで(6分以内にアップロードが終わらないサイズの場合、アップロードが出来ません)
- POSTで送信できるファイルサイズが上限50MBまでとなってる
- OneDrive Businessは1TBが容量。E3以上が5TBまで使えますが、アップするファイルサイズは最大250GBまで。
そうなると、50MB以上の巨大なファイルを送りたい場合、途中でセッション切れやファイルサイズ制限で送れないことになります。そこで今回はGraph APIを使うに当たって認証とAccess Tokenの処理はGAS側で行わせ、アップロード作業はHTML側でFetch APIを使ってリクエストを送るという方法で回避しています。
認証を行う処理を作成する
OAuth2 for Apps Scriptのページの「Create the OAuth2 Service」にあるコードを元に、Google Apps Script側で構築をします。この時、Microsoft365側で取得したアプリケーションIDやシークレットを使います。また、今回はいつもよりも要求するアクセス権限が多い点に注意。
認証実行はstartoauthを叩くのですが、事前にウェブアプリケーションとしてデプロイしておく必要があります。
GAS側コード
//メニューを構築する
function onOpen(e) {
var ui = SpreadsheetApp.getUi();
ui.createMenu('▶OAuth認証')
.addItem('認証の実行', 'startoauth')
.addSeparator()
.addItem('ログアウト', 'reset')
.addItem('Teamsデータ取得', 'getTeamsLogs')
.addToUi();
}
//認証用の各種変数
var appid = 'ここにアプリケーションIDを入れる';
var appsecret='ここにシークレットを入れる';
var scope = "User.Read openid offline_access Files.ReadWrite.All Sites.Read.All"
var endpoint = "https://graph.microsoft.com/beta"
var tokenurl = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
var authurl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
function startoauth(){
//UIを取得する
var ui = SpreadsheetApp.getUi();
//認証済みかチェックする
var service = checkOAuth();
if (!service.hasAccess()) {
//認証画面を出力
var output = HtmlService.createHtmlOutputFromFile('template').setHeight(450).setWidth(500).setSandboxMode(HtmlService.SandboxMode.IFRAME);
ui.showModalDialog(output, 'OAuth2.0認証');
} else {
//認証済みなので終了する
ui.alert("すでに認証済みです。");
}
}
//アクセストークンURLを含んだHTMLを返す関数
function authpage(){
var service = checkOAuth();
var authorizationUrl = service.getAuthorizationUrl();
var html = "<center><b><a href='" + authorizationUrl + "' target='_blank' onclick='closeMe();'>アクセス承認</a></b></center>"
return html;
}
//認証チェック
function checkOAuth() {
return OAuth2.createService("Microsoft Graph")
.setAuthorizationBaseUrl(authurl)
.setTokenUrl(tokenurl)
.setClientId(appid)
.setClientSecret(appsecret)
.setScope(scope)
.setCallbackFunction("authCallback") //認証を受けたら受け取る関数を指定する
.setPropertyStore(PropertiesService.getScriptProperties()) //スクリプトプロパティに保存する
.setParam("response_type", "code");
}
//認証コールバック
function authCallback(request) {
var service = checkOAuth();
Logger.log(request);
var isAuthorized = service.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput("認証に成功しました。ページを閉じてください。");
} else {
return HtmlService.createHtmlOutput("認証に失敗しました。");
}
}
//ログアウト
function reset() {
checkOAuth().reset();
SpreadsheetApp.getUi().alert("ログアウトしました。")
}
- 今回利用するGraph APIのエンドポイントはhttps://graph.microsoft.com/betaとなります。
- 要求する権限はscopeに半角スペースで区切って、Azure AD側で用意したものと同じものを設定します。
- startoauthを実行して認証を実行すれば、スクリプトプロパティにAccess Tokenが格納されます。
HTML側コード
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
<script>
google.script.run.withSuccessHandler(onSuccess).authpage();
function onSuccess(data){
document.getElementById("kinoko").innerHTML = data;
}
</script>
<style type="text/css">
/* --- ボックス --- */
div.section {
width: 480px; /* ボックスの幅 */
background-color: #ffffff; /* ボックスの背景色 */
border: 1px #c0c0c0 solid; /* ボックスの境界線 */
font-size: 100%; /* ボックスの文字サイズ */
}
/* --- 見出し --- */
div.section h3 {
margin: 0; /* 見出しのマージン */
padding: 6px 10px; /* 見出しのパディング(上下、左右) */
background-color: #f5f5f5; /* 見出しの背景色 */
border-bottom: 1px #c0c0c0 solid; /* 見出しの下境界線 */
font-size: 120%; /* 見出しの文字サイズ */
}
/* --- ボックス内の段落 --- */
div.section p {
margin: 1em 10px; /* 段落のマージン(上下、左右) */
}
</style>
<div class='section'>
<center>
<img border="0" src="https://officeforest.org/library/oauth2.png" alt="oauth2">
<h3 id='header'>OAuth認証の許可が必要です。</h3>
</center>
<hr>
<div id="info">
<p>
このスクリプトは、Microsoft Graph APIにアクセスするために、特別なログイン処理を利用しています。<br>
既に特別なログインに関する設定はなされており、承認がされるとプログラムを実行することが出来ます。この承認がなされない場合、プログラムの実行に制限が掛かり、
処理が続行できません。<br><br>
<div id="kinoko"></div>
</div>
<p>
<script>
function closeMe(){
if(google && google.script && google.script.host){
google.script.host.close();
} else if(window && window.close){
window.close();
}
}
</script>
</div>
- 実際にこれらのコードで、startoauthを実行すると、スプレッドシート上で認証用のダイアログが出ます。
- 認証でMicrosoft365アカウントにログインします
- 取得したAccess Tokenほかはスクリプトプロパティのoauth2.Graphという項目にガッツリ値が格納されます。ここにはAccess Token, Refresh Token, expire_inのタイムなどが入っています。
- reset関数はログアウトされて、再度認証ができるようになります。
ソースコード
GAS側コード共通
//外部貼り付け用
function doGet(e){
var html = HtmlService.createTemplateFromFile("index").evaluate()
html = html.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return html;
}
//Access Tokenを取得する
function getAccessToken(){
//Graph APIサービスを取得する
var service = checkOAuth();
if (service.hasAccess()) {
return service.getAccessToken();
}else{
//エラーを返す(認証が実行されていない場合)
return "error";
}
}
//uploadlogのシートデータを返す
function getUploadList(){
//プロパティを取得
let prop = PropertiesService.getScriptProperties();
let ssid = prop.getProperty("sheetid");
//スプレッドシート情報を取得
let sheet = SpreadsheetApp.openById(ssid);
let ss = sheet.getSheetByName("uploadlog").getRange("A2:J").getValues();
//シートデータから空行を排除する
let array = [];
for(var i = 0;i<ss.length;i++){
let rec = ss[i];
if(rec[0] == ""){
}else{
array.push(rec);
}
}
//タイトル行
var title = ["id","mail","update","filename","fileid","filesize","sharelink","downloadlink","limitdate","status"];
//JSONデータを生成する
return JSON.stringify(array.map(function(row) {
var json = {}
row.map(function(item, index) {
json[title[index]] = item;
});
return json;
}));
}
- GAS側は共通です。Web表示やシートデータの取得と返却、Access Tokenの取得などが仕事になります。
- また、アップロード後の情報を記録するのもGAS側の仕事になります。
小さいファイルのアップロード
JS側コード
async function uploadOneDrive2(){
//GAS側からAccess Tokenを取得
google.script.run.withSuccessHandler(function(token){
//FileReaderでBlob取得
var fileInput = document.getElementById("fileInput2");
var file = fileInput.files[0];
if(file == "" || file == undefined){
alert("ファイルついてないっすよ")
return;
}
var reader = new FileReader();
reader.readAsArrayBuffer(file);
//ファイル名を取得
var filename = encodeURI(file.name);
var data = reader.result;
var filesize = file.size;
//アップロード先フォルダの指定
var foldername = "テストマン"
fetch("https://graph.microsoft.com/v1.0/me/drive/root:/" + foldername + "/" + filename + ":/content?@name.conflictBehavior=rename", {
method: "PUT",
headers: {
"Authorization": "Bearer " + token,
"Content-Type": "application/octet-stream",
},
body: data
})
.then(response => response.json())
.then(json => {
console.log(json)
//webUrlとDownload URLの2つを取得
var downurl = json["@microsoft.graph.downloadUrl"];
var weburl = json["webUrl"];
var fileid = json["id"]
alert("アップしたよ")
//inputを空にする
fileInput = "";
});
}).getAccessToken();
}
- 小さいファイルをアップロードする機能はわりと簡単に実装出来ます。最大4MBまでとなります。
- アップロードが完了すると、共有リンクのURLやファイル情報を取得できます。
- 起動時にinputの中身を空にして、完了後にダイアログを閉じます。
- 今回は同じファイル名があった場合には自動リネームするようにendpointのURLに指定しています。
図:簡単なアップロードダイアログ
図:アップロード出来た
HTML側コード
<p>小サイズファイルのアップロード</p>
<input id="fileInput2" type="file" name="file2" />
<button class="favorite styled" type="button" onClick="uploadOneDrive2()">
あっぷ
</button>
- ファイルアップ用のinputとボタンを用意してるだけ。
- ボタンはuploadOneDrive2を叩くだけのシンプル仕様
大きいファイルのアップロード
今回のコードはピュアなJavaScriptとHTMLのみで構築しています。但し、こちらの方式は4MBの制限なく大容量のファイルをアップロードできるのですが、かなり複雑なコードになります。
JS側コード
async function uploadOneDrive(){
//GAS側からAccess Tokenを取得
google.script.run.withSuccessHandler(function(token){
//FileReaderでBlob取得
var fileInput = document.getElementById("fileInput");
var file = fileInput.files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
//ファイル名を取得
var filename = encodeURI(file.name);
var data = reader.result;
var filesize = file.size;
//分割するサイズとフラグメントのトータルを計算する
var fragSize = 320 * 1024;
var fragmentsTotal = Math.ceil(filesize / fragSize)
let bytesRemaining = filesize
//アップロード先フォルダの指定
var foldername = "テストマン"
//重複ファイル名の時は自動リネーム
var payload = {
"item":{
"@microsoft.graph.conflictBehavior":"rename"
}
}
//アップロード用のURLを取得(POSTで投げる)
fetch("https://graph.microsoft.com/v1.0/me/drive/root:/" + foldername + "/" + filename + ":/createUploadSession", {
method: "POST",
headers: {
"Authorization": "Bearer " + token,
"Content-Type":"application/json",
},
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(json => {
var uloadUrl = json.uploadUrl;
//分割アップロード実行
fetchman(uloadUrl, fragmentsTotal, fragSize, filesize, token, file, bytesRemaining)
});
}).getAccessToken();
}
//分割アップロード用関数
async function fetchman(uloadUrl,fragmentsTotal, fragSize, filesize, token, file, bytesRemaining){
for ( i = 0; i < fragmentsTotal; i++) {
var step = (i + 1) * fragSize
var fromman = i * fragSize
//送信ファイルサイズ調整(最後だけ端数)
var to;
if(bytesRemaining > fragSize){
to = step - 1;
}else{
to = filesize - 1;
}
bytesRemaining = Math.max(filesize - step, 0)
//指定サイズで分割アップロード(PUTで投げる)
var ret = ""
var res = await fetch(uloadUrl, {
method: "PUT",
headers: {
"Authorization": "Bearer " + token,
"Content-Type": "application/json",
'Content-Length': `${to - fromman + 1}`,
'Content-Range': `bytes ${fromman}-${to}/${filesize}`
},
body: file.slice(fromman, to + 1)
})
.then(response => {
//レスポンス情報を取得
ret = response;
return response.json()
})
.then(json => {
if (ret.status === 201 || ret.status === 200) {
//アップロードファイルの情報を取得
console.log(json["@content.downloadUrl"])
console.log(json["webUrl"])
return "OK"
}
})
}
}
- こちらのサイトのコードをベースに必要な情報を得る為のコードを追加しています。
- GASの制限を回避する為にHTML側のJSでGraph APIを叩いています。
- リジュームアップロードが必要で、最大320KB毎に分割してアップロードする必要があります。
- 前項の方式と違い、payloadとして重複時には自動リネームを明示的に指定する必要があります。
- createUploadSessionでまずは、アップロード先URLというものを取得します(このときのメソッドはPOSTである必要がある)
- アップロード先URLを手に入れたら、fetchmanという関数に渡して同期的にアップロードする必要があります。
- 一番最後のアップロードだけファイルの分割サイズを残りの端数で指定するように調整が必要です。
- アップロード先URLに対してのリクエストはPUTである必要があります。
- inputにアップしたblobデータをsliceで指定サイズで切り出してbodyに指定します
- content-lengthやcontent-rangeというヘッダでの指定方法がかなり独特なので注意。
- response.json()で変換したデータに、ダウンロードURL等が入っています。responseそのものの中にはステータスなどが入っています。
- 途中まではステータスは202なのですが、最後のアップロードだけは201, 200で返ってくるので、その際に共有リンクを取得するようにします。
図:無事にアップと自動リネーム出来た
HTML側
<body>
<input id="fileInput" type="file" name="file" />
<button class="favorite styled" type="button" onClick="uploadOneDrive()">
あっぷ
</button>
</body>
- ファイルアップ用のinputとボタンを用意してるだけ。
- ボタンはuploadOneDriveを叩くだけのシンプル仕様
図:こんな感じのアップ画面
共有リンクのURLを生成する
前述までに紹介した「downloadUrl」や「webUrl」なのですが、クリックすると分かりますが「直接ダウンロード」のURLとなっています。直リンクであるため、いきなりプレビュー無でダウンロードされる。一方でウェブ上で共有をクリックした時のURLは別途生成してあげる必要があります。こちらのリンクの場合は、OneDrive上のプレビューアで開かれることになるので、ExcelやCSVはExcel Onlineにて、動画類はstreamで開かれるようになります。
以下のコードはGAS側で生成して返すようにしています。
function driveItemInfo(itemid){
//Graph APIサービスを取得する
var service = checkOAuth();
if (service.hasAccess()) {
//tokenを取得
var token = service.getAccessToken();
//endpoint指定
var endpoint = "https://graph.microsoft.com/v1.0/me/drive/items/" + itemid + "/createLink";
//payload
var payload = {
"type": "view",
"scope": "anonymous",
"retainInheritedPermissions": false
}
//リクエストヘッダ
var headers = {
Authorization: "Bearer " + token,
}
//APIリクエスト
var response = UrlFetchApp.fetch(endpoint, {
headers: {
Authorization: "Bearer " + token
},
method: "POST",
contentType: "application/json",
payload : JSON.stringify(payload)
});
//取得した値を返す
var res = JSON.parse(response.getContentText());
var weburl = res["link"].webUrl
return weburl;
}else{
//エラーを返す(認証が実行されていない場合)
return "error";
}
}
- この共有リンクを生成するとeTagの値がカウントアップされてしまうので注意。
- createLinkに対してPOSTでリクエストを投げます。
- payloadの中身で共有範囲や権限、パスワードの設定等が可能です。
- この時生成されるwebUrlは前述のアップ時のレスポンスには含まれていないので、このリクエストで生成する必要があります。

図:共有リンク生成画面のここから生成するものを取得する
eTagの値を取得する
Microsoft Graph APIでのデータの更新や削除を行うには、ファイルに割り当てられているeTagという値をリクエストに追加しないと、実行が出来ません。このeTagの値は共有リンクの生成やファイルの上書きアップロードなど手動で変更を加えると変わるという性質があります。よって、削除や更新時には最新のeTagの値を取得してから更新を掛ける必要があります。
以下のコードはGAS側で生成して返すようにしています。
//ファイルの現在のetag値を取得する
function nowetagvalue(fileid){
//Graph APIサービスを取得する
var service = checkOAuth();
if (service.hasAccess()) {
//tokenを取得
var token = service.getAccessToken();
//endpoint指定
var endpoint = "https://graph.microsoft.com/v1.0/me/drive/items/" + fileid;
//リクエストヘッダ
var headers = {
"Authorization" : "Bearer " + token
}
//APIリクエスト
var response = UrlFetchApp.fetch(endpoint, {
headers: headers,
method: "GET"
});
//レスポンスからeTagの値を取得(ダブルコーテーション付きで取る必要がある)
var res = JSON.parse(response.getContentText());
var etagvalue = String(res["eTag"]);
return etagvalue;
}else{
//エラーを返す(認証が実行されていない場合)
return "error";
}
}
- 取得されるeTagの値は「ダブルコーテーション」が前後についた状態で取得する必要があるので、除外してはいけません。
- GETで取得して、レスポンス内にeTagというのがあるのでこれを拾って返します。
実行した結果、以下のような値が取得されます。前の数値部分は更新や共有リンク生成しても変わらないのですが、カンマより後ろの数値が更新のたびに1つずつカウントアップしていく仕様になっています。
eTag: '"{0321167B-8943-4F05-88E4-xxxxxxxx},4"',
ファイルを削除する
アップロードしたファイルのIDをスプレッドシートに記録しておき、後で削除や一定日数経過したファイルを自動削除のトリガーで処理してしまうといった場合、Graph APIでOneDrive上のファイルを削除する関数を用意する必要があります。この時、前述の通りeTagの値が必要になるので、リクエストがやや複雑です。
//対象のファイルを削除する
async function deleteDriveItem(fileid){
//Graph APIサービスを取得する
var service = checkOAuth();
if (service.hasAccess()) {
//tokenを取得
var token = service.getAccessToken();
//endpoint指定
var endpoint = "https://graph.microsoft.com/beta/me/drive/items/" + fileid
//ファイルIDから現在のetag値を取得する
var etagman = await nowetagvalue(fileid);
//リクエストヘッダ
var headers = {
"Authorization" : "Bearer " + token,
"if-Match": etagman
}
//APIリクエスト
var response = UrlFetchApp.fetch(endpoint, {
headers: headers,
method: "DELETE"
});
//レスポンスコードで判定
if(response.getResponseCode() == 204){
return "OK";
}else{
return "error";
}
}else{
//エラーを返す(認証が実行されていない場合)
return "error";
}
}
- 関数はasyncで実行する必要があります。
- 前述のnowetagvalue関数を使って現在のeTagの値を取得させます。
- リクエストヘッダにif-MatchでeTagの値を含めます。
- リクエストはDELETEメソッドで行います。
- レスポンスコードが204であれば削除成功です。
リクエスト時のeTagの値がおかしかったり付け忘れると、レスポンスコードは「409 Conflict」「412 Precondition Failed」で返ってきてしまいますので、常に最新の情報を取得するようにします。このあたりは過去にPlannerの操作でも遭遇して苦しめられました。
関連リンク
- How do I setup OneDrive Filepicker v8 for Business/Sharepoint with Javascript?
- OneDrive File Picker v8 now generally available!
- OneDrive File Picker v8 Samples
- JavaScript 対応 OneDrive ファイル ピッカー v7.2 の概要
- ファイル ピッカー - OneDrive Developer
- Opening Files with the OneDrive File Picker JavaScript SDK v7.2
- React Onedrive Filepicker Examples and Templates
- Unable to open Onedrive File Picker in Nuxt application
- Using OneDrive File Picker in a website
- OneDrive Picker Framing - JSFiddle
- File Picker v8 in iframe - Content Security Policy sporadic issue
- Box/Dropbox/Onedrive Pickers - Codepen
- Using the OneDrive File Picker in SharePoint Framework Solutions
- Saving Files with the OneDrive File Picker JavaScript SDK v7.2
- Microsoft Graph sending chat message with fetch() BadRequest
- OnedriveApp - Google Apps Script Library
- I can't able to upload image and pdf files to OneDrive via Rest API
- Microsoft Graph's createUploadSession, Content-Range Header error
- google apps script. copy files to one drive
- DriveItem の内容をアップロードまたは置換する
- アップロード セッションを使ってサイズが大きいファイルをアップロードする
- How to check file MIME type with JavaScript before upload?
- (Node.js) OneDrive -- Upload Large Files with an Upload Session
- Cannot get conflict behaviour to work with upload to sharePoint







