Oh! 365 blog

主に SPO や Teams に関する情報を発信しています。

SharePoint Online の PnP PowerShell サンプル (たくさんのリスト アイテムを作成)

以前 SharePoint Online 管理シェルで、テナント管理の観点で使うコマンドの一覧を記事にしましたが、今回は検証環境づくりでよく使うものをまとていきます。
CSOM でしか対応できない場合を除いて、基本的には PnP のコマンドを使っています。

今回はたくさんのアイテムが存在するリストを作成するサンプルです。作成したビューがリストビューのしきい値に抵触しないかなど、主にビューの検証をしたいときに、5000 件、20000 件などのたくさんのアイテムが存在するリストで動作検証をしたいときがあるかと思います。そういう時にこのサンプルを実行して置けば、簡単に環境が出来上がります。実行する時間帯やサーバーの負荷で前後する可能性があるため、あくまで参考値となりますが以前テスト用に 50000 件のアイテムを作成した際は 30 分程度で作成できました。

注意点
これらは開発環境でたくさんアイテムや列を作って動作確認をしたい場合等を想定してます。
短時間に繰り返しサーバーに対して要求を行うとリクエストがブロックされたり調整されたりする可能性があるので、あくまで検証用途での使用を想定している点にはご留意ください。

たくさんのアイテムを作成する

# 既存のサイトに接続
Connect-PnPOnline -Url <strong>https://tenant.sharepoint.com/sites/testsite</strong> -Interactive

# カスタムリストを作成
New-PnPList -Title <strong>"LargeCustomList" </strong>-Url <strong>lists/largecustomlist</strong> -Template GenericList

# 作成したリストに 20000 アイテムを作成
$batch = New-PnPBatch

for($i=1; $i -lt 20000; $i++)
{
    Add-PnPListItem -List <strong>"LargeCustomList" </strong>-Values @{"Title" = "Test Item$($i)"; } -Batch $batch
}
Invoke-PnPBatch -Batch $batch

※ サイトの URL や リスト名、リスト URL 等は自由に置き換えてください。

関連情報
PnP PowerShell を使用して繰り返し処理する際、使用するコマンドが Batch に対応している場合、サーバーの負荷を軽減するために Batch を使用することが推奨されています。サーバー側への要求数が減るので、スロットリングのリスクや、サーバーの負荷を高めるリスクを軽減する効果が期待できます。

pnp.github.io

その他のコマンドは以下です。

New-PnPList (PnP.Powershell) | Microsoft Docs
New-PnPBatch (PnP.Powershell) | Microsoft Docs
Add-PnPListItem (PnP.Powershell) | Microsoft Docs

同じように PnP のコマンドを使えばたくさんのアイテムを作成しつつ、カスタムの列、カスタムの値など色んなバリエーションで環境を作ることが出来ます。
普段は必要な時にスクリプトを作成したものは使い捨てることが多いですが、昔使ったスクリプトが役立った機会もあるので、手元にストックしているものを記事にしていきます。
もしこういうサンプルがあったらいいなというものがあれば是非コメントいただければ幸いです。

SharePoint Online でサイトの権限一覧を取得する

SharePoint Online において、サイトの権限の一覧を取得したいシーンがよくあります。従来、監査目的で、適切に権限の管理ができているかを見るために、CSOM 等の API を使用してサイトやライブラリ、ファイル単位で権限を一覧化する場面が多々ありましたが、SharePoint Online の標準機能のレポート機能で、サイトコレクション内の固有の権限が付与されたファイルの一覧を CSV ファイルに出力する機能が提供されるようになったため、備忘録として残しておきます。

できること

手元の環境で動作を検証してみたところ、今回紹介するレポート機能では以下のことができそうです。

  • サイトコレクションの管理者のみ実行可能
  • 固有の権限が付与されたサイトやフォルダ、ファイルが対象に、権限の一覧を CSV ファイルに出力可能
  • 外部ユーザーも対象となる
  • サイトコレクション単位で出力 (トップサイトだけでなく、配下のサブサイトも対象になる)

以下のようにサイト、ライブラリ、ファイルなどの単位で固有の権限が付与されたコンテンツの権限一覧が出力されます。SharePoint グループ経由で権限を付与している場合は、SharePoint グループ名のまま表示されるので、SharePoint グループに所属しているユーザーの確認は別途必要になりますね。

f:id:keisuke-blog:20210418124146p:plain
出力されたレポートの例

標準機能でこのレベルのレポートが出てきたのはすごいですね。サイトコレクション単位で固有の権限が付与されたコンテンツの一覧が CSV ファイルに出力できるので、多くの場面ではこのレポートが十分使えそうです。テナント全体でとか、複数のサイトコレクションを一括で、というシナリオだとスクリプトの実装を検討する必要がありますが、少ないサイトであれば十分利用できそうです。残念ながら、このレポートは画面操作で出力する必要があるので、API から取得するということはできなさそうです(2021年 4 月時点)スクリプトを 1 から実装するのは大変ですが、Power Automate が使える環境であれば、サイトコレクションの一覧だけ取得 UI Flow で自動化してしまうのはアリかもしれません。

公開情報は以下です。
docs.microsoft.com

機能の説明も引用しておきます。

特定の SharePoint サイトまたは OneDrive 上のすべての一意のファイル、ユーザー、アクセス許可、およびリンクの CSV ファイルを作成できます。 これにより、使用されている共有方法や、ファイルやフォルダーがゲストと共有されているかどうかを確認することができます。 レポートを実行するには、サイト管理者である必要があります。

レポートの出力手順

以下の手順で利用できます。

1. サイトコレクションの管理者でサイトの利用状況ページにアクセスします。

f:id:keisuke-blog:20210418121954p:plain

2. ページ下部にある "外部ユーザーと共有” の "レポートを実行" というボタンを押します。
f:id:keisuke-blog:20210418121854p:plain

※ 表示されていない場合、権限が足りないことが想定されます。
※ 検証では、フルコントロールを与えても表示されませんでした。アクセス権の取得対象がサイトコレクション全体なので、サイトコレクションの管理者が必要と考えられます。


3. レポートの出力先フォルダを指定します。
f:id:keisuke-blog:20210418123348p:plain

※ 今のところ、サイト作成時にデフォルトで存在する "ドキュメント" という名前のドキュメント ライブラリのみが選択できるようです。
※ ポップアップの上部に [+新規] というボタンもあるので、既存のフォルダがない場合はこのポップアップ上からも新規フォルダを作成可能です。

