Azure Data FactoryでSharePointのファイルをADLS Gen2にコピー


やりたいこと

  • SharePointOnline(SPO)の特定のフォルダ内にあるファイルをゴソっとAzure Data Lake Storage Gen2(ADLS Gen2)にコピーしたい
  • MSドキュメントに1ファイルだけコピーする方法が紹介されていたので、これを参考にやってみます

Azure AD にアプリケーションを登録

Azure Portalから、Azure Active Directory > アプリの登録 > 新規登録

アプリケーションIDとテナントIDをコピーしておきましょう

続いて、このアプリケーションのシークレットを発行します
証明書とシークレット > 新しいクライアントシークレット
クライアントシークレットが作成されたら、をコピーします

アプリケーションにSPOサイトのアクセス許可を付与

SharePoint上での作業です
アプリに権限を付与するために、以下のページを開きます
<tenant_name><site_name>は環境に合わせて変更します

https://<tenant_name>.sharepoint.com/sites/<site_name>/_layouts/15/appinv.aspx

アプリIDに先程コピーしたIDを貼り付けて、参照ボタンを押すとタイトルに登録したアプリ名が自動で設定されます。アプリ ドメインリダイレクト先のURL権限の要求XML冒頭のMSドキュメントの通り入力します

信頼します

これで、Data FactoryからSPOにアクセスするために利用するアプリが作成できました

ちなみに、SharePoint REST APIに関するページを見ると、https://{site_url}/_api/web/...といった形でリクエストするURLが記載されていますが、{site_url}の部分が<tenant_name>.sharepoint.com/sites/<site_name>です

Data Factoryでパイプラインを作成

パイプラインの完成形

ここから長くなるので、先に完成形をお見せしておきます

Webアクティビティ:SPOのアクセストークンを取得

以下の通り設定します
<app_id><app_secret><tenant_id><tenant_name>は置き換えてください
ドキュメントに記載がありますが、セキュリティで保護された出力をONにしているのは、シークレットの値がログに出力されないようにするためです

  • URL: https://accounts.accesscontrol.windows.net//tokens/OAuth/2
  • メソッド: POST
  • Headers: Content-Type:application/x-www-form-urlencoded
  • 本文: grant_type=client_credentials&client_id=@&client_secret=&resource=00000003-0000-0ff1-ce00-000000000000/.sharepoint.com@
  • セキュリティで保護された出力: チェックON

Lookupアクティビティ:取得したいフォルダ内のファイル一覧を取得する

まずはSPOのデータセットを作成します
ソースデータセットで新規を押してSharePoint Online リストを選択

データセットが作成されたら、リンクサービスで新規を押します
テスト接続が成功することを確認しておきます

  • テナント: アプリケーションのテナントID
  • サービスプリンシパルID: アプリケーションID
  • サービスプリンシパルキー: アプリケーションのクライアントシークレット

リンクサービスが作成できたら、リスト名でドキュメントを選択します

データのプレビューを押して、以下のようにドキュメント一覧が表示されたらOKです
私の環境では、名前という列にファイル名、パスという列に/sites/<site_name>/Shared Documents/General/...の形式でファイル名を除いたパスが表示されていました。このファイル名とパスを表す列名は後ほど使うのでコピーしておきましょう

やっとLookupアクティビティのソースデータセットが定義できました

次に、特定のフォルダ内のファイルのみコピーしたいので、クエリを使ってフィルターします
SharePoint REST APIには色んなクエリ操作が用意されていますが、今回は$filterクエリを利用します

パスが特定の文字列ではじまるという条件にします
startswithの1つめの引数が英語ではありませんが、シングルクォートなどは必要はありません

$filter=startswith(パス,'/sites/<site_name>/Shared Documents/General/...')

startswithのような関数は他にもあるので、他の条件を試したい場合はコチラを参考にしてください

これでもう一度プレビューしてみましょう
今度は対象フォルダのファイルだけが表示されていますね

Foreachアクティビティ:ファイル一覧をパラメーターにループ処理

ここまでで半分ぐらい終わりました
まずは、Foreachアクティビティの項目を指定します。ループ対象の配列を指定するイメージです
動的なコンテンツの追加をクリックして、@activity('Lookupアクティビティの名前').output.valueを入力しましょう
Lookupアクティビティの出力結果を確認したい場合は、Lookupアクティビティをデバッグ実行するとjson形式の出力が確認できます

