Sharepointのリスト操作をUiPathで自動化しましょう

## Sharepointリストとは？

オンラインで複数のユーザーが共有できるリストです。Excelのような使い方もできる便利なツールです。公式ガイドからの抜粋は以下の通りです。
>Microsoft 365 のリストは、ユーザーと同僚が柔軟に情報を整理できるデータのコレクションです。
>テキスト、 通貨 、複数選択など、さまざまな種類のデータに列を追加できます。 データを効果的 に表示するビューを作成します。 リストを並べ替え、グループ化、書式設定、フィルター処理して、最も重要な情報を強調表示します。 リストには、ユーザー、リンク、画像、日付などの情報を含めることができます。 バージョンを使用して、リスト アイテムの履歴を時間の中で 追跡します。 また、リストを 自動化して作業を効率化し、時間を節約できます。

参考：[Microsoft 365 のリストとは](https://support.microsoft.com/ja-jp/office/microsoft-365-%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A8%E3%81%AF-93262a88-20ad-4edc-8410-b6909b2f59a5)


## 前提条件
本稿執筆時点の最新版を使用して動作確認をしております。
SharepointリストをUiPathから操作するには、Microsoft Office 365 アクティビティパッケージが必要です。
- UiPath Studio: v22.10.7
- 対応OS: Windowsプロジェクト
- UiPath.MicrosoftOffice365.Activities: v2.1.1

参考：[Microsoft Office 365 アクティビティ パッケージについて](https://docs.uipath.com/ja/activities/other/latest/user-guide/about-the-microsoft-office-365-activities-pack)


## ユースケース
本稿では、以下のような”経費一覧”というリストを例として、以下のユースケースを取り上げます。
- Sharepointからリストを取得する
- リストアイテムのIDを取得する
- 特定の列の値を読み取る
- リストに対してフィルタをかける
- Sharepointリストにリストアイテムを追加する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/b54e14ad-0d55-58f6-ee21-1925ae78c2e6.png)


## Sharepointからリストを取得する

Sharepoint Listから情報を取得する場合、『リストの情報を取得』を使用します。
Office 365の認証を行うため、『Microsoft Office 365 スコープ』の中に配置して使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/57c6c40a-f242-c6f7-3a00-b848d8309e43.png)

"サイトURLまたはID"にはリストのURLを指定します。ブラウザから対象のリストを開いた際のURLの内、
```"Lists/List/AllItems.aspx"```より前の部分を設定します。
"リストのタイトル/ID"にはリスト名を指定します。この例の場合、"経費一覧"を設定します。
『リストの情報を取得』の出力として、Office365SharepointList型の変数を設定します。
このOffice365SharepointList型の変数を『繰り返し（各リストアイテム）』の入力として、リストの各行に対応するリストアイテムを操作できます。

『Microsoft Office 365 スコープ』の認証設定については[公式ガイド](https://docs.uipath.com/ja/activities/other/latest/user-guide/microsoft-office-365-scope)に詳しい説明があるので、ご参照ください。


## リストアイテムのIDを取得する

各リストアイテムのIDはOffice365SharepointListItemの組み込みの値として用意されており、
```ListItem.Id```
として取得できます。
一例として、以下のような構成で各リストアイテムのIDを取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/dc3957f7-6897-284f-981a-ecde0fafc3be.png)


## 特定の列の値を読み取る

各リストアイテム（ListItem）から特定の列の値を取得する方法として、以下の3つが考えられます。
- インデックスを指定する
- 指定した文字列と列名が一致するインデックスを探索する
- ディクショナリに変換して取得する

### インデックスを指定する

例えば10のインデックスが振られた列に操作対象の値が格納されている場合、
```ListItem.Fields(10).Value``` のような構文で値を取り出せます。
しかしながら、この"10"というインデックスがリストの組み方によって異なる場合があることや、列名で指定した方が取り扱いの面で便利です。一方でリストアイテムの列名を直接指定して値を取り出すことが現状できません。

### 指定した文字列と列名が一致するインデックスを探索する

代替案として、```ListItem.Fields(x).DisplayName.Equals({列名})```を各リストアイテムの中で繰り返し、Trueが返ったときのxの値をインデックスとして取得し、```ListItem.Fields(x).Value```として対象の列の値を取り出す方法が考えられます。

ワークフローの組み方の一例を以下に示します。
図のようなワークフローを列のインデックスが必要なワークフローから呼び出すことで、必要なインデックスを取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/e6c16ff7-c670-c9b2-f75f-4dc7744d30c0.png)

ここで、変数と引数の内容は以下の通りです。

|変数名 | 型  | 説明  |
|:-|:-|:-|
| ColumnNameMatch  | Boolean  | 探索の文字列と列名が一致したら正とします  |
| Counter  | int32  | カウンター。インデックスになります  |

|引数名 | 型  | 方向|  説明  |
|:-|:-|:-|:-|
| in_ListItem  | Office365SharepointListItem  | 入力  |リストアイテム
| in_SearchColumnName  | String  | 入力  |探索対象の文字列
| out_Counter  | Int32  | 出力  |列のインデックス