4. 保存ボタンを押すと、指定したフォルダにレポートが出力されます。
※ 少し時間がかかります。手元の環境では数分で完了しましたが、コンテンツが多いとさらに時間が必要かもしれません。

日本語が文字化けする場合

日本語のファイル名が含まれていると、そのまま Excel で開くと以下のように文字化けしてしまいました。

f:id:keisuke-blog:20210418124617p:plain

Excel にインポートするときに文字コードを指定するか、一旦メモ帳などで開いてから文字コードを変換することで回避可能です。手元の環境では以下の手順で回避できることを確認しました。

1) 出力された CSV をメモ帳で開く。
2) 文字コードを [ANSI] または [UTF-8(BOM 付き)] に変更して保存しなおす。

OneDrive の場合

OneDrive for Business の場合は、"OneDrive の設定" から、利用できそうです。

f:id:keisuke-blog:20210418125907p:plain

おわりに

サイトの利用状況の公開情報もありますが、具体的な内容ではないですが、分析機能が強化されたと記載がありました。"外部ユーザーと共有” の項目に表示されているファイルの一覧がレポートされるのかと勘違いしそうですよね。

SharePoint サイトの利用状況データを表示する - SharePoint

[ 外部ユーザーと共有] レポートをエクスポートして、さらに分析やレポートを行うことができるようになりました。 サイト所有者と管理者は、ページの右下隅にある [ レポートの実行] ボタンをクリックして、このデータを CSV ファイルに抽出できます。

今回の投稿は以上です。

Send-PnPMail で no-reply@sharepointonline.com からメールを送付する

SharePoint Online 上からサイトやコンテンツを共有したときや、通知機能などで SharePoint Online の SMTP サーバーからメールが送付される場合、差出人が no-reply@sharepointonline.com になります。このメールアドレスを使用したテストを行いたい場合に、PnP の Send-PnPMail コマンドが便利です。もちろん、送信元を SharePoint Online としてメールを送付したいシナリオでも使えますが、SharePoint Online からのメールが受信できない場合や来ないなどに、単なる遅延なのか、組織のフィルタの影響かなどの切り分けにも使えたので、備忘録として残しておきます。

docs.microsoft.com

以下のように、送信元にしたいサイトに接続して、Send-PnPMail コマンドを実行するだけで使えます。

Connect-PnPOnline -Url https://contoso.sharepoint.com/sites/TeamSite
Send-PnPMail -To user01@contoso.onmicrosoft.com -Subject "This is subject" -Body "This is body"

コマンドを実行すると、以下のように no-reply@sharepointonline.com からメールが送付されるので、メールが受信できないときなどに、まずこの送信元からのメールを受信できるのかなどの切り分けになります。

f:id:keisuke-blog:20210418112747p:plain
Send-PnPMail コマンドの実行で配信されたメール

記事の引用となりますが、同じテナント内のユーザーのみにメールを送付することができます。

Sends an e-mail using the SharePoint SendEmail method using the current context. E-mail is sent from the system account and can only be sent to accounts in the same tenant

試しに異なるドメイン (テナント) を指定して実行すると、以下のエラーメッセージが表示されます。

Send-PnPMail : The e-mail message cannot be sent. Make sure the e-mail has a valid recipient.


Google 検索してみると、2017 年の記事ですが TechNet 上で質問している方もいました。REST API を使うアイデアが出ていましたが、今なら PnP コマンドで手軽に実行出来て良いですね。

メール送信の送信元を、【no-reply@sharepointonline.com】で送信したい。

今回の投稿は以上となります。

【SPO】SharePoint Online の予定表リストに祝日を重ねわせる方法 (PowerShell スクリプトを利用)

会社では Microsoft Teams のチームのタブに SharePoint Online の予定表リスト (予定表アプリ) を追加し、休暇の管理として使っています。ふと祝日が考慮されていないことに気づき、祝日の予定表をインポートする方法を探しました。

しかし、Exchange Online や Outlook の予定表を重ねれば良いかと思っていましたが、どうやらそれは実現できないようでした。

docs.microsoft.com
answers.microsoft.com

SharePoint 上の予定表リスト上に、別の予定表リストを重ね合わせすることはできるため、祝日のみがイベントとして保存されている予定表リストがあれば実現できるのですが、Google CalendarOutlook の予定表などをインポートすることができないようでした。

1 年間で 10 数回の祝日しかないので手入力でイベントを作ればいいかとも思いましたが、何度も行う可能性も考えるととても面倒になったので、既存の予定表リストに祝日のイベントを作成する PowerShell スクリプトを書いてみました。せっかくなので備忘録として残しておきます。

記事のスクリプトで実現できること

指定した予定表リストに祝日のイベントを作成します。祝日のイベントを追加した予定表リストを、メインの予定表リストに重ね合わせることで、祝日のイベントを反映させます。

f:id:keisuke-blog:20210126221738p:plain
予定表リストに祝日用の予定表を重ね合わせている

スクリプト

PnP PowerShell を使用した PowerShell スクリプトです。

param(
    [parameter(Mandatory=$true,Position=0)]
    [String]$webUrl,
    [parameter(Mandatory=$true,Position=1)]
    [string]$calendarListName
)

# Install PnP PowerShell Module.
### Install-Module SharePointPnPPowerShellOnline

# Get Japan holidays list (CSV) from Cabinet Office
$holidayList = Invoke-WebRequest -uri "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"
$holidayListUTF8 = [System.Text.Encoding]::GetEncoding("Shift_JIS").GetString( [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetBytes($holidayList.Content))

Connect-PnPOnline -url $webUrl -UseWebLogin

$currentYear = Get-Date -Format "yyyy"
foreach($line in $holidayListUTF8 -split "`r`n")
{
    if($line.StartsWith($currentYear))
    {
        [datetime]$date = $line.split(",")[0]
        $title = $line.split(",")[1]
        $startDate = $date.ToString("yyyy/MM/dd 00:00")
        $endDate = $date.ToString("yyyy/MM/dd 23:59")
        
        $result = Add-PnPListItem -list $calendarListName -Values @{ "Title"=$title; "EventDate"=$startDate; "EndDate"=$endDate; "fAllDayEvent"=$True}

        Write-Host $title, $date.ToString("yyyy/MM/dd")
    }
}

祝日について

内閣府の HP にその年の祝日が公開されています。また、CSV ファイルとしてダウンロード可能です。PowerShell スクリプト内で Invoke-WebRequest コマンドを使用して CSV ファイルから祝日の一覧を取得しています。2021 年のようにオリンピックの影響で祝日が変則的になる可能性があるため、内閣府の HP の祝日を参照するのが最も良いと判断しました。

スクリプトにより追加される祝日について

内閣府CSV では、1955 年から現在 (今年なら 2021 年) までの祝日の一覧が格納されています。過去の祝日は不要取りこまないよう、 PowerShell を実行する年の祝日のみ作成するようにしています。

例) 2021 年に実行すると 2021 年の祝日のみをイベントとして追加する。

