見出し画像

【VBA】Microsoft Graph APIを使って、OneDriveのアイテム一覧を取得する

Microsoft Graph API(REST API)を使って、OneDriveの中身一覧を取得するコードのサンプルです。
個人アカウントを使うOneDriveでなく、組織アカウントを使うOneDrive for Bussinessを想定しています。

【概要】
・Azure ADでアプリを事前登録しておく。
・承認エンドポイントへHTTPリクエストして認証コードを取得。
・トークンエンドポイントへHTTPリクエストしてトークンを取得。
・Microsoft Graph APIを使ってOneDriveリソースを取得。
・OneDriveリソース(JSON形式)全文をテキストファイルへ出力。
・OneDriveリソースのアイテム一覧(名前・サイズ・タグ名)を
 Excelシートへ書き出す。

Microsoft Graph APIを使うことで、OneDrive(つまりShare Point)内のリソースに様々な処理を加えることができます。
得られるメリットは大きい一方で、コードを実装するのは結構大変です。OneDriveに限らず、Microsoft Graph APIを扱うときはそれなりの覚悟で臨んだ方が良いでしょう。
VBAからOneDrive操作するサンプルをネット上で殆ど見かけなかったので珍しいかも知れません。必要としている人に届けば嬉しいです。

Private Sub Get_OneDrive_Items()
   '1.AzureAD 各設定値
    Const TenantID = "h23409d4-kaer-46h3-93g4g5g58509"
    Const ClientID = "g5kj8839-ffu5-85k9-4hv6-uunnf87gf6r4"
    Const Scope = "Sites.ReadWrite.All  '「openid」「Sites.ReadWrite.All」「User.ReadWrite」"
    Const RedirectURL = "https://www.google.com"
    Const Auth_EndPoint = "https://login.microsoftonline.com/" & TenantID & "/oauth2/v2.0/authorize"  'OAuth 2.0の承認エンドポイント(v2)
    Const Token_EndPoint = "https://login.microsoftonline.com/" & TenantID & "oauth2/v2.0/token"  'OAuth 2.0トークンエンドポイント(v2)
    
    '2.アクセスコード取得
    Dim Param As String
    Param = "response_type=code" & _
                    "&client_id=" & ClientID & _
                    "&scope=" & Scope & _
                    "&redirect_uri" & RedirectURL
    Dim Req As Object 'XMLHTTP60
    Dim Response As String
    Dim S As Integer
    Dim E As Integer
    Dim code As String
    Set Req = CreateObject("MSXML2.XMLHTTP") '=New XMLHTTP60
    Req.Open "GET", Auth_EndPoint & "?" & Param, False
    Req.send
    Response = Req.responsetext
    S = InStr(Response, "code=")
    E = InStr(S + 5, Response, "&session")
    Code = Mid(Response, S + 5, E - S - 5)
    
    '3.アクセスコードを使ってトークン取得
    Dim Req As Object 'XMLHTTP60
    Dim AccessToken As String
    Set Req = CreateObject("MSXML2.XMLHTTP") '=New XMLHTTP60
    Param = "grant_type=authorization_code" & _
                    "&response_type=code" & _
                    "&client_id=" & ClientID & _
                    "&scope=" & Scope & _
                    "&redirect_uri=" & RedirectURL & _
                    "&code=" & code
    Req.Open "POST", Token_EndPoint, False
    Req.setRequestHeader "Content-type", "application/x-www-form-urlencoded"
    Req.send Param
    Response = Req.responsetext
    If InStr(Response, "AADSTS54005") > 0 Then '低確率で出るエラー
        Debug.Print "トークンエンドポイントのレスポンスが正常に受信出来ませんでした。"
        MsgBox "OAuth2 認証コードは既に引き換え済みです。" & vbCrLf & "暫く経ってから、もう一度試してください。"
        End
    End If
    Dim Json As Dictionary 'Object
    Set Json = JsonConverter.ParseJson(Response)
    AccessToken = Json("access_token")
    Set Json = Nothing
    
    '4.トークンを使って、Microsoft Graph APIを使用
    Const RootURL = "https://graph.microsoft.com/v1.0"
    Const UserID = "xxxxxxxxxxx@xxxxx.com"
    Const RootResource = "/users/" & UserID
    Const ResourceTarget = "/drive/root/children" 'ルートフォルダ情報を取得
    Const RequestURL = RootURL & RootResource & ResourceTarget
    Req.Open "GET", RequestURL, False
    Req.setRequestHeader "Authorization", "Bearer " & AccessToken
    Req.send
    Response = Req.responsetext
    Set Req = Nothing
    
    '5.取得したJSON形式のレスポンスをテキスト出力
    Dim Text As String
    Set Json = JsonConverter.ParseJson(Response)
    Text = JsonConverter.ConvertToJson(Json, Whitespace:=2)
    datFile = ActiveWorkbook.Path & "\ResponseText.txt"
    Open datFile For Output As #1
    Print #1, Text, vbCrLf
    Close #1
    Debug.Print "data.txtに書き出しました"
    
    '6.シートに書き出す
    Dim Items() As Variant
    ReDim Items(Json("value").Count, 3)
    Dim value As Dictionary
    i = 0
    For Each value In Json("value")
        Items(i, 0) = value("name")
        Items(i, 1) = value("size")
        Items(i, 2) = Mid(value("eTag"), 3, 36) 'eTag36文字固定のためこれでOK \w{8}-\w{4}-\w{4}-\w{4}-\w{12}$
        On Error Resume Next 'Itemがファイルのとき(フォルダで無い時)エラー発生するため
        Items(i, 3) = value("folder")("childcount") 'value("folder")(1)
        On Error GoTo 0
        i = i + 1
    Next value
    Set Json = Nothing
    Dim asheet As Worksheet
    Dim LastRow As Long
    Set asheet = Worksheets("sheet1")
    LastRow = asheet.Cells(Rows.Count, 1).End(xlUp).Row
    asheet.Range("A5", "D" & LastRow).Clear
    asheet.Range("A5").Resize(UBound(Items), 4).value = Items()