上の図では分かり難いですが『複数代入』の一つ目では以下の代入処理をしています。
左辺：```ColumnNameMatch```
右辺：```in_ListItem.Fields(Counter).DisplayName.Equals(in_SearchColumnName)```


###  ディクショナリに変換して取得する

リストアイテムに格納されている値を一度ディクショナリに変換し、列名から取得する方法が考えられます。
上記の”列名が一致するインデックスを探索する”方法でも取得できますが、列やアイテム数が多い場合はこちらの方法を使用する方が高速です。
ディクショナリに変換するにはEnumerable.ToDictionaryメソッドを使用できます。
そちらの詳細については以下のようなリンクを参照してください。
[dobon.net 配列やListを基にしてDictionaryを作成する](https://dobon.net/vb/dotnet/programing/arraytodictionary.html)
[Enumerable.ToDictionary メソッド](https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.todictionary?view=net-7.0)

例えば”説明”の列の値を取得する場合、
```ListItem.Fields.ToDictionary(Function(x) x.Name, Function(x) x.Value)("_x8aac__x660e_")```
のような書き方になります。
```ListItem.Fields.ToDictionary(Function(x) x.Name, Function(x) x.Value)```の部分で```ListItem.Fields```に格納されている```Name```をキー、```Value```を値としたディクショナリに変換します。
次に、ディクショナリのキーとして```("_x8aac__x660e_")```の部分で"説明"を指定し、格納されている値を取り出します。

:::note info
```displayName```をキーとした方が使い勝手がよさそうですが、"Title"という```displayName```が重複する場合があります。その場合ディクショナリに変換する際にエラーとなってしまうため、内部名をキーとする方法をご紹介しました。
:::


## リストに対してフィルタをかける

『繰り返し（各リストアイテム）』アクティビティにおいてフィルターを設定できます。フィルタをかけることで操作対象を絞り込むことができ、処理の高速化等が期待できます。フィルタの設定方法については[公式ガイド](https://docs.uipath.com/ja/activities/other/latest/user-guide/office365-for-each-list-item)に記載があるので、参照ください。

ここでリストの列名を日本語で設定している場合の注意点があり、列の指定にはSharepoint Listが生成した内部名を入力する必要があります。Sharepoint Listの仕様として、日本語文字で設定された場合対応するUnicode文字番号に変換された文字列が内部名に設定されるようです。（列名が英語表記であれば組み込みの内部名以外であればそのままの名称が使用されるようです。

例として、”カテゴリ”が”グルメ”のものだけ抽出する場合、
```"fields/_x30ab__x30c6__x30b4__x30ea_ eq 'グルメ'"```
のような設定になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/dc34af37-5f32-39d6-1b08-7a5086f094bf.png)
内部名の取得方法については、後述の**Sharepointリスト各列の内部名を取得する**をご参照ください。


## Sharepointリストにリストアイテムを追加する

Sharepointリストにリストアイテムを追加する場合、『リストアイテムを追加』アクティビティを使用します。
リストの追加方法として、データテーブル形式のリストを1行追加する方法と、複数行を一度に追加する方法があります。以下にそれぞれの方法をご紹介します。
どちらもDatatabel型の変数を入力として使用します。

### リストアイテム（1個）の場合

1行目にリストのフィールド名、2行目以降にフィールド値を指定します。列名の列はString型、値の列はObject型にします。
"タイトル"は組み込みのフィールドであり、```displayName```が"Title"になっているので、それに合わせます。実行してみてうまく行かない場合は**Sharepointリスト各列の内部名を取得する**のセクションを参考に、```displayName```を確認してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/4c7cc5ca-b6f7-93d9-a7cf-3a3c96fa7b7c.png)
用意したDatatable型の変数を"リストアイテム（1個）"に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/aacc4142-e2fa-3308-ccfa-28fbb74ca82a.png)


### リストアイテム（複数）の場合

1行目にリストの列名、2行目以降に値を指定します。データ型が不明なものはObject型にしておくとよいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/81deadf3-78f0-553a-9dd8-550d3ab5f0a2.png)
Excelから読み取ったDatatable型の変数を使用する場合、こちらの方法の方が行列の方向が対応しているので使いやすいかと思います。
用意したDatatable型の変数を"リストアイテム（複数）"に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/13597f9c-70f3-d68a-683e-2ea825aacfef.png)


::: note
Excelデータからデータテーブルとして取り込む場合、上記のデータテーブル設定のように、
リストのフィールド名を設定する箇所がデータテーブルのヘッダー(Col1, Col2, ...)ではなく、
データテーブルの1行目(Title, Status, ...)である点に注意してください。
:::


## Sharepointリスト各列の内部名を取得する

Sharepointリスト各列の内部名を取得する方法として、以下のような方法が考えられます。
それぞれについて紹介します。
- アクティビティから取得したリストアイテムから取得する
- Microsoft Graph Explorerを使用して一覧を取得する
- Unicode文字番号を予測する