スクリプト内の $currentYear 変数を翌年 (2022) に変更すれば翌年の祝日のみを取り込んでくれます。例えば 2021 年のうちに内閣府の公開している CSV ファイルに翌年 (2022) の祝日が反映されていたら、変数 2022 に変えて実行すれば、2022 年の祝日のみが追加されます。内閣府の HP にアクセスし、翌年の祝日が公開されていないかを確認してください。
CSV のリンクが変わってしまったら、修正するか、外部サイトなどで公開されているものを CSV ファイルとして手動で保存するのもありです。

実行方法

1) 事前に祝日のイベントを作成する新規予定表リスト (例. 祝日用カレンダー) を作成しておきます。

f:id:keisuke-blog:20210117003508p:plain

2) PowerShell を起動します。
3) PnP PowerShell モジュールをインストールします。

Install-Module SharePointPnPPowerShellOnline


4) 上述のソースコードをメモ帳などにコピーして ps1 ファイルとして保存します。
(例. AddJPNHolidays.ps1)

5) 以下のように実行します。

.\AddJPNHolidays.ps1 -webUrl https://<tenant>.sharepoint.com/sites/sitename -calendarListName "祝日用カレンダー"

※ 実行時に資格情報を求めるポップアップが起動するため、指定したサイトにアクセス権を持つユーザーの資格情報を入力します。

  • -webUrl: 祝日用に作成した予定表リストがあるサイト URL
  • -calendarListName: 祝日用に作成した予定表リストの表示名

実行結果例

実行に成功すると、以下のように予定表に追加された祝日が列挙されます。

f:id:keisuke-blog:20210117004958p:plain

予定表リストにアクセスし、[すべてのイベント] ビューを見ると、祝日用のイベントが作成されていることが確認できます。

f:id:keisuke-blog:20210117005236p:plain

メインの予定表に祝日用の予定表を重ね合わせれば、祝日の予定を反映することができます。
重ね合わせの方法は下記記事をご参考ください。

support.microsoft.com
※ 記事中の Exchange 予定表の部分は SharePoint Online では利用できません。

おわりに

自由にパブリックな予定表をインポートできればよかったのですが、PowerShell スクリプトを使用する方法となってしまいました。もしかしたらもっと簡単な方法があるかもしれないので、もしいい方法があればコメントお待ちしております。

SharePoint 上の予定表リスト上ではなくて、個人の予定表で祝日が参照できれば良い場合は、以下の記事のように個人の Outlook と同期される方法が良いですね。

a-zs.net


スクリプトで使用している PnP については過去の記事にまとめています。
www.samurainote.com

今回の投稿は以上です。

【Teams】Add-TeamUser と Remove-TeamUser でのユーザーの追加や削除が Teams に反映されない場合の注意点

Teams PowerShell を使用してチームのメンバーシップを変更した際、コマンドが正常終了してから、実際に Teams に反映されるまでに時間を要す場合があります。そして、変更を反映をトリガーさせるには、Teams アプリを開いておく必要があるようです。コマンドのリファレンスにも注意事項としてされていますが、かなり気づきにくいので関連する記事や動作についてまとめておきます。

公開情報

公式のリファレンスは以下になります。(英語の記事のみ)
※ 2020 年 12 月時点の記事の内容を抜粋しており、今後サービス側で変更される可能性もあるため公開情報を適宜参考にしてください。

Add-TeamUser コマンドの記事では、コマンドの実行後すぐには反映されず、反映までに 24 -48 時間要する可能性があることが記載されています。

Add-TeamUser (MicrosoftTeamsPowerShell) | Microsoft Docs

The command will return immediately, but the Teams application will not reflect the update immediately. The change can take between 24 and 48 hours to appear within the Teams client.

一方、Remove-TeamUser の記事では、反映までに 24 - 48 時間要する可能性があることに加え、変更を反映させるために Teams アプリを最大 1 時間開いておく必要があると記載されています。

Remove-TeamUser (MicrosoftTeamsPowerShell) | Microsoft Docs

Note: the command will return immediately, but the Teams application will not reflect the update immediately. The Teams application may need to be open for up to an hour before changes are reflected.

つまり、単にコマンドを実行するだけでは、チームのメンバーシップを変更したことが Teams に変更されない可能性があるということです。

コマンドの中身は Graph API

Teams PowerShell のコマンドを実行すると、内部的には処理に対応するエンドポイントの Graph API がコールされます。これは、Web 系の開発でよく使われる Fiddler などの通信をキャプチャするツールを使用すると確認できます。

例えば、Add-TeamUser の場合は、Graph API/groups/ エンドポイントが呼ばれて処理されていることがわかります。

f:id:keisuke-blog:20201231142707p:plain
いくつかの Graph API が呼ばれて処理されている


Teams のチームは Azure AD の Microsoft 365 グループで管理されているので、Graph API の /groups/ エンドポイントの API を使用してグループにユーザーを追加しているようですね。
完全に蛇足ですが、通信をキャプチャした結果から、Add-TeamUser は以下の流れで処理をしていることがわかります。

  1. /users/ エンドポイントに GET 要求し、コマンド実行時に指定したユーザーの情報を取得
  2. /groups/ エンドポイントに GET 要求で、該当のユーザーが M365 グループに存在しているかをチェック ※上記画像ではユーザー (リソース) が存在しなかったので 404 のステータスで応答)
  3. /groups/ エンドポイントに POST 要求で、該当のユーザーを追加


Remove-TeamUser の場合も、同様の流れで Graph API の /groups/ エンドポイントを使用して、M365 グループからユーザーを削除しています。

f:id:keisuke-blog:20201231143415p:plain
Remove-TeamUser コマンドの通信をキャプチャしたもの