End Sub

コード簡略化のため、OneDriveのRootフォルダ(第一階層)だけに絞ってアイテム一覧を取得しました。具体的には、ファイル名とフォルダ―名、サイズ、タグ名を取得しています。
サンプル内のテナントID(TenantID)、クライアントID(ClientID)、ユーザーID(UserID)はダミーです。

実際は組織アカウント毎に仕様が異なると思うので、細かい修正は必須だと思います。(今回の記事は、コピペだと動きません。ちゃんと内容理解しないと実装は厳しいかも。)


【Microsoft Graph APIのおさらい】
コードとは直接関係ありませんが、前提知識をおさらいします。
Microsoft Graphは、Azure ADやMicrosoft 365などのクラウドサービスのリソースにアクセスするためのWeb APIです。このWeb APIは、リソースへのアクセス権を安全に委譲するために保護されており、ユーザーは直接APIを呼び出すことができません。Azure AD登録済みアプリを使うことで初めて呼び出せます。
Azure AD(Azure Active Directory)とはリソースへのアクセス権限を管理しているサービスです。Azure AD上でアプリを登録しておくと、そのアプリがユーザーに代わってリソースへアクセスし様々な処理を行ってくれます。
アプリの動きをもう少し見ていきます。
アプリはOAuthという仕組みを使ってアクセス権を受け取り、アクセス権を行使することで「保護された」リソースへ接続できるようになります。具体的には、「トークン」という形で認可サーバーから発行されたアクセス権を受け取り、アプリがリソースへアクセスする(何らかの要求を出す)ときには必ずトークンが一緒に送り込まれています。アプリ側から送られるトークンと、リソース側で保持しているトークン情報を照合することで、ユーザー本人の操作かどうかを見極めている訳です。
 外部参考:一番分かりやすい OAuth の説明 - Qiita
アプリがリソースへアクセスする際の、このトークンを用いたやり取りを規格化したものがOAuthという仕組みです。そしてOAuthにも、OAuth1.0, OAuth2.0…とバージョンがあります。Microsoftではバージョン毎に公式リファレンスが存在していて、似て非なるリファレンス同士で混乱しがちです。まず自分の組織がどの認可・認証方式を を使っているか確認しましょう。Azure ADのエンドポイント一覧から確認可能。
舐めて掛かると大変な目に合います。
私の会社はOAuth2.0でした。

少し脱線しましたが、Azure AD上でアプリを登録すると、リソースへのアクセス元となるクライアントID(アプリケーションIDともいう)が自動生成されます。ユーザーの代わりにアクセス処理を実行するアプリを判別するための識別子です。
登録したアプリに対して適切な権限や動作を付与することで、リソースへの接続準備(APIを呼び出す準備)が整います。
Azure AD上での操作手順は割愛。


【コードの補足】
Microsoft Graph APIを呼び出す流れは、ざっくり次の通りです。
a. 認証エンドポイントへGET送信して、認証コードを取得
b. トークンエンドポイントへPOST送信して、アクセストークンを取得
  このときa. で取得した認証コードをリクエストボディに含めること
c. Microsoft Graph API呼び出し用のURLにリクエストを送信(取得の場合はGET送信)し、リソースへアクセス
 このときb. で取得したアクセストークンをリクエストヘッダーに含めること