出力サンプル
{
    "count": 3,
    "value": [
        {
            "コンテンツタイプのID": "xxxxx",
            "更新日時": "2020-10-21T11:59:55Z",
            "名前": "xxxxx",
            "コンプライアンス資産ID": null,
            "タイトル": null,
            "Tags": null,
            "ExtractedText": null,
            "場所国地域": null,
            "場所都道府県": null,
            "場所市区町村": null,
            "場所郵便番号コード": null,
            "場所番地": null,
            "場所座標": null,
            "場所名前": null,
            "ID": 24,
            "コンテンツタイプ": "ドキュメント",
            "登録日時": "2020-10-21T11:59:55Z",
            "登録者Id": 10,
            "更新者Id": 10,
            "コピー元": null,
            "承認の状況": "0",
            "パス": "xxxxx",
            "チェックアウト先Id": null,
            "ウイルスの状態": "2628",
            "現在のバージョン": true,
            "Owshiddenversion": 1,
            "バージョン": "1.0"
        },
        {
            "コンテンツタイプのID": "xxxxx",
            "更新日時": "2020-10-21T11:59:56Z",
            "名前": "xxxxx",
            "コンプライアンス資産ID": null,
            "タイトル": null,
            "Tags": null,
            "ExtractedText": null,
            "場所国地域": null,
            "場所都道府県": null,
            "場所市区町村": null,
            "場所郵便番号コード": null,
            "場所番地": null,
            "場所座標": null,
            "場所名前": null,
            "ID": 25,
            "コンテンツタイプ": "ドキュメント",
            "登録日時": "2020-10-21T11:59:56Z",
            "登録者Id": 10,
            "更新者Id": 10,
            "コピー元": null,
            "承認の状況": "0",
            "パス": "xxxxx",
            "チェックアウト先Id": null,
            "ウイルスの状態": "387602",
            "現在のバージョン": true,
            "Owshiddenversion": 1,
            "バージョン": "1.0"
        },
        {
            "コンテンツタイプのID": "xxxxx",
            "更新日時": "2020-10-21T11:59:56Z",
            "名前": "xxxxx",
            "コンプライアンス資産ID": null,
            "タイトル": null,
            "Tags": null,
            "ExtractedText": null,
            "場所国地域": null,
            "場所都道府県": null,
            "場所市区町村": null,
            "場所郵便番号コード": null,
            "場所番地": null,
            "場所座標": null,
            "場所名前": null,
            "ID": 26,
            "コンテンツタイプ": "ドキュメント",
            "登録日時": "2020-10-21T11:59:56Z",
            "登録者Id": 10,
            "更新者Id": 10,
            "コピー元": null,
            "承認の状況": "0",
            "パス": "xxxxx",
            "チェックアウト先Id": null,
            "ウイルスの状態": "2683952",
            "現在のバージョン": true,
            "Owshiddenversion": 1,
            "バージョン": "1.0"
        }
    ],
    "effectiveIntegrationRuntime": "DefaultIntegrationRuntime (Japan East)",
    "billingReference": {
        "activityType": "PipelineActivity",
        "billableDuration": [
            {
                "meterType": "AzureIR",
                "duration": 0.016666666666666666,
                "unit": "DIUHours"
            }
        ]
    },
    "durationInQueue": {
        "integrationRuntimeQueue": 1
    }
}

Foreach内のコピーアクティビティ(SharePointのAPIを利用してADLS Gen2にコピー)

ソースデータセットを定義します

リンクサービスの定義
ベースURLhttps://<tenant_name>.sharepoint.com/sites/<site_name>/_api/web/の形式(末尾の"/"を忘れないようにしましょう) 1

ここまで進めると、バイナリのデータセット定義が以下のようになっていると思います

ファイルのフルパスをパラメーターで指定したいので、パラメーターを追加します

そのパラメーターを使って相対URLを動的に指定します

相対URLの設定
GetFileByServerRelativeUrl('@{uriComponent(dataset().FilePath)}')/$value

GetFileByServerRelativeUrlはSharePoint REST APIのメソッドで相対URLを指定してファイルをダウンロードします。GetFileByServerRelativeUrlメソッドの引数に指定している@{uriComponent(dataset().FilePath)}の部分は、FilePathというパラメーターをData Factoryの関数uriComponentを使ってエンコードしています

Copyアクティビティの残りのソース定義を設定していきます
先程データセット定義で作成したFilePathというパラメーターがここに表示されています。Foreachでループ中の要素が持っているファイル名とファイルパスの情報を使ってファイルのフルパスを動的に指定します
動的なコンテンツの追加をクリックして、@{item().パス}/@{item().名前}と入力しましょう
ループ中の要素にアクセスする際はitem()を使います
item()の後ろには、SPOのデータをプレビューした時にコピーしたファイル名とパスを表す列名を使います

要求メソッド追加のヘッダーはドキュメント通りです

  • 要求メソッド: GET
  • 追加のヘッダー: @{concat('Authorization: Bearer ', activity('<Web-activity-name>').output.access_token)}

シンクデータセットを定義します

こちらはADLS Gen2のデータセット定義で特に注意すべきポイントもないので詳細は割愛します。SPOのファイル名をそのまま使いたいケースがほとんどかと思うので、ソースデータセットと同じように、FileNameのようなパラメーターを追加して、そのパラメーターに@item().名前を渡しておきましょう

さいごに

もっと簡単にできるのかと思っていましたが、思ったより苦戦しました…
他に良い方法があれば教えて下さいm(_ _)m


  1. 以下の説明を読むと、ベースURLと相対URLの間の"/"はData Factoryがつけてくれそうなもんですが、つけてくれなかったのでベースURLの末尾につけました