見出し画像

【VBA】OneDriveの1階層目~5階層目までのデータを根こそぎ一覧化する

以前の記事の応用編です。
以前の記事:【VBA】Microsoft Graph APIを使って、OneDriveのアイテム一覧を取得する|地獄の油揚げ (note.com)

今回は、OneDriveのRootフォルダ以下5階層目までのアイテム情報を根こそぎ取得し、一覧化します。

OneDriveアイテムの取得例(イメージ)

OneDriveのRootフォルダを第1階層と考えたとき、その下の子フォルダが第2階層、孫フォルダが第3階層…といった様に階層構造が形成されているかと思います。
任意の階層(本サンプルでは第5層目)を指定することで、その階層までのフォルダ・アイテム情報を全て取得し、Excelシートへ書き出しています。

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.OneDrive内のItem一覧を取得する
    Const UserID = "xxxxxxxxxxx@xxxxx.com"
    Dim Cnt As Integer '階層カウンター
    Dim asheet As Worksheet
    Dim target As String
    Dim Items() As Variant 'Item一覧を格納するための二次元配列
    Dim LastRow As Long
    Set asheet = Worksheet("Sheet1")
    Application.ScreenUpdating = False
    Cnt = 1
    Do
        Select Case Cnt
        Case 1 '1階層目(Rootフォルダのとき)
            target = UserID & "/drive/root/children"
            Items = Get_ItemsArray(target, Cnt, AccessToken)   'Get_ItemsArrayプロシージャを呼び出して、戻り値として二次元配列(アイテム情報)を受け取る
            LastRow = asheet.Cells(Rows.Count, 1).End(elup).Row
            Sheet.Range("A5", "E" & LastRow).Clear
            Selection.Rows.ClearOutline 'グループ化ボタンをクリア
            With asheet.Range("A5").Resize(UBound(Items), 5)
                .value = Items()
                .Borders.LineStyle = xlContinuous
                .Interior.Color = RGB(222, 164, 0) '濃い黄色
            End With
        Case Else '2階層目以降(子フォルダよりも深い階層のとき)
            Dim DataValue() As Variant
            Dim ChildCnt As Long
            Dim n As Long
            LastRow = asheet.Cells(Rows.Count, 1).End(xlUp).Row
            DataValue = Range("C5:E" & LastRow).value 'D列・E列の値を二次元配列DataCalueへ格納。一次元目が行数、二次元目の1がC列の値、二次元目の2がD列の値、3がE列の値
            For n = LBound(DataValue) To UBound(DataValue)
                ChildCnt = ChildCnt + DataValue(n, 2)
            Next
            Dim x As Long
            Dim num As Long
            For x = 5 To LastRow + ChildCnt - 5 '5行目~最終行(子Itemsを全て行挿入したときの最終行)を処理
                num = x - 4
                If DataValue(num, 2) = 0 Then '空フォルダのとき何もしない
                ElseIf DataValue(num, 2) <> "" And DataValue(num, 3) = Cnt - 1 Then 'Cnt階層の親階層(=取得したい階層)かつ、中身があるフォルダのとき
                    target = UserID & "/drive/items/" & DataValue(num, 1) & "/children"
                    Items = Get_ItemsArray(target, Cnt, AccessToken)
                    asheet.Range(x + 1 & ":" & x + UBound(Items)).Insert '書き出す分だけ行挿入
                    With asheet.Range("A" & x + 1).Resize(UBound(Items), 5)
                        .value = Items()
                        .Borders.LineStyle = xlContinuous
                        If Cnt = 2 Then .Interior.Color = RGB(255, 206, 51) '黄色
                        If Cnt = 3 Then .Interior.Color = RGB(255, 230, 151) '薄い黄色
                        If Cnt = 4 Then .Interior.Color = RGB(255, 245, 213) '更に薄い黄色
                        If Cnt = 5 Then .Interior.Color = RGB(255, 255, 255) '白
                    End With
                    asheet.Range(x + 1 & ":" & x + UBound(Items)).Group
                    LastRow = asheet.Cells(Rows.Count, 1).End(xlUp).Row
                    DataValue = Range("C5:E" & LastRow).value ' 行挿入したら、二次元配列DataValueも再設定
                    x = x + UBound(Items)
                End If
                If x >= LastRow Then Exit For
            Next x
        End Select
        Cnt = Cnt + 1 'Cnt(階層カウンター)を1つ増やす