### アクティビティから取得したリストアイテムから取得する

上述の通り、Webブラウザ上で表示されている列名は```displayName```として設定されており、```ListItem.Fields(x).DisplayName```として取得できます。
一方で、各列の内部名も同様に```ListItem.Fields(x).Name```として取得できます。
従って、同じFields(x)から得られる```DisplayName```と```Name```の対応より、列名に対応する内部名を確認できます。Fields(x)の個数分だけループを回し、対応関係の一覧を出力しておくと使いやすいと思います。


### Microsoft Graph Explorerを使用して一覧を取得する

Microsoft Graph Explorerを使用し、Graph APIをコールして```displayName```と```name```を含む情報を取得します。
手順は以下の通りです。

1. [Microsoft Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)にアクセスします。
1. 右上の人型のアイコンよりログインします。<br/>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/3295ef84-3de8-29de-61cf-f7386a2dfccc.png)
1. 以下のようなクエリを実行します。```https://graph.microsoft.com/v1.0/sites/[『リストの情報を取得』に入力しているサイトURL]:/Lists```
1. Status Code 200が返ってくると成功です。<br/> Response Previewの欄にリストやサイト一覧が表示されます。操作対象のリストの名前が```"displayName"```に設定されているものを探し、対応する```"id"```をメモします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/50a30011-5b20-ced1-9d61-d0f0609eec01.png)

1. 以下のようなクエリを実行します。<br/>```https://graph.microsoft.com/v1.0/sites/[『リストの情報を取得』に入力しているサイトURL]:/lists/[4でメモしたid]/columns```
1. Response Previewの欄にリストに登録されている各列の情報が出力されます。 ```"columnGroup": "Custom Columns"```となっている箇所が独自に作成された列です。Sharepoint List上で列を作成した際の列名が```"displayName"```に設定されています。対応する列の内部名を```"name"```で確認できます。<br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/872d12d3-0f01-89b2-c843-953dabcf3348.png)

:::note info
上記のクエリでうまく行かない場合、指定しているエンドポイントが正しくないことが考えられます。Fiddlerを使用してパケットをキャプチャし、リクエストの送信先を確認してみるとよいと思います。
[Fiddler を使用して Web 要求をキャプチャする](https://learn.microsoft.com/ja-jp/power-query/web-connection-fiddler)
:::

### Unicode文字番号を予測する

日本語文字を列名に指定した場合、対応するUnicode文字番号が内部名に指定されるようです。
（入力文字列に対応するUnicode文字番号を確認するにはこちらの[Unicode文字一覧表](https://tools.m-bsys.com/ex/unicode_table.php)等をご参照ください）
例えば、"説明"は```"_x8aac__x660e_"```となります。
一案ですが、ワークフローの中で文字番号に変換する処理を加えることで、内部名をハードコードせず、```dislpayName```から直接変換して使用できることが考えられます。以下がその変換処理の一例です。
入力引数```in_String```として受け取った文字列をUnicode文字番号に変換し、内部名の指定に使用できる形に整形した上で出力変数```out_EncodedString```で返します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3334074/c49949ea-8dfb-3584-98d1-3e0ab8ef04b5.png)

上記のワークフローの各アクティビティの設定項目を再掲すると以下の通りです。

<table>
  <caption>複数代入：入力引数の文字列を文字番号に変換し、配列に変換。変数初期化</caption>
  <thead>
    <tr>
      <th>左辺</th> <th>右辺</th> <th>説明</th>
    </tr>
  </thead>
  <tr>
    <td> bytes </td> <td> System.Text.Encoding.Unicode.GetBytes(in_String) </td> <td>Byte型の配列に変換</td>
  </tr>
  <tr>
    <td> bitString </td> <td>BitConverter.ToString(bytes).ToLower </td> <td> 文字番号に変換し、さらに小文字に変換 </td>
  </tr>
  <tr>
    <td> myArray </td> <td> bitString.Split("-"c)  </td> <td> "-"で区切られた文字列を配列に変換 </td>
  </tr>
  <tr>
    <td> result </td> <td> "" </td> <td> 文字番号を格納する変数を初期化 </td>
  </tr>
  <tr>
    <td> counter </td> <td> 0 </td> <td> カウンターを初期化  </td>
  </tr>
</table>

<table>
  <caption>代入：文字番号追加</caption>
  <thead>
    <tr>
      <th>左辺</th> <th>右辺</th>
    </tr>
    <tr>
          <td>result</th> <td>result + "_x" + myArray(counter+1) + myArray(counter) + "_"</td>
  </thead>
  <tr>
</table>


:::note info
"Title"のように必ずしも変換された文字列が内部名に設定されない場合があります。変換した内部名でうまく指定できない場合は、上記のリストアイテムやGraph Explorerを使用して設定されている内部名を確認してください。
:::

## おわりに
最後まで読んで頂きありがとうございました。UiPathと一緒に素敵なリストライフを！