ここから Teams PowerShell を使用したグループのメンバーシップ変更が Teams に即時反映されない動作は、Graph API の動作に起因していることがわかります。

変更が同期される条件

Add-TeamUser / Remove-TeamUser コマンドの中身は Graph API であることがわかりました。そこで Graph API 側のリファレンスを確認すると、メンバーシップの変更が同期される仕組みについて記載があります。

docs.microsoft.com

チームが作成された後にメンバーを追加するには、[メンバーの追加]操作をします。 追加操作の間に 1 秒の遅延を追加することをお勧めします。
メンバーシップの変更に関しては、次の点にご注意ください。

Microsoft 365 グループに対して行われたメンバーシップの変更は、通常 24 時間 (場合によってはそれ以上) を必要とするバックグラウンド同期メカニズムによって、Teams と同期します。
このバックグラウンドプロセスは、チーム内の1人以上のユーザー(所有者もしくはメンバー)が Teams デスクトップクライアントでアクティブになっている場合にのみ実行されます。

チームアプリケーションを起動したり実行することはアクティビティになります。ユーザーは、特に変更されているチームにアクセスをする必要はありません。
注: Teams のモバイルクライアントは、メンバーシップの同期を実行しません。このバックグラウンドプロセスが円滑に動作するように、少なくとも1人のユーザーがデスクトップクライアントにログオンしている必要があります。

自分の解釈では以下ようになります。

  • Microsoft 365 グループに対して実行したメンバーシップの変更 (ユーザーの追加や削除) は、Azure AD から Teams へのバックグラウンドでの同期処理により反映される
  • バックグラウンド処理がトリガーされるためには条件がある
  • メンバーシップを変更した M365 グループ (チーム) に所属する任意のユーザーが、Teams デスクトップアプリを開く (ログイン) することで M365 グループから Teams への同期がトリガーされる

また、同期がトリガーされ、M365 グループから Teams アプリへの同期処理が完了するまでに 24 時間程度要することが想定されるため、結果的に、Teams の PowerShell コマンドはエラー無く終了したにも関わらず、Teams 上ではその変更が反映されていないように見えてしまう状況が発生しうることになるようです。公開情報によるとモバイル版の Teams では同期処理をトリガーしないとありますが、Web 版の Teams アプリについては言及がありません。Teams デスクトップと明記していることから、Teams デスクトップアプリを使用することが無難かと思います。

おわりに

テナントによっては、特に新しいチームを作成するような状況では、ユーザー側に直接チームの管理を任せておらず、管理者が PowerShell などで事前にユーザーを追加するような運用もあるかと思います。Add-TeamUser / Remove-TeamUser コマンドでユーザーの追加/削除をしたのに、Teams アプリ上でみるとチームのメンバーが変化していないなどの状況が起きたら、チームに所属するユーザーが Teams のデスクトップアプリを起動して、時間経過により反映されるようになるかを確認する必要がありそうです。

なお、チームの作成からメンバーの追加をまとめて Teams PowerShell で行っている場合、チームの作成者がチームの所有者 (Owner) になっていると思います。この場合は、コマンドを実行するユーザーが Teams デスクトップアプリを起動しておけば、チームの任意のユーザーが Teams デスクトップアプリにサインインするという上限を満たせると思います。手元の環境で検証する限り、デスクトップアプリを起動していなくても反映されることがあったので、必須ではないのかもしれませんが、もし Teams にメンバーシップの変更が反映されない状況になったら、注意してみてください。

チーム作成やメンバーの変更に関するチェック項目も記載があるので、Teams に変更が同期されていないなと思ったら、下記チェック項目も確認してみることをお勧めします。

https://docs.microsoft.com/ja-jp/graph/teams-create-group-and-team?source=docs#checklist-for-validationMicrosoft Graph を使用してチームの作成と、メンバーを管理する - Microsoft Graph | Microsoft Docs

今回の投稿は以上です。

【Teams】Get-Team コマンドの挙動について

Get-Team コマンドは実行するユーザーに依存して得られる結果が異なります。リファレンスを見ると、特定のユーザーが属するすべてのチームや、組織 (テナント) 内のすべてのチームの取得をサポートしていることが記載されています。

docs.microsoft.com

This cmdlet supports retrieving teams with particular properties/information, including all teams that a specific user belongs to, all teams that have been archived, all teams with a specific display name, or all teams in the organization.

Microsoft 365 センターの役割である、Teams サービス管理者が Get-Team コマンドを引数無しで実行すると、テナント内のすべてのチームが返ります。一方、一般ユーザーが実行すると、自分が所属しているすべてのチームが取得されます。

  • Teams サービス管理者: 組織内のすべてのチーム
  • 一般ユーザー: 自分が所属しているすべてのチーム

Teams の PowerShell モジュールは、管理者の役割を与えられていない一般のユーザーであっても実行できるため、他のサービスをいつも触っていると混同してしまう可能性がありますので備忘録として残しておきます。

動作検証

動作を検証してみます。検証に使用した Teams PowerShell モジュールのバージョンは Ver 1.1.6 でした。
検証用に teamsadmin という名前でユーザーを作成し、[Teams サービス管理者] の役割を付与します。

f:id:keisuke-blog:20201231000848p:plain
Microsoft 365 管理センターで役割を付与します

検証に使用するテナントで何チーム存在するかを確認します。検証時は 526 チーム存在していました。

f:id:keisuke-blog:20201231000744p:plain
Teams 管理センターからみると 526 チーム存在している

Teams サービス管理者で Connect-MicrosoftTeams コマンドで Teams に接続後、Get-Team コマンドを実行し、返り値の数をカウントします。

f:id:keisuke-blog:20201231001334p:plain
Get-Team コマンドの返り値が Teams 管理センターの値と一致します

Teams 管理センターで確認したチーム数と一致することが確認できました。
ちなみに、Teams サービス管理者の資格情報でコマンドを実行する場合に、自分が所属しているチームだけを取得したい場合は -User パラメータに自身の UPN を指定します。teamsadmin を適当な 1 チームに所属させ、-User パラメータをつけて実行してみます。

Get-Team -User <ユーザーの UPN>

f:id:keisuke-blog:20201231001835p:plain
-User パラメータを使用すると指定したユーザーが所属しているチームだけが取得できる

一般ユーザーで同じように Get-Team コマンドを実行すると、デフォルトで自分が所属するチームだけが取得されます。