'    LoopWhile Cnt < 3 'Root、子の2階層分までループする
'    LoopWhile Cnt < 4 'Root、子、孫の3階層分までループする
'    LoopWhile Cnt < 5 'Root、子、孫、ひ孫の4階層分までループする
    LoopWhile Cnt < 6 'Root、子、孫、ひ孫、ひひ孫の5階層分までループする

    asheet.Outline.ShowLevels rowllevels:=1 'グループ化された行を全て折りたたむ
    asheet.Outline.SummaryRow = xlAbove 'グループ化マークを行の上側に付ける
    Application.ScreenUpdating = True
    asheet.Range("A4:E4") = Array("名前", "サイズ(KB)", "タグ名", "子Itemの数", "階層")
    asheet.Columns("A:E").AutoFit
    
    End Sub
    
    
    '受け取ったフォルダ内のJSONデータを取得し、必要な情報を二次元配列に格納して返す
Private Function Get_ItemsArray(ByVal target As String, ByVal Cnt As Integer, ByVal token As String) As Variant
    Dim Req As Object 'XMLHTTP60
    Dim URL As String
    Dim Json As Dictionary
    Dim value As Dictonary
    URL = "https://graph.microsoft.com/v1.0/users/" & target
    Set Req = CreateObject("MSXML2.XMLHTTP") ' =New XMLHTTP60
    Req.Open "GET", URL, False
    Req.setRequestHeader "Authorization", "Bearer " & token
    Req.send
    Response = Req.responsetext
    Set Req = Nothing
    Set Json = JsonConverter.ParseJson(Response)
    ReDim Items(Json("value").Count, 4)
    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
        Items(i, 4) = Cnt
        i = i + 1
    Next
    Set Json = Nothing
    Get_ItemsArray = Items
End Function

1.~3.までの処理は、前回記事と同様なので割愛。
4.のところも、基本的には前回記事とやっていることは同じです。
Microsoft Graph APIを使ってOneDrive内のアイテム情報を取得し二次元配列に格納する処理の部分を、サブルーチン(Get_ItemsArrayファンクションプロシージャ)に切り分けましたが、それ以外は大体一緒です。)

大きな流れとしては
・承認エンドポイントへHTTPリクエストして認証コードを取得。
・トークンエンドポイントへHTTPリクエストしてトークンを取得。
・Rootフォルダのアイテム一覧をExcelシートへ出力。【第1階層の処理】
・子フォルダのアイテム数の分だけ、Excelシートの行を挿入。
 子フォルダのアイテム一覧をExcelシートへ出力
 次の行へ処理を移し、それが子フォルダであれば同様に行挿入&出力。
 全ての子フォルダに対して処理を行う。【第2階層の処理】
・孫フォルダのアイテムの数だけ、…(以下同様)【第3階層の処理】
・ひ孫フォルダのアイテムの数だけ、…(以下同様)【第4階層の処理】
これらを、任意の階層分(変数Cnt回分)繰り返す。

処理内容は単純ですが、コードが冗長になっていまいました。
補足情報は、コード内にコメントアウトして載せてあります。
もっと効率的な書き方があるかもしれませんが、とりあえず動くのでこれで良しとします。
因みに第5階層までを根こそぎ取得する場合、私の職場環境では5分くらい掛かりました(笑)
コーヒーでも飲んで気長に待ちましょうか。。





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