これでAPIが呼び出せます。
ここから、サンプルの内容を順に見ていきます。

1.Azure AD各設定値のところ
Azure ADのアプリ画面から、アプリ情報とエンドポイントURLを確認可能。
・テナントID:組織共通のID
・クライアントID:Azure ADで登録したときにアプリへ割り当てられるID。
         別名アプリケーションIDとも言う。
・スコープ:アプリのアクセス範囲を規定するもの。
・リダイレクトURI:アプリが正しく承認されたときに承認コードやトークンを受け取る場所。
・この他、シークレットIDなどの項目があります。私の職場環境では不要だったのでコードに含めていませんが、必要に応じて追加してください。
・認証エンドポイント:認証コードを取得するためのリクエスト先URL。
・トークンエンドポイント:トークンを取得するためのリクエスト先URL。

2.アクセスコード取得のところ
認証エンドポイントに対して、適切なパラメータをHTTPリクエストボディに含めてGET送信することで、アクセスコードが得られます。上記例では、認証エンドポイントURLとパラーメーターを「?」で繋げた文字列をGET送信しています。GET送信するとリダイレクト後のURLが結果として返ってくるため、リダイレクト後のURL内の"code="に続けて含まれる認証コード文字列を取得しましょう。
因みに、一番手軽なGET送信の方法は、繋げた文字列をブラウザのアドレスバーに叩き込むことです。ブラウザ側でリダイレクトが始まるので、リダイレクト後のURLをアドレスバーから確認できるかと思います。
上記例では、"response_type"、"&client_id"、"&scope"、"&redirect_uri"の4つをパラメーターに設定し、文字列を「&」で繋げています。
組織ごとに細かな違いがあると思うので、実装の際は、GET送信して返ってきたレスポンスのメッセージを見ながら微調整が必要になると思われます。GET送信したパラメーターの項目が足りてなければ、大体リソース側からちゃんとエラーレスポンス(○○のパラメーターが足りません等)が返ってくるので、エラーメッセージに従います。
因みにresponse_type=code はOAuth2.0の仕様で定義されている認証コードを要求する値。
scopeとredirect_uriにはアプリで付与した設定値を記載。

3.アクセスコードを使ってトークン取得のところ
今度はアクセストークンエンドポイントに対してPOST送信します。リクエストボディに含めるパラメーターには、先ほど取得した認証コードを含めます。POST送信すると、結果としてHTTPレスポンスが返ってきます。正常に処理されればJSON形式のレスポンス内に"access_token"キーが含まれているので、キーに対応する値(文字列)を取得しましょう。
 ※JSON解析には「VBA-JSON」ライブラリを使用しました。
参考:GitHub - VBA-tools/VBA-JSON: JSON conversion and parsing for VBA
色々考えたもののVBA-JSONを使うのがベストと判断しました。上記リンクから.basをダウンロードして、モジュールへインポートすれば導入完了。
但しVBA-JSONを使うので「Microsoft Scripting Runtime」の参照設定が必要です。


4.トークンを使ってMicrosoft Graph APIを使用のところ
Microsoft Graph APIを使う際は、要求毎にHTTPリクエストヘッダーでアクセストークンを提示する必要があります。
なおResourceTarget = "/drive/root/children"で、取得対象をrootフォルダ内のアイテムに設定しています。rootの部分をフォルダID(フォルダのタグ名)に変えれば、指定したフォルダ内のアイテムを取得できます。
公式リファレンスには色々なパス指定方法が載っているので、あくまで一例ということで。
HTTPリクエストのメソッドは、GETで取得、POSTで送信、PUTで編集・上書きなので、実行したい操作に応じて使い分ければOK。ここではアイテム一覧を取得したいのでGETとしています。

5.と6.では、取得したJSON形式のリソースを整形◦解析しています。
最初はJSONデータを全てテキストに書き出してどんな情報が取れたのかをまず確認し、欲しい値がどれか、どのキーを指定すれば欲しい値が特定できるかを探せば良いでしょう。


【作成経緯】
私の会社はアプリ版OneDriveのインストール禁止、OneDriveフォルダをネットワークドライブに割当てることも禁止なので、OneDriveはブラウザ表示するしかありません。
ブラウザ表示だと操作性が悪いので、APIを呼び出して直接データ操作してやろうと思いました。職場のOneDriveが使いづらくて何とか改善したい!って人は多数いるんじゃないかと思いますが、ノンプログラマーにとってMicrosoft Graph APIがそんな気軽に使える代物じゃないので悩ましいですね。





この記事が気に入ったらサポートをしてみませんか?