f:id:keisuke-blog:20201231002035p:plain

今回の投稿は以上です。

【SPO】サイトテンプレート機能が表示されないときに確認する項目

オンプレスの SharePoint Server を利用していた場合、サイトの横展開の機能といえばサイト テンプレートを思いつくと思います。クラウドになった SharePoint Online においても、制限事項はあるものの、まだまだ利用されるシーンを多く見ます。本機能は、サイトの設定によって、表示/非表示が切り替わるため、利用するサイトにおいて、テンプレートの項目が表示されていない場合は本記事の項目をチェックし、当てはまっていないかを確認してみてください。なお、公開情報で多くの情報が記載されているため、他の記事に比べて公開情報を多めにリンクしています。

サイトテンプレート機能が表示されない場合とは、以下のような状況を想定しています。

f:id:keisuke-blog:20201227111739p:plain
サイトテンプレート機能
f:id:keisuke-blog:20201227111604p:plain
サイトの設定にサイトテンプレートが表示されていない

注意事項

以下の公開記事に記載があるように、SPO においてはクラシックサイトのみサポート対象となっています。これはおそらくサイトコレクションのテンプレートでいう STS#0 で作られたサイトのみという意味だと思います。そのため、コミュニケーションサイト (SITEPAGEPUBLISHING#0)、モダンチームサイト (STS#3)、および、Teams 等で使用する Microsoft 365 グループのサイト (GROUP#0) 等のサイトではサポートされない機能となります。

f:id:keisuke-blog:20201227103309p:plain
SPO ではクラシックサイトのテンプレート (STS#0) のみがサポート対象

https://docs.microsoft.com/ja-jp/sharepoint/dev/general-development/save-download-and-upload-a-sharepoint-site-as-a-template


また、クラシックサイトにおいてはサポート対象とありますが、サイトテンプレートは、ある時点のサイトの状態をスナップショットとしてパッケージ化する機能のため、SPO とは相性が悪いです。オンプレミス製品では、ユーザーの意思で更新を制御できましたが、クラウド製品である SPO では日々サービス側で機能更新が行われます。そのため、ある時期に作成したサイトテンプレートを展開しようとした際に、当時は存在していたけれど、最新の環境においてはサービス側で削除された列や機能があるなどで、パッケージと現環境での情報に不整合が生じ、サイトテンプレートの展開に失敗することが頻発します。SPO におけるサイトテンプレート利用の注意点や代替機能については、サポートチームが過去に投稿しているようですのでリンクしておきます。

https://social.msdn.microsoft.com/Forums/ja-JP/a07667e0-7921-4cd7-b699-f85345c3745b/-?forum=sharepointsupportteamja

確認項目1: サイトのテンプレートを確認する

上述の公開情報に記載がありますが、SPO においてはクラシックサイトのみサポート対象と記載があります。まずは、利用しているサイトがクラシックサイトのテンプレートを利用しているのかを確認します。確認方法は、複数ありますが、SharePoint 管理センターから確認するか、SharePoint 管理シェルを使用します。基本的にサイトコレクションのテンプレートがモダンのテンプレートの場合、サイトレコレクション全体のサイトにおいてサイトテンプレート機能は表示されていないので、サイトコレクションのテンプレートを確認することが一般的です。(後述しますが、サイトコレクション単位でサイトスクリプトがブロックされるため)

SharePoint 管理センターから確認する場合

アクティブなサイトの一覧のビューにある [テンプレート] 列から判断可能です。チームサイト (クラシック表示)

f:id:keisuke-blog:20201227113344p:plain
チームサイト (クラシック表示) であればクラシックサイト
PowerShell で確認する場合

SharePoint 管理シェルをダウンロードして、以下のコマンドを実行することで使用しているサイトのテンプレートを確認し、STS#0 であればクラシックサイトのテンプレートです。

Get-SPOSite -Identity [SiteUrl] | select Temlate

他のテンプレートの種類については以下の記事にまとめているので必要に応じてご参考ください。

www.samurainote.com

確認項目2: カスタムスクリプトがブロックされていないかを確認する

多くの場合これに該当すると思います。カスタムスクリプトの詳細や設定の影響については公開情報に記載がありますのでご参考ください。

f:id:keisuke-blog:20201227114450p:plain

カスタム スクリプトを許可または禁止する - SharePoint in Microsoft 365 | Microsoft Docs

カスタムスクリプトがブロックされている場合には、サイトテンプレート機能やライブラリのテンプレート機能が利用できないことが明記されています。そもそも、モダンテンプレートでサイトテンプレートが利用できない理由も、作成時のデフォルト値がカスタムスクリプトをブロックする設定になっているからです。クラシックサイトではデフォルト値が "許可" になっているので、サイトテンプレート機能が利用できます。例えば、クラシックサイトのテンプレートで作成されたサイトコレクションにおいて、サイトコレクション作成後にカスタムスクリプトをブロックに変更した場合は、サイト テンプレートが利用できなくなります。

サイトコレクションにおけるカスタムスクリプトの設定は以下の PowerShell コマンドで取得します。

Get-SPOSite -Identity [URL] | select DenyAddAndCustomizePages

戻り値
Enabled: ブロック
Disabled: 許可

返り値が Disabled であり、このサイトコレクションで許可に変更したい場合は以下のコマンドを実行します。

Set-SPOSite -Identity [URL] -DenyAddAndCustomizePages 0

カスタムスクリプトを許可する場合の考慮事項については公開情報がありますのでご参考ください。

カスタム スクリプトを許可する場合のセキュリティに関する考慮事項 - SharePoint in Microsoft 365 | Microsoft Docs

確認項目3: SharePoint Server 発行インフラストラクチャ機能の状態

一番見落としがちですが、[サイトコレクションの機能] のうち、発行インフラストラクチャ機能をアクティブ化している場合、サイトテンプレート機能が表示されなくなります。

f:id:keisuke-blog:20201227132213p:plain

サイトコレクションの機能は、サイトコレクションの管理者ユーザーで、トップサイトのサイトの設定ページにアクセスしたときにのみ表示されます。注意点として、一度でもアクティブ化した場合は、あとで非アクティブに戻したとしても、サイトのテンプレート機能は表示されません。サイトやサイトコレクションの機能をアクティブ化/非アクティブ化すると、内部的な Feature が展開/削除されるのですが、おそらくこの機能に関しては非アクティブ化時に削除されない内部的な機能があるのだと思います。もしクラシックサイトを利用しており、かつ、カスタムスクリプトを許可しているにもかかわらず、[テンプレートとしてサイトを保存] の項目が表示去れていない場合は、発行インフラストラクチャ機能がアクティブ化されているか、過去にアクティブ化されていたか可能性があります。

これに該当する場合は、サイトテンプレートの利用をあきらめて、代替の手段 (サイトスクリプトや PnP リモートプロビジョニング)、あるいは、サードパーティで要望を満たすようなツールがないかを検討する方向になると思います。

今回の投稿は以上となります。

【SPO】サイトページ上のリスト Web パーツにアイテムのコメントを表示させる

最近のアップデートにより、モダン表示のリストのアイテムに対してコメントを残せるようになりました。しかしながら、リスト Web パーツを使用して、サイトページ上に表示した場合は、コメント数が表示されず、コメントが存在するかどうかが判別できません。今回は、列の書式設定 を使用したカスタマイズにより、サイトページ上のリストにおいても、コメント数を把握する方法を紹介します。

リストのコメント機能について

モダンリスト上ではアイテム毎にコメントが残せるようになりました。アイテムにコメントがついていると、ビュー上に吹き出し風のアイコンが表示されます。

f:id:keisuke-blog:20201226160310p:plain

リストのコメント機能

ただし、2020 年 12 月時点では、サイト ページ上にリスト Web パーツを使用してリストを参照した場合は、吹き出し風のアイコンが表示されず、コメントがついているかどうかの判別ができないようです。そのため、各アイテムをクリックして、参照元のリストの表示フォームを開くまでコメントがあるかどうかは分かりません。
 

f:id:keisuke-blog:20201226161302p:plain

サイトページの [リスト] Web パーツではコメントが表示されない

今回行う列の書式設定を使用すると、サイトのページ上でリストを参照する場合も、コメント数と吹き出し風のアイコンを表示することができます。また、アイコンやコメント数をクリックすると、アイテムの表示フォームを開くことができ、参照元のリストと同じような動作を実現できます。

f:id:keisuke-blog:20210126222511p:plain

サイトページ上でも通常のリストと似た動作が実現可能

列上にコメント数を表示させる手順

- 手順
1) 新規にリストを作成し、1 行テキスト列を 1 つ作成します。(例. CommentCount)
2) ビュー上で、作成した列を選択し [この列の書式設定] をクリックします。

