01サイトの URL(ホスト名とパス)から site の id を引く確認済
Graph の GET /sites/{hostname}:/{server-relative-path} で、URL しか分からないサイトの id を得る。ドライブやリストを読む後続の呼び出しは、この id を使う。
- サイトの URL を、ホスト名(example.sharepoint.com)とサーバー相対パス(/sites/docs)に分ける
- msgraph-sdk の by_site_id() に「ホスト名:/パス」を渡すと : と / が符号化され、文書の形と違う URL になる(要求の URL を出力して確かめた)。文書どおりの URL を組み、with_url() で呼ぶ
- 返った site の id、displayName、webUrl を表示し、id を環境変数 SITE_ID に控える
- 権限は委任の Sites.Read.All が最小。アプリの権限で対象を1サイトに限るなら Sites.Selected を使う(別項)
"""Resolve a SharePoint site id from its URL (hostname + server-relative path)."""
import asyncio
import os
from urllib.parse import urlparse
from azure.identity import DeviceCodeCredential
from msgraph import GraphServiceClient
from msgraph.generated.sites.item.site_item_request_builder import SiteItemRequestBuilder
SCOPES = ["Sites.Read.All"]
SITE_URL = os.environ.get("SITE_URL", "https://example.sharepoint.com/sites/docs")
def site_path_url(site_url):
# by_site_id() percent-encodes ":" and "/", so build the documented URL shape
u = urlparse(site_url)
return "https://graph.microsoft.com/v1.0/sites/%s:%s" % (u.hostname, u.path.rstrip("/"))
async def main():
cred = DeviceCodeCredential(client_id=os.environ["CLIENT_ID"], tenant_id=os.environ["TENANT_ID"])
client = GraphServiceClient(credentials=cred, scopes=SCOPES)
builder = SiteItemRequestBuilder(client.request_adapter, site_path_url(SITE_URL))
site = await builder.get()
print("id:", site.id)
print("name:", site.display_name)
print("webUrl:", site.web_url)
if __name__ == "__main__":
asyncio.run(main())実行結果(2026-09-12)
https://graph.microsoft.com/v1.0/sites/example.sharepoint.com:/sites/docsmsgraph-sdk と DeviceCodeCredential を使い、SharePoint サイトの URL からホスト名とパスを取り出して GET /sites/{hostname}:/{path} を呼び、site の id と webUrl を表示する Python を書いて。by_site_id は : を符号化するので、URL を組んで with_url で呼ぶこと。
パスは URL のとおりに書く。サイトが無いか権限が無いときは Graph がエラーを返すので、id を得られたかを確かめてから次に進む。Sites.Read.All は、サインインした人が読めるすべてのサイトのリストと文書に及ぶ。得た id はコードに直書きせず環境変数で渡す。
- 利用条件
- Microsoft Graph v1.0。委任(職場または学校のアカウント)とアプリの両方で最小権限は Sites.Read.All。個人の Microsoft アカウントは非対応。msgraph-sdk と azure-identity を使う。
- 必要なもの
- msgraph-sdk 1.62.0, azure-identity 1.25.3
- 権限
- Sites.Read.All(委任)
- 試験
- サインインの手前まで実行して確認(Microsoft 365 には接続していない)・集計・判定の部分を見本データで実行して確認(2026-09-12、Python 3.12.10 (venv) / Windows 11、msgraph-sdk 1.62.0、azure-identity 1.25.3)
- 実践編
- SharePoint / OneDrive ── SharePoint サイトのエージェントで社内資料を探す
- 出典
- Microsoft Learn「Get a site resource by path」
Microsoft Learn「Get a SharePoint Site」 - 確認日
- 2026-09-12(第 1 版)
出典の該当箇所
In addition to retrieving a site by ID you can retrieve a site based on server-relative URL path.
If you have the server-relative URL for a site resource, you can construct a request as follows:
02サイトのドキュメントライブラリと、フォルダーの中身を一覧にする確認済
GET /sites/{siteId}/drives でサイトのドキュメントライブラリ(drive)を一覧にし、名前で選んだライブラリのフォルダーの中身を $select で必要な項目だけ取る。複数ページに分かれたら @odata.nextLink をたどる。
- SITE_ID のサイトで drives を取り、name が一致するライブラリの drive id を得る
- フォルダーはパスで指定し、/drives/{drive-id}/root:/{パス}:/children の URL を組んで with_url() で呼ぶ(by_drive_item_id() では : と / が符号化されるため)
- $select で name、size、lastModifiedDateTime、webUrl、folder、file だけを取り、応答を小さくする
- odata_next_link がある間は、その URL を with_url() で呼んで次のページを取る
- folder と file のどちらが入っているかでフォルダーとファイルを分け、out_children.csv に書く
"""List a site's document libraries, then every item in one folder of a library."""
import asyncio
import csv
import os
from urllib.parse import quote
from azure.identity import DeviceCodeCredential
from msgraph import GraphServiceClient
SCOPES = ["Files.Read.All"]
LIBRARY = os.environ.get("LIBRARY_NAME", "Documents")
FOLDER_PATH = os.environ.get("FOLDER_PATH", "Reports")
SELECT = "name,size,lastModifiedDateTime,webUrl,folder,file"
def to_row(item):
kind = "folder" if item.folder else "file"
return [kind, item.name, item.size or 0, item.last_modified_date_time, item.web_url]
async def main():
cred = DeviceCodeCredential(client_id=os.environ["CLIENT_ID"], tenant_id=os.environ["TENANT_ID"])
client = GraphServiceClient(credentials=cred, scopes=SCOPES)
drives = await client.sites.by_site_id(os.environ["SITE_ID"]).drives.get()
drive = next((d for d in drives.value or [] if d.name == LIBRARY), None)
if drive is None:
print("library not found:", LIBRARY, [d.name for d in drives.value or []])
return
# documented path form; the SDK would encode ":" inside an item id
url = "https://graph.microsoft.com/v1.0/drives/%s/root:/%s:/children?$select=%s" % (
drive.id, quote(FOLDER_PATH), SELECT)
builder = client.drives.by_drive_id(drive.id).items.by_drive_item_id("root").children
page = await builder.with_url(url).get()
rows = []
while page:
rows += [to_row(i) for i in page.value or []]
if not page.odata_next_link:
break
page = await builder.with_url(page.odata_next_link).get()
with open("out_children.csv", "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f)
w.writerow(["kind", "name", "size", "modified", "webUrl"])
w.writerows(rows)
print("items:", len(rows))
if __name__ == "__main__":
asyncio.run(main())実行結果(2026-09-12)
['folder', '2026', 0, None, None]
['file', 'a.docx', 1200, None, None]msgraph-sdk で、SITE_ID のサイトのドライブ一覧から名前が一致するライブラリを選び、指定したフォルダーパスの children を $select 付きで全ページ取得して CSV に書く Python を書いて。フォルダーのパスは root:/{path}:/children の URL で指定すること。
API の表では委任の最小権限は Files.Read だが、説明は「サインインした人のファイルを読む」である。サイトのライブラリを読むため、説明が「サインインした人がアクセスできるすべてのファイルを読む」の Files.Read.All を選んだ。項目の多いフォルダーは呼び出しが増えるので、対象を絞る。
- 利用条件
- Microsoft Graph v1.0。委任は Files.Read.All(管理者の同意は不要と記載)。msgraph-sdk と azure-identity を使う。
- 必要なもの
- msgraph-sdk 1.62.0, azure-identity 1.25.3
- 権限
- Files.Read.All(委任)
- 試験
- サインインの手前まで実行して確認(Microsoft 365 には接続していない)・集計・判定の部分を見本データで実行して確認(2026-09-12、Python 3.12.10 (venv) / Windows 11、msgraph-sdk 1.62.0、azure-identity 1.25.3)
- 実践編
- OneDrive との連携 ── ライブラリやフォルダーを指定して探す範囲を絞る
- 出典
- Microsoft Learn「List available drives」
Microsoft Learn「List children of a driveItem」
Microsoft Learn「Microsoft Graph permissions reference」 - 確認日
- 2026-09-12(第 1 版)
出典の該当箇所
To list the document libraries for a site, your app requests the drives relationship on the Site.
GET https://graph.microsoft.com/v1.0/drives/{drive-id}/root:/{path-relative-to-root}:/children
Allows the app to read all files the signed-in user can access.
Allows the app to read the signed-in user's files.
03ライブラリのファイルの中身をダウンロードして保存する確認済
GET /drives/{drive-id}/items/{item-id}/content はファイルの本体を返す。応答は事前認証済みの URL への 302 リダイレクトで、msgraph-sdk の content.get() はバイト列を返すので、それを新しいファイルに書く。
- DRIVE_ID と ITEM_ID で対象を決める。項目の id は children や delta の一覧で得る
- 先に項目の name、size、file を $select で取り、file が無い(フォルダーなど)ときは止める。ダウンロードできるのは file を持つ項目だけ
- content.get() の戻り値(bytes)を、out_ を付けた新しいファイル名で書く。既存のファイルは上書きしない
- ファイル名はパスの部分を捨てて名前だけを使い、保存先のフォルダーの外に書かないようにする
- 大きなファイルを分けて取るときは、@microsoft.graph.downloadUrl の URL に Range ヘッダーを付ける(/content の要求には付けない)
"""Download one file from a drive to a new local file (never overwrites)."""
import asyncio
import os
from pathlib import Path
from azure.identity import DeviceCodeCredential
from kiota_abstractions.base_request_configuration import RequestConfiguration
from msgraph import GraphServiceClient
from msgraph.generated.drives.item.items.item.drive_item_item_request_builder import (
DriveItemItemRequestBuilder,
)
SCOPES = ["Files.Read.All"]
def target_path(name, folder="."):
# keep only the file name and add an out_ prefix; refuse to overwrite
path = Path(folder) / ("out_" + Path(name).name)
if path.exists():
raise FileExistsError(path)
return path
async def main():
cred = DeviceCodeCredential(client_id=os.environ["CLIENT_ID"], tenant_id=os.environ["TENANT_ID"])
client = GraphServiceClient(credentials=cred, scopes=SCOPES)
item_rb = client.drives.by_drive_id(os.environ["DRIVE_ID"]).items.by_drive_item_id(os.environ["ITEM_ID"])
query = DriveItemItemRequestBuilder.DriveItemItemRequestBuilderGetQueryParameters(
select=["name", "size", "file"])
item = await item_rb.get(request_configuration=RequestConfiguration(query_parameters=query))
if item.file is None:
print("not a file:", item.name)
return
data = await item_rb.content.get()
path = target_path(item.name)
path.write_bytes(data)
print("saved", path, len(data), "bytes")
if __name__ == "__main__":
asyncio.run(main())実行結果(2026-09-12)
out_報告書.docxmsgraph-sdk で DRIVE_ID と ITEM_ID のファイルの name と file を $select で確かめてから content.get() で中身を取り、out_ を付けた名前で保存する Python を書いて。フォルダーなら止め、既存のファイルは上書きしないこと。
ファイルの中身を手元に複製することになるので、秘密度ラベルや社内の持ち出しの規則に従う。事前認証済みの URL はトークンなしで中身を取れるので、ログやチャットに残さない。手元の試験はネットワークを塞いでおり、302 のたどり方は実際の応答で確かめていない。
- 利用条件
- Microsoft Graph v1.0。委任は Files.Read.All。msgraph-sdk と azure-identity を使う。
- 必要なもの
- msgraph-sdk 1.62.0, azure-identity 1.25.3
- 権限
- Files.Read.All(委任)
- 試験
- サインインの手前まで実行して確認(Microsoft 365 には接続していない)・集計・判定の部分を見本データで実行して確認(2026-09-12、Python 3.12.10 (venv) / Windows 11、msgraph-sdk 1.62.0、azure-identity 1.25.3)
- 出典
- Microsoft Learn「Download driveItem content」
- 確認日
- 2026-09-12(第 1 版)
出典の該当箇所
Returns a 302 Found response redirecting to a preauthenticated download URL for the file, which is the same URL available through the @microsoft.graph.downloadUrl property on the driveItem.
Only driveItem objects with the file property can be downloaded.
You must append the Range header to the actual @microsoft.graph.downloadUrl URL and not to the request for /content.
04リストの項目を列の値つきで読む(expand=fields と select)確認済
GET /sites/{site-id}/lists/{list-id}/items に expand=fields(select=列1,列2) を付けると、項目ごとに指定した列の値だけが fields に入る。SharePoint ではファイルもリストの項目なので、ライブラリの列の値もこの形で読める。
- expand に fields(select=FileLeafRef,DocId,Status,ReviewDue,Owner) を渡す。列名は表示名ではなく API 用の name を使う
- 列の値は ListItem.fields の additional_data(辞書)に入るので、列名で取り出す
- 結果は複数ページに分かれることがあるので、odata_next_link をたどる
- $filter で絞るときは、インデックスのある列を使う。インデックス付きの列で一度に絞れるのは1列だけ
- 隠し列の値は既定では返らない。要るときは select に列名を書く
"""Read list items with selected column values and write them to CSV."""
import asyncio
import csv
import os
from azure.identity import DeviceCodeCredential
from kiota_abstractions.base_request_configuration import RequestConfiguration
from msgraph import GraphServiceClient
from msgraph.generated.sites.item.lists.item.items.items_request_builder import ItemsRequestBuilder
SCOPES = ["Sites.Read.All"]
COLUMNS = ["FileLeafRef", "DocId", "Status", "ReviewDue", "Owner"]
def flatten(item_id, web_url, fields):
row = {"id": item_id, "webUrl": web_url}
for c in COLUMNS:
row[c] = fields.get(c)
return row
async def main():
cred = DeviceCodeCredential(client_id=os.environ["CLIENT_ID"], tenant_id=os.environ["TENANT_ID"])
client = GraphServiceClient(credentials=cred, scopes=SCOPES)
items_rb = client.sites.by_site_id(os.environ["SITE_ID"]).lists.by_list_id(os.environ["LIST_ID"]).items
query = ItemsRequestBuilder.ItemsRequestBuilderGetQueryParameters(
expand=["fields(select=%s)" % ",".join(COLUMNS)])
page = await items_rb.get(request_configuration=RequestConfiguration(query_parameters=query))
rows = []
while page:
for it in page.value or []:
fields = it.fields.additional_data if it.fields else {}
rows.append(flatten(it.id, it.web_url, fields))
if not page.odata_next_link:
break
page = await items_rb.with_url(page.odata_next_link).get()
with open("out_list_items.csv", "w", encoding="utf-8-sig", newline="") as f:
w = csv.DictWriter(f, fieldnames=["id", "webUrl"] + COLUMNS)
w.writeheader()
w.writerows(rows)
print("rows:", len(rows))
if __name__ == "__main__":
asyncio.run(main())実行結果(2026-09-12)
15 R-2026-001 Nonemsgraph-sdk で SITE_ID と LIST_ID のリスト項目を expand=fields(select=FileLeafRef,DocId,Status,ReviewDue,Owner) で全ページ読み、項目の id と webUrl と列の値を CSV に書く Python を書いて。列の値は fields.additional_data から取ること。
select の列名が違うと、その列は結果に入らない。列の name はリストの columns で確かめる(別項)。Sites.Read.All は読めるすべてのサイトに及ぶので、対象を1サイトに限りたいときは Sites.Selected を検討する。個人名などの列を CSV に出すときは保存先と保存期間を決めておく。
- 利用条件
- Microsoft Graph v1.0。委任とアプリの両方で最小権限は Sites.Read.All。個人の Microsoft アカウントは非対応。
- 必要なもの
- msgraph-sdk 1.62.0, azure-identity 1.25.3
- 権限
- Sites.Read.All(委任)
- 試験
- サインインの手前まで実行して確認(Microsoft 365 には接続していない)・集計・判定の部分を見本データで実行して確認(2026-09-12、Python 3.12.10 (venv) / Windows 11、msgraph-sdk 1.62.0、azure-identity 1.25.3)
- 実践編
- OneDrive との連携 ── ライブラリやフォルダーを指定して探す範囲を絞る
- 出典
- Microsoft Learn「List items」
Microsoft Learn「columnDefinition resource type」
Microsoft Learn「Overview of Selected permissions in OneDrive and SharePoint」 - 確認日
- 2026-09-12(第 1 版)
出典の該当箇所
GET /sites/{site-id}/lists/{list-id}/items?expand=fields(select=Column1,Column2)
When filtering on indexed fields, the service can only filter one indexed field at a time.
To list hidden field values on listItems, include the desired columns by name in your $select statement.
Within SharePoint, all files are list items, but all list items are not files.
05ドライブの差分クエリで、前回からの変更だけを取る確認済
GET /drives/{drive-id}/root/delta を最後のページまでたどると @odata.deltaLink が返る。これをファイルに保存し、次の実行でその URL を呼ぶと、前回からの追加・変更・削除だけが返る。
- 初回は /drives/{drive-id}/root/delta を呼び、@odata.nextLink がある間たどる
- 最後のページの @odata.deltaLink を out_delta_state.json に保存する。URL の中身は解釈せず、そのまま使う
- 次回は保存した deltaLink を with_url() で呼ぶ。deleted を持つ項目は手元の記録から消す
- 410 Gone が返ったら保存した状態を捨て、初回と同じ全件の列挙からやり直す
- 全件を読まずに今からの変更だけを追うなら、token=latest で deltaLink だけを得る
"""Track changes in a drive: save the deltaLink and resume from it on the next run."""
import asyncio
import json
import os
from pathlib import Path
from azure.identity import DeviceCodeCredential
from kiota_abstractions.api_error import APIError
from msgraph import GraphServiceClient
SCOPES = ["Files.Read.All"]
STATE = Path(os.environ.get("DELTA_STATE", "out_delta_state.json"))
def first_link(drive_id):
return "https://graph.microsoft.com/v1.0/drives/%s/root/delta" % drive_id
def load_link(drive_id):
if STATE.exists():
return json.loads(STATE.read_text(encoding="utf-8"))["deltaLink"]
return first_link(drive_id)
def summarize(items):
changed = [i["name"] for i in items if not i["deleted"]]
removed = [i["id"] for i in items if i["deleted"]]
return changed, removed
async def read_all(builder, link):
items, page = [], await builder.with_url(link).get()
while True:
items += [{"id": i.id, "name": i.name, "deleted": i.deleted is not None} for i in page.value or []]
if not page.odata_next_link:
return items, page.odata_delta_link
page = await builder.with_url(page.odata_next_link).get()
async def main():
drive_id = os.environ["DRIVE_ID"]
cred = DeviceCodeCredential(client_id=os.environ["CLIENT_ID"], tenant_id=os.environ["TENANT_ID"])
client = GraphServiceClient(credentials=cred, scopes=SCOPES)
builder = client.drives.by_drive_id(drive_id).items.by_drive_item_id("root").delta
try:
items, delta_link = await read_all(builder, load_link(drive_id))
except APIError as e:
if e.response_status_code != 410:
raise
# the saved token can no longer be used: enumerate everything again
items, delta_link = await read_all(builder, first_link(drive_id))
changed, removed = summarize(items)
print("changed:", len(changed), "deleted:", len(removed))
for name in changed[:10]:
print(" ", name)
STATE.write_text(json.dumps({"deltaLink": delta_link}), encoding="utf-8")
if __name__ == "__main__":
asyncio.run(main())実行結果(2026-09-12)
(['a.docx'], ['2'])
delta_link_attr Truemsgraph-sdk で DRIVE_ID のドライブに差分クエリを掛け、nextLink を最後までたどって deltaLink を JSON ファイルに保存し、次回はその URL から続ける Python を書いて。410 のときは全件から取り直し、削除された項目の数も表示すること。
差分には削除も入るので、手元の一覧から消す処理を忘れない。途中で失敗したときは deltaLink を保存しない(次回に同じ範囲を読み直す)。410 のときは全件を読み直し、手元の記録と比べ直す必要がある。
- 利用条件
- Microsoft Graph v1.0。委任は Files.Read.All(API の表の最小は Files.Read)。SharePoint と OneDrive の差分は token=latest に対応。
- 必要なもの
- msgraph-sdk 1.62.0, azure-identity 1.25.3
- 権限
- Files.Read.All(委任)
- 試験
- サインインの手前まで実行して確認(Microsoft 365 には接続していない)・集計・判定の部分を見本データで実行して確認(2026-09-12、Python 3.12.10 (venv) / Windows 11、msgraph-sdk 1.62.0、azure-identity 1.25.3)
- 出典
- Microsoft Learn「driveItem: delta」
Microsoft Learn「Use delta query to track changes in Microsoft Graph data」 - 確認日
- 2026-09-12(第 1 版)
出典の該当箇所
To check for changes in the future, call delta again with the @odata.deltaLink from the previous response.
In these cases the service returns an HTTP 410 Gone error with an error response containing one of the error codes below, and a Location header containing a new nextLink that starts a fresh delta enumeration from scratch.
you can copy and apply the @odata.nextLink or @odata.deltaLink URL to the next delta function call without having to inspect the contents of the URL, including its state token.
To retrieve the latest deltaLink, call delta with a query string parameter ?token=latest.
06Sites.Selected で、アプリが読めるサイトを割り当てたものだけに限る確認済
Sites.Selected は同意しただけでは何も読めない。サイトごとに POST /sites/{siteId}/permissions で役割(read など)を割り当てて、初めてそのサイトにアクセスできる。アプリのみと委任の両方で使える。
- Entra ID でアプリに Sites.Selected(アプリケーションまたは委任)を追加し、同意する。この時点ではどのサイトも読めない
- サイト側で POST /sites/{siteId}/permissions を呼び、roles(read、write、owner、fullcontrol のいずれか)と grantedToIdentities のアプリを指定する。この割り当ての呼び出しには Sites.FullControl.All が要る
- アプリのみで動かすときは、手元では証明書(CertificateCredential)、Azure 上ではマネージド ID でトークンを取る。スコープは .default にする
- 委任で使うときは、アプリの権限とサインインした人の権限が重なる範囲だけが使える
- やめるときは、サイトへの割り当てを削除するか、Entra ID で同意を取り消す。どちらでもアクセスは止まる
"""App-only read of a site that was assigned to this app through Sites.Selected."""
import asyncio
import os
from azure.identity import CertificateCredential, ManagedIdentityCredential
from msgraph import GraphServiceClient
# app-only tokens use .default; the app registration holds Sites.Selected
SCOPES = ["https://graph.microsoft.com/.default"]
def make_credential():
cert = os.environ.get("CERT_PATH", "")
if cert and os.path.exists(cert):
return CertificateCredential(
tenant_id=os.environ["TENANT_ID"], client_id=os.environ["CLIENT_ID"], certificate_path=cert)
# on Azure (Functions, VM) use a managed identity instead of a certificate file
return ManagedIdentityCredential(client_id=os.environ.get("AZURE_CLIENT_ID"))
def describe(site_name, lists):
lines = ["site: %s" % site_name]
lines += [" list: %s %s" % (name, url) for name, url in lists]
return "\n".join(lines)
async def main():
client = GraphServiceClient(credentials=make_credential(), scopes=SCOPES)
site_rb = client.sites.by_site_id(os.environ["SITE_ID"])
site = await site_rb.get()
lists = await site_rb.lists.get()
print(describe(site.display_name, [(x.display_name, x.web_url) for x in lists.value or []]))
if __name__ == "__main__":
asyncio.run(main())実行結果(2026-09-12)
site: docs
list: 文書ライブラリ https://example.sharepoint.com/sites/docs/Shared%20Documents
ManagedIdentityCredentialSites.Selected を割り当てたアプリで、SITE_ID のサイト名とリストの一覧を表示する Python を msgraph-sdk で書いて。CERT_PATH の証明書があれば CertificateCredential、無ければ ManagedIdentityCredential を使い、スコープは https://graph.microsoft.com/.default にすること。
アプリのみのトークンは利用者がいないため危険度が高いと文書にあり、委任が使えるなら委任が望ましいとされる。割り当てに要る Sites.FullControl.All は強い権限なので、割り当ての作業は管理者が行い、読むアプリには持たせない。リストやファイルに割り当てると継承が切れるので、一意の権限の上限に注意する。
- 利用条件
- Microsoft Graph v1.0。Sites.Selected はアプリケーションと委任の両方にある(アプリケーションは管理者の同意が必要)。サイトの割り当ては SharePoint Online 側で設定される。
- 必要なもの
- msgraph-sdk 1.62.0, azure-identity 1.25.3
- 権限
- Sites.Selected(アプリケーション)
- 試験
- サインインの手前まで実行して確認(Microsoft 365 には接続していない)・集計・判定の部分を見本データで実行して確認(2026-09-12、Python 3.12.10 (venv) / Windows 11、msgraph-sdk 1.62.0、azure-identity 1.25.3)
- 実践編
- 保管した情報で Copilot を動かす ── Copilot は閲覧権限のあるデータだけを表示する
- 出典
- Microsoft Learn「Overview of Selected permissions in OneDrive and SharePoint」
Microsoft Learn「Microsoft Graph permissions reference」 - 確認日
- 2026-09-12(第 1 版)
出典の該当箇所
Selected scopes require an explicit assignment action; an application consented for Lists.SelectedOperations.Selected would initially have no access.
Now, lists, list items, folders, and files are also supported, and all Selected scopes now support delegated and application modes.
In the delegated scenario, both the application and user permissions are calculated and then intersected
Because you can grant full control permissions to a site collection by using Sites.Selected, this requirement is necessarily high.
Application only scenarios have no user present and are considered higher risk.
The specific site collections and the permissions granted will be configured in SharePoint Online.
07フォルダー内のファイルの共有リンクを調べ、匿名・組織全体のリンクを拾う確認済
GET /drives/{drive-id}/items/{item-id}/permissions の結果で link を持つものが共有リンクで、link.scope が anonymous なら誰でも、organization なら同じテナントでサインインした人が使える。フォルダー内のファイルを順に調べ、広いリンクを CSV にする。
- フォルダーの children でファイルの id と name を集める。permissions は一覧の呼び出しで展開できないので、1件ずつ呼ぶ
- 各ファイルの permissions を取り、link を持つものだけを見る
- link.scope が anonymous か organization のものを広いリンクとし、link.type、roles、expirationDateTime と一緒に記録する
- ファイルの所有者として実行する。所有者でない人が呼ぶと、その人に当てはまる共有だけが返る
- 結果の CSV を所有者と確かめ、不要なリンクは別の手順で解除する(このコードは解除しない)
"""List anyone/organization sharing links on the files of one folder (read only)."""
import asyncio
import csv
import os
from urllib.parse import quote
from azure.identity import DeviceCodeCredential
from msgraph import GraphServiceClient
SCOPES = ["Files.Read.All"]
FOLDER_PATH = os.environ.get("FOLDER_PATH", "Reports")
BROAD = {"anonymous", "organization"}
def broad_links(file_name, perms):
out = []
for p in perms:
link = p.get("link") or {}
if link.get("scope") in BROAD:
out.append([file_name, link["scope"], link.get("type"), ",".join(p.get("roles") or []),
p.get("expirationDateTime") or ""])
return out
def as_dict(p):
link = {"scope": p.link.scope, "type": p.link.type} if p.link else None
exp = p.expiration_date_time.isoformat() if p.expiration_date_time else ""
return {"roles": p.roles, "link": link, "expirationDateTime": exp}
async def main():
cred = DeviceCodeCredential(client_id=os.environ["CLIENT_ID"], tenant_id=os.environ["TENANT_ID"])
client = GraphServiceClient(credentials=cred, scopes=SCOPES)
drive_rb = client.drives.by_drive_id(os.environ["DRIVE_ID"])
url = "https://graph.microsoft.com/v1.0/drives/%s/root:/%s:/children?$select=id,name,file" % (
os.environ["DRIVE_ID"], quote(FOLDER_PATH))
children_rb = drive_rb.items.by_drive_item_id("root").children
page, files = await children_rb.with_url(url).get(), []
while page:
files += [i for i in page.value or [] if i.file]
if not page.odata_next_link:
break
page = await children_rb.with_url(page.odata_next_link).get()
rows = []
for f in files:
perms = await drive_rb.items.by_drive_item_id(f.id).permissions.get()
rows += broad_links(f.name, [as_dict(p) for p in perms.value or []])
with open("out_broad_links.csv", "w", encoding="utf-8-sig", newline="") as fh:
w = csv.writer(fh)
w.writerow(["file", "scope", "type", "roles", "expires"])
w.writerows(rows)
print("files:", len(files), "broad links:", len(rows))
if __name__ == "__main__":
asyncio.run(main())実行結果(2026-09-12)
broad: 2
['提案書.docx', 'anonymous', 'view', 'read', '']
['提案書.docx', 'organization', 'edit', 'write', '2026-12-31']msgraph-sdk で DRIVE_ID のドライブの FOLDER_PATH にあるファイルの permissions を1件ずつ取り、link.scope が anonymous か organization の共有リンクだけを、ファイル名・scope・type・roles・有効期限で CSV に書く Python を書いて。判定は関数に分けること。
所有者でない呼び出し元には自分に当てはまる共有だけが返るので、結果が空でも安全とは限らない。SharePoint と OneDrive for Business は inheritedFrom を返さないので、親から受け継いだかどうかはこの結果では分からない。組織全体の過剰共有は管理者のレポートで確かめる。
- 利用条件
- Microsoft Graph v1.0。委任は Files.Read.All(API の表の最小は Files.Read)。
- 必要なもの
- msgraph-sdk 1.62.0, azure-identity 1.25.3
- 権限
- Files.Read.All(委任)
- 試験
- サインインの手前まで実行して確認(Microsoft 365 には接続していない)・集計・判定の部分を見本データで実行して確認(2026-09-12、Python 3.12.10 (venv) / Windows 11、msgraph-sdk 1.62.0、azure-identity 1.25.3)
- 実践編
- OneDrive との連携 ── 共有設定がそのまま Copilot の見える範囲になる
保管した情報で Copilot を動かす ── データアクセスガバナンスのレポートで過剰共有を探す - 出典
- Microsoft Learn「sharingLink resource type」
Microsoft Learn「List sharing permissions on a driveItem」
Microsoft Learn「permission resource type」 - 確認日
- 2026-09-12(第 1 版)
出典の該当箇所
Value anonymous indicates the link is usable by anyone, organization indicates the link is only usable for users signed into the same tenant.
For a non-owner caller, only the sharing permissions that apply to the caller are returned.
The permissions relationship of DriveItem cannot be expanded as part of a call to get DriveItem or a collection of DriveItems.
OneDrive for Business and SharePoint document libraries don't return the inheritedFrom property.
08ライブラリの列を点検し、必須の空欄と期限切れを洗い出す確認済
GET .../lists/{list-id}/columns で required が true の列を調べ、項目の fields と照らして空欄を拾う。日付の列が基準日より前なら期限切れとする。判定は audit() に分け、見本の JSON で確かめる。
- columns を取り、required が true で、hidden と readOnly が false の列の name を集める。name が fields のキーになる
- items を expand=fields(select=...) で取り、必須の列と日付の列だけを読む
- audit() で、必須の列が無いか空の項目と、ReviewDue が基準日より前の項目を分ける
- 基準日は引数で渡す。試験では固定の日付を使い、結果が日によって変わらないようにする
- 結果を out_column_audit.csv に書き、担当者に確認を頼む。このコードは値を直さない
"""Audit a library: empty required columns and past-due dates (report only)."""
import asyncio
import csv
import datetime
import os
from azure.identity import DeviceCodeCredential
from kiota_abstractions.base_request_configuration import RequestConfiguration
from msgraph import GraphServiceClient
from msgraph.generated.sites.item.lists.item.items.items_request_builder import ItemsRequestBuilder
SCOPES = ["Sites.Read.All"]
DATE_COL = os.environ.get("DATE_COLUMN", "ReviewDue")
def audit(items, required, date_col, today):
rows = []
for it in items:
f = it.get("fields") or {}
name = f.get("FileLeafRef", it.get("id"))
for col in required:
if f.get(col) in (None, ""):
rows.append([it.get("id"), name, "missing:" + col, it.get("webUrl")])
due = f.get(date_col)
if due and str(due)[:10] < today:
rows.append([it.get("id"), name, "overdue", it.get("webUrl")])
return rows
async def fetch(client, site_id, list_id):
list_rb = client.sites.by_site_id(site_id).lists.by_list_id(list_id)
cols = await list_rb.columns.get()
required = [c.name for c in cols.value or [] if c.required and not c.hidden and not c.read_only]
select = ",".join(["FileLeafRef", DATE_COL] + required)
query = ItemsRequestBuilder.ItemsRequestBuilderGetQueryParameters(expand=["fields(select=%s)" % select])
page = await list_rb.items.get(request_configuration=RequestConfiguration(query_parameters=query))
items = []
while page:
items += [{"id": i.id, "webUrl": i.web_url, "fields": i.fields.additional_data if i.fields else {}}
for i in page.value or []]
if not page.odata_next_link:
break
page = await list_rb.items.with_url(page.odata_next_link).get()
return items, required
async def main():
cred = DeviceCodeCredential(client_id=os.environ["CLIENT_ID"], tenant_id=os.environ["TENANT_ID"])
client = GraphServiceClient(credentials=cred, scopes=SCOPES)
items, required = await fetch(client, os.environ["SITE_ID"], os.environ["LIST_ID"])
rows = audit(items, required, DATE_COL, datetime.date.today().isoformat())
with open("out_column_audit.csv", "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f)
w.writerow(["id", "file", "problem", "webUrl"])
w.writerows(rows)
print("required:", required, "problems:", len(rows))
if __name__ == "__main__":
asyncio.run(main())実行結果(2026-09-12)
missing: 14 overdue: 2
['1', '文書01.pdf', 'missing:Owner', 'https://example.sharepoint.com/sites/docs/Shared%20Documents/01.pdf']
['2', '文書02.pdf', 'missing:Status', 'https://example.sharepoint.com/sites/docs/Shared%20Documents/02.pdf']
['2', '文書02.pdf', 'overdue', 'https://example.sharepoint.com/sites/docs/Shared%20Documents/02.pdf']
['5', '文書05.pdf', 'missing:DocId', 'https://example.sharepoint.com/sites/docs/Shared%20Documents/05.pdf']Graph の list の columns から required が true の列を取り、items の fields と照らして、必須列の空欄と ReviewDue が基準日より前の項目を一覧にする audit(items, required, date_col, today) を Python で書いて。Graph の listItem の JSON をそのまま渡せる形にし、msgraph-sdk の呼び出しは別の関数にすること。
required は列の定義で、既存の項目に値が入っている保証ではない。日付の列は ISO 形式の文字列として比べているので、時刻やタイムゾーンを含む列では比べ方を合わせる。個人名の列を出力するときは、保存先と共有の範囲を決める。結果は人が確かめてから通知する。
- 利用条件
- Microsoft Graph v1.0。columns と items の読み取りとも、最小権限は Sites.Read.All。
- 必要なもの
- msgraph-sdk 1.62.0, azure-identity 1.25.3
- 権限
- Sites.Read.All(委任)
- 試験
- サインインの手前まで実行して確認(Microsoft 365 には接続していない)・集計・判定の部分を見本データで実行して確認(2026-09-12、Python 3.12.10 (venv) / Windows 11、msgraph-sdk 1.62.0、azure-identity 1.25.3)
- 実践編
- OneDrive との連携 ── ライブラリやフォルダーを指定して探す範囲を絞る
- 出典
- Microsoft Learn「columnDefinition resource type」
Microsoft Learn「List columnDefinitions in a list」
Microsoft Learn「List items」 - 確認日
- 2026-09-12(第 1 版)
出典の該当箇所
Specifies whether the column value isn't optional.
The API-facing name of the column as it appears in the fields on a listItem.
GET /sites/{site-id}/lists/{list-id}/columns