f:id:keisuke-blog:20201226162113p:plain

[この列の書式設定] から JSON によるカスタマイズを行う

3) [詳細モード] を選択します。 

f:id:keisuke-blog:20201226162410p:plain

[詳細モード] を選択することで自由に記述することができる

4) 以下の JSON をコピペして貼り付けます。


gist0c31250fb2c78d814fbba76f1cdab0cf


5) [保存] すると、列にアイコンとコメント数が表示されます。

f:id:keisuke-blog:20201226170912p:plain

 

列上に表示させているため、サイトページに挿入したリスト Web パーツ上でもコメント数が表示される動作となります。参照元のリストでは、タイトル列の横にも既定の吹き出しのアイコンが表示されてしまうので、もしアイコンを 2 つ表示させたくない場合は、JSON でアイコンを指定している以下の箇所を含む span 要素を削除し、CommentCount 列側のアイコンを表示しないようにしてください。

"attributes": {
     "iconName": "Comment"
}

作成した列をフォーム上から非表示にする

列の書式設定を使用することで、列上にアイコンとコメント数を表示することができましたが、列の値としては何も保存されていません。そのため、ビュー上にはコメント数が表示されますが、フォーム上では CommentCount 列の値は空になっています。

f:id:keisuke-blog:20201226172535p:plain

実際に値として格納されているわけではない

フォームを表示した際に、ユーザーにこの列の値を表示させたくない場合は、以下の方法でフォーム上から非表示にします。

- 手順
1) [リストの設定] > [詳細設定] から [コンテンツタイプの管理を許可する] を [はい] にします。

f:id:keisuke-blog:20201226172711p:plain

2) [リストの設定] ページ上に [コンテンツ タイプ] のセクションが表示されるので、"Item" をクリックします。 

f:id:keisuke-blog:20201226173047p:plain

[リストの設定] ページの中央あたりに表示されている


3) フォーム上から非表示にしたい列を選択します。(例. CommentCount)

f:id:keisuke-blog:20201226173336p:plain

4) 以下の [非表示 (フォームに表示しない) ] に変更し、保存します。

f:id:keisuke-blog:20201226173548p:plain


これで、表示フォームや編集フォーム上でアイテムを表示する際には、この列が表示されなくなります。

おまけ

今回使用した JSON では、Button 要素の customRowAction プロパティとして defaultClick を指定しています。これを指定すると、オリジナルのリストでリストアイテムをクリックしたときと同じように、アイテムの列の値などが表示されます。サイトページ上に挿入したリスト Web パーツ上で、アイコンやコメント数の部分をクリックすると、別タブが起動し、オリジナルのリストの表示フォーム (DispForm,aspx?ID=1&....) という URL に遷移して、オリジナルのリスト側でコメントを閲覧したり投稿したりする動作となります。

Button 要素の説明については下記記事が参考になります。

列の書式設定で SharePoint をカスタマイズする | Microsoft Docs

本日の投稿は以上です。

【Teams】365管理ポータルから Teams で使用するチーム (Microsoft 365 グループ) の一覧を取得する

Microsoft 365 管理センターのフィルタ機能を使用して、テナント内の Microsoft 365 グループの中から、Teams で使用されている Microsoft 365 グループの一覧を取得する方法を紹介します。

1) Microsoft 管理センターにアクセスします。
2) ナビゲーションの [グループ] > [アクティブなグループ] を選択します。
3) テナント内のグループ一覧が表示されたら、[Teamsの状態] という列が表示されていることを確認します。

※ 列の表示は必須ではありませんが、アイコンが表示され、手順に誤りがあったときに気づきやすいやので、表示することをお勧めします。
※ 表示されていない場合は、ヘッダの右端にある [列の選択] から、列の表示/非表示の切り替えができます。

f:id:keisuke-blog:20201226110313p:plain

[Teams の状態] 列が表示されていることを確認する

4) 右上に表示されている [フィルター] > [Teams でのグループ] を選択します。

Teams のチームにも使われている M365 グループのみに絞り込み可能

5) ページ上部にある [グループをエクスポート] をクリックします。


6) "Group.csv " というファイルが出力され、Teams を使用する M365 グループの情報が保存されていることを確認します。

 

CSV ファイルの一部を抜粋

グループ ID など、管理センターの画面上には表示されていなかった情報も保存されていることがわかります。この CSV ファイルを使用して、各サービスの PowerShell にて、Teams で使用するグループだけを対象に、スクリプトで設定を取得/変更といった用途も考えられますね。

普段はすべて PowerShell で済ませることも多いかと思いますが、画面操作でも特定の用途のグループに絞り込むことができるという内容でした。

今回の投稿は以上です。

 

【SPO】SharePoint REST API のエンドポイントの一覧

SharePoint / SharePoint Online において、内部的なプロパティ値の確認したい場面では REST API が便利です。特に GET に関しては、ブラウザの URL に直接入力することで実行できるのでとても便利です。業務でよく使うのですが、リファレンスが少ないので、Web スクレイピングを利用して一覧を作成してみました。

Note:

これらは REST API の返り値に含まれる link 要素を再帰的に参照し、一覧化したものです。そのため、返り値に含まれないエンドポイントなどが含まれていない可能性があるため、網羅性や正確性を保証するものではないことをご留意ください。

エンドポイントの一覧

/_api/Web
/_api/Web/Activities
/_api/Web/ActivityLogger
/_api/Web/Alerts
/_api/Web/AllProperties
/_api/Web/AppTiles
/_api/Web/AssociatedMemberGroup
/_api/Web/AssociatedOwnerGroup
/_api/Web/AssociatedVisitorGroup
/_api/Web/Author
/_api/Web/AvailableContentTypes
/_api/Web/AvailableFields
/_api/Web/AvailableFields(guid'GUID')
/_api/Web/AvailableFields(guid'GUID')/DescriptionResource
/_api/Web/AvailableFields(guid'GUID')/TitleResource
/_api/Web/CanModernizeHomepage
/_api/Web/ClientWebParts
/_api/Web/ContentTypes
/_api/Web/ContentTypes(ID)
/_api/Web/ContentTypes(ID)/DescriptionResource
/_api/Web/ContentTypes(ID)/FieldLinks
/_api/Web/ContentTypes(ID)/Fields
/_api/Web/ContentTypes(ID)/NameResource
/_api/Web/ContentTypes(ID)/Parent
/_api/Web/ContentTypes(ID)/WorkflowAssociations
/_api/Web/CurrentUser
/_api/Web/DataLeakagePreventionStatusInfo
/_api/Web/DescriptionResource
/_api/Web/EventReceiver/GetById(guid'GUID')
/_api/Web/EventReceivers
/_api/Web/Features
/_api/Web/Features/GetById(guid'GUID')
/_api/Web/Fields
/_api/Web/FirstUniqueAncestorSecurableObject
/_api/Web/Folders
/_api/web/GetFolderByServerRelativeUrl('/SITES/SITENAME/LIBRARY/FOLDER')/Files('FILENAME.xlsx')
/_api/Web/GetFolderByServerRelativePath(decodedurl='/sites/sitename/Shared Documents/Folder')
/_api/Web/GetFolderByServerRelativePath(decodedurl='/sites/sitename/Shared Documents/Folder')/Files
/_api/Web/GetFolderByServerRelativePath(decodedurl='/sites/sitename/Shared Documents/Folder')/Folders
/_api/Web/GetFolderByServerRelativePath(decodedurl='/sites/sitename/Shared Documents/Folder')/ListItemAllFields
/_api/Web/GetFolderByServerRelativePath(decodedurl='/sites/sitename/Shared Documents/Folder')/ParentFolder
/_api/Web/GetFolderByServerRelativePath(decodedurl='/sites/sitename/Shared Documents/Folder')/Properties
/_api/Web/GetFolderByServerRelativePath(decodedurl='/sites/sitename/Shared Documents/Folder')/StorageMetrics
/_api/Web/GetUserById(ID)
/_api/Web/GetUserById(ID)/Alerts
/_api/Web/GetUserById(ID)/Groups
/_api/Web/HostedApps
/_api/Web/Lists
/_api/Web/Lists(guid'GUID')
/_api/Web/Lists(guid'GUID')/Author
/_api/Web/Lists(guid'GUID')/ContentTypes
/_api/Web/Lists(guid'GUID')/CreatablesInfo
/_api/Web/Lists(guid'GUID')/DefaultView
/_api/Web/Lists(guid'GUID')/DescriptionResource
/_api/Web/Lists(guid'GUID')/EventReceivers
/_api/Web/Lists(guid'GUID')/Fields
/_api/Web/Lists(guid'GUID')/FirstUniqueAncestorSecurableObject
/_api/Web/Lists(guid'GUID')/Forms
/_api/Web/Lists(guid'GUID')/InformationRightsManagementSettings
/_api/Web/Lists(guid'GUID')/Items
/_api/Web/Lists(guid'GUID')/ParentWeb
/_api/Web/Lists(guid'GUID')/RoleAssignments
/_api/Web/Lists(guid'GUID')/RootFolder
/_api/Web/Lists(guid'GUID')/RootFolder/Files
/_api/Web/Lists(guid'GUID')/Subscriptions
/_api/Web/Lists(guid'GUID')/TitleResource
/_api/Web/Lists(guid'GUID')/UserCustomActions
/_api/Web/Lists(guid'GUID')/Views
/_api/Web/Lists(guid'GUID')/WorkflowAssociations
/_api/Web/Lists/GetByTitle('TITLE')
/_api/Web/Lists/GetByTitle('TITLE')/Author
/_api/Web/Lists/GetByTitle('TITLE')/ContentTypes
/_api/Web/Lists/GetByTitle('TITLE')/CreatablesInfo
/_api/Web/Lists/GetByTitle('TITLE')/DefaultView
/_api/Web/Lists/GetByTitle('TITLE')/DescriptionResource
/_api/Web/Lists/GetByTitle('TITLE')/EventReceivers
/_api/Web/Lists/GetByTitle('TITLE')/Fields
/_api/Web/Lists/GetByTitle('TITLE')/FirstUniqueAncestorSecurableObject
/_api/Web/Lists/GetByTitle('TITLE')/Forms
/_api/Web/Lists/GetByTitle('TITLE')/InformationRightsManagementSettings
/_api/Web/Lists/GetByTitle('TITLE')/Items
/_api/Web/Lists/GetByTitle('TITLE')/ParentWeb
/_api/Web/Lists/GetByTitle('TITLE')/RoleAssignments
/_api/Web/Lists/GetByTitle('TITLE')/RootFolder
/_api/Web/Lists/GetByTitle('TITLE')/RootFolder/Files
/_api/Web/Lists/GetByTitle('TITLE')/Subscriptions
/_api/Web/Lists/GetByTitle('TITLE')/TitleResource
/_api/Web/Lists/GetByTitle('TITLE')/UserCustomActions
/_api/Web/Lists/GetByTitle('TITLE')/Views
/_api/Web/Lists/GetByTitle('TITLE')/WorkflowAssociations
/_api/Web/ListTemplates
/_api/Web/ListTemplates()
/_api/Web/MultilingualSettings
/_api/Web/MultilingualSettings/Recipients
/_api/Web/Navigation
/_api/Web/Navigation/GetNodeById(ID)
/_api/Web/Navigation/GetNodeById(ID)/Children
/_api/Web/Navigation/QuickLaunch
/_api/Web/Navigation/TopNavigationBar
/_api/Web/OneDriveSharedItems
/_api/Web/ParentWeb
/_api/Web/PushNotificationSubscribers
/_api/Web/RecycleBin
/_api/Web/RegionalSettings
/_api/Web/RegionalSettings/InstalledLanguages
/_api/Web/RegionalSettings/TimeZone
/_api/Web/RegionalSettings/TimeZones
/_api/Web/RoleAssignments
/_api/Web/RoleAssignments/GetByPrincipalId(ID)
/_api/Web/RoleAssignments/GetByPrincipalId(ID)/Member
/_api/Web/RoleAssignments/GetByPrincipalId(ID)/RoleDefinitionBindings
/_api/Web/RoleDefinitions
/_api/Web/RoleDefinitions(ID)
/_api/Web/RootFolder
/_api/Web/SiteCollectionAppCatalog
/_api/Web/SiteGroups
/_api/Web/SiteGroups/GetById(ID)
/_api/Web/SiteGroups/GetById(ID)/Owner
/_api/Web/SiteGroups/GetById(ID)/Users
/_api/Web/SiteUserInfoList
/_api/Web/SiteUserInfoList/Author
/_api/Web/SiteUserInfoList/ContentTypes
/_api/Web/SiteUserInfoList/ContentTypes(ID)
/_api/Web/SiteUserInfoList/ContentTypes(ID)/DescriptionResource
/_api/Web/SiteUserInfoList/ContentTypes(ID)/FieldLinks
/_api/Web/SiteUserInfoList/ContentTypes(ID)/Fields
/_api/Web/SiteUserInfoList/ContentTypes(ID)/NameResource
/_api/Web/SiteUserInfoList/ContentTypes(ID)/Parent
/_api/Web/SiteUserInfoList/ContentTypes(ID)/WorkflowAssociations
/_api/Web/SiteUserInfoList/CreatablesInfo
/_api/Web/SiteUserInfoList/DefaultView
/_api/Web/SiteUserInfoList/DescriptionResource
/_api/Web/SiteUserInfoList/EventReceivers
/_api/Web/SiteUserInfoList/Fields
/_api/Web/SiteUserInfoList/Fields(guid'GUID')
/_api/Web/SiteUserInfoList/Fields(guid'GUID')/DescriptionResource
/_api/Web/SiteUserInfoList/Fields(guid'GUID')/TitleResource
/_api/Web/SiteUserInfoList/FirstUniqueAncestorSecurableObject
/_api/Web/SiteUserInfoList/Forms
/_api/Web/SiteUserInfoList/InformationRightsManagementSettings
/_api/Web/SiteUserInfoList/Items
/_api/Web/SiteUserInfoList/Items(ID)
/_api/Web/SiteUserInfoList/Items(ID)/AttachmentFiles
/_api/Web/SiteUserInfoList/Items(ID)/ContentType
/_api/Web/SiteUserInfoList/Items(ID)/FieldValuesAsHtml
/_api/Web/SiteUserInfoList/Items(ID)/FieldValuesAsText
/_api/Web/SiteUserInfoList/Items(ID)/FieldValuesForEdit
/_api/Web/SiteUserInfoList/Items(ID)/File
/_api/Web/SiteUserInfoList/Items(ID)/FirstUniqueAncestorSecurableObject
/_api/Web/SiteUserInfoList/Items(ID)/Folder
/_api/Web/SiteUserInfoList/Items(ID)/GetDlpPolicyTip
/_api/Web/SiteUserInfoList/Items(ID)/LikedByInformation
/_api/Web/SiteUserInfoList/Items(ID)/ParentList
/_api/Web/SiteUserInfoList/Items(ID)/Properties
/_api/Web/SiteUserInfoList/Items(ID)/RoleAssignments
/_api/Web/SiteUserInfoList/Items(ID)/Versions
/_api/Web/SiteUserInfoList/ParentWeb
/_api/Web/SiteUserInfoList/RoleAssignments
/_api/Web/SiteUserInfoList/RootFolder
/_api/Web/SiteUserInfoList/Subscriptions
/_api/Web/SiteUserInfoList/TitleResource
/_api/Web/SiteUserInfoList/UserCustomActions
/_api/Web/SiteUserInfoList/Views
/_api/Web/SiteUserInfoList/Views(guid'GUID')
/_api/Web/SiteUserInfoList/Views(guid'GUID')/ViewFields
/_api/Web/SiteUserInfoList/WorkflowAssociations
/_api/Web/SiteUsers
/_api/Web/TenantAppCatalog
/_api/Web/TenantAppCatalog/AvailableApps
/_api/Web/TenantAppCatalog/SiteCollectionAppCatalogsSites
/_api/Web/ThemeInfo
/_api/Web/TitleResource
/_api/Web/UserCustomActions
/_api/Web/WebInfos
/_api/Web/Webs
/_api/Web/WorkflowAssociations
/_api/Web/WorkflowTemplates

 

ちょっとした値の確認だけであれば、モジュールのインストール無しで利用できるため、PnP や CSOM を使うより手軽に使えます。もし抜け漏れがあれば随時追加するのでコメントいただければと思います。

 

今回の投稿は以上です。