膨大な数の Office 365 ユーザーへの PowerShell コマンドレットの実行


(この記事は 2015 年 11 月 2 日に Exchange Team Blog に投稿された記事 Running PowerShell cmdlets for large numbers of users in Office 365 の翻訳です。最新情報については、翻訳元の記事をご参照ください。)

 

PowerShell は Exchange 2007 の時代に登場したスクリプト言語であり、多くの Exchange 管理者に恩恵をもたらしています。PowerShell のおかげで、管理者は大量のオブジェクトをすばやくシームレスに管理できるようになりました。以来、PowerShell は、管理者がユーザー、グループ、その他のオブジェクト セットを更新する際に欠かせないものとなりました。

ただし、Office 365 の提供が開始されてからは、少し状況が変わりました。もはやローカルの Exchange サーバーに対してコマンドレットを実行するのではなく、インターネット経由で共有のリソースに対して通信を行うことが必要になりました。そのため、以前には存在しなかった大量のオブジェクトを管理する必要が生じ、さまざまな課題が発生するようになりました。

PowerShell のスロットル

Office 365 には多くのスロットルが導入されており、1 つのテナントまたは 1 ユーザーがリソースを過剰に使用して多くのユーザーに悪影響を与えることがないようになっています。PowerShell では、主に、Micro Delay として表示されます。

WARNING: Micro delay applied. Actual delayed: 21956 msecs, Enforced: True, Capped delay: 21956 msecs, Required: False,
Additional info: .;
PolicyDN: CN=[SERVER]-B2BUpgrade-2015-01-21T03:07:53.3317375Z,CN=Global
Settings,CN=Configuration,CN=company.onmicrosoft.com,CN=ConfigurationUnits,DC=FOREST,DC=prod,DC=outlook,DC=com;
Snapshot: Owner: Sid~EURPR05A003\a-ker531361849849165~PowerShell~false
BudgetType: PowerShell
ActiveRunspaces: 0/20
Balance: -1608289/2160000/-3000000
PowerShellCmdletsLeft: 384/400
ExchangeCmdletsLeft: 185/200
CmdletTimePeriod: 5
DestructiveCmdletsLeft: 120/120
DestructiveCmdletTimePeriod: 60

上記のような Office 365 のスロットルは、タンクのようなものと考えることができます。サービスが常にタンクの最上部から時間をつぎ足し、管理者がタンクの最下部から時間を使用します。サービスによって追加される時間が管理者の使用時間を上回っている限り、問題が発生することはありません。

問題が発生しやすいのは、Get-MobileDeviceStatisticsSet-ClutterGet-MailboxFolderStatistics などの高負荷のコマンドを実行するときです。これらのコマンドは各ユーザーに対して実行され多くのリソースを使用するため、多大な時間を必要とします。このシナリオでは、サービスによって追加される時間よりも多くの時間を管理者が使用しているため、最終的にスロットル制御される状態になります。

見方を変えれば、返された値から、どのくらいの時間をコマンド実行に費やせるかを計算できます。Micro Delay の警告を見てみると、次のようにリチャージ レートが示されています。

ActiveRunspaces: 0/20
Balance: -1608289/2160000/-3000000
PowerShellCmdletsLeft: 384/400

この場合、リチャージ レートは 2,160,000 ミリ秒です。これは、1 時間あたり何ミリ秒をリソースの使用に費やすことができるかを示しています。ここに示される値は、テナントの受信トレイの数によって異なります。

リチャージ レートが確認できれば、その値を 1 時間のミリ秒数で除算すると、セッション内でアクティブにリソースを使用できる時間数に換算できます。

2,160,000 ミリ秒のリチャージ レート / 3,600,000 ミリ秒 = 0.6 時間

つまり、0.6 × 60 分 = 36 分

ただし、日常のタスクに PowerShell を使用している場合、こうした制限にかかりそうになることはありません。コマンドの入力には多少の時間がかかるうえに、たいていの人は 1 回で何時間も PowerShell コマンドを全速力で入力しようとはしません。

Micro Delay が発生したときの簡単な解決策は、コマンドとコマンドの間に十分な一時停止を挟んで使用可能な枠を上回らないようにすることです。

$(Get-Mailbox) | foreach { Get-MobileDeviceStatistics -mailbox $_.identity; Start-Sleep -milliseconds 500 }

セッションの安定性

次に共通の問題として挙げられるのは、セッションの安定性の問題です。インターネット経由で接続するため、セッションの安定性は非常に大きな懸念事項となります。スクリプトを使用して、180,000 ユーザーに対して Get-InboxRule を 4 日間実行すると、その期間のどこかの時点で HTTPS セッションが切断される可能性が非常に高くなります。

セッションが切断される原因は複数考えられます。

  • 長時間実行されているセッションがファイアウォールによって切断された
  • Office 365 CAS サーバーがアップグレードまたは再起動された
  • アップストリーム ネットワークの問題

セッションの切断の原因の多くは、管理者には制御できないものです。この問題は、資格情報が必要な PowerShell ウィンドウに戻ると明らかになります。

これを打開する手軽で簡単な方法はありません。接続の状態を監視して、何らかのエラーが発生している場合は再接続する必要があります。それを実行することは可能ですが、複数行のコードを使用するうえに、必要になるたびに組み込んで使用するという手間を伴います。

返されるデータ

最後に、返されるデータの問題があります。膨大な数の受信トレイに対して Set コマンドを実行する前に、実行対象のオブジェクトの配列を取得する必要があります。多くの場合、$mbx = Get-Mailbox -ResultSize Unlimited のようなコマンドを使用します。ただし、何千という受信トレイにこのコマンドを実行すると、大量のデータがサーバーから返され長時間を要する可能性があります。

この問題は、2 つのシンプルな PowerShell のテクニックを用いることで簡単に回避できます。

はじめに、Invoke-Command を使用して、ローカル ホストではなくリモート サーバーに対して Get コマンドを実行します。こうすることで、インターネット経由でコマンドを送信して Office 365 サーバーで実行することができるため、多くのクライアントをループ処理から外すことができます。

処理を実行する負荷をデータのソース側に任せることができるので、この方法は非常に有効です。ただし、完璧なソリューションではありません。リモート セッションと Invoke-Command を使用して複雑な処理を実行することはできないからです。つまり、きわめて簡単なコマンドレットとシンプルなフィルター処理しか実行できません。複雑な処理を実行しようとすると、サーバーからエラーが返されコマンドが拒否されます。

そこで、2 つ目のテクニック Select-Object が必要になります。Invoke-CommandSelect-Object を組み合わせて使用することにより、オブジェクトを迅速に取得して限られた量のデータだけが返されるようにすることができます。

Invoke-Command -session (Get-Pssession) -scriptblock {Get-Mailbox -resultsize unlimited | select-object -property Displayname,Identity,PrimarySMTPAddress}

このコマンドはサーバーのセッションを呼び出して Get-Mailbox を実行し、Select-Object を使用して、処理対象のオブジェクトの識別に必要なプロパティのみを返します。

5,000 件のメール連絡先を使用した私のテストでは、Invoke-Command の処理時間は平均して 35% 短縮され、PowerShell クライアントに対して返されるデータ量は大幅に削減されました。

スクリプト化したソリューション

ここまでで、どのような問題が発生する可能性があるかご理解いただけたと思います。大量のオブジェクトに対してコマンドレットを実行する必要がある場合は、一貫性のある再利用可能なソリューションを使用することで、それらの問題に対処できます。

そのため、私たちは汎用性の高いラッパー スクリプト、Start-RobustCloudCommand.ps1 (英語) を開発しました。このスクリプトは、スロットルを回避しセッションの問題に積極的に対処する安定した方法で、サービスに対して PowerShell コマンドを実行します。Invoke-Command と組み合わせて操作対象のオブジェクトのリストをすばやく取得してから、私たちが提供する PowerShell 機能を使用してください。

スクリプトはこちらのページ (英語) からダウンロードできます。

はじめに、重要な免責条項が表示されます。このスクリプトは、コマンドの完了までの時間を短縮することはありません。ユーザーあたりの処理時間が 5 秒のコマンドを 7,200 ユーザーに対して実行すると、完了まで少なくとも 10 時間はかかります。このラッパー スクリプトを使用すると、実際には少し遅くなります。このスクリプトは、PowerShell コマンドが、管理者による操作なしで中断、失敗することなく 10 時間以上実行されるようにするという非常に困難な作業を担当します。

スクリプトの使用方法

このラッパー スクリプトを使用するには、以下の 3 つの手順を実行します。

  1. 実行する PowerShell スクリプト ブロックを作成します。
  2. 実行対象のオブジェクトを収集します。
  3. スクリプト ブロックをラップして実行します。

PowerShell スクリプト ブロックを作成する

PowerShell コマンドとほとんど同じように作成できます。コマンドを作成したら、Start-RobustCloudCommand.ps1 で使用する前に 1 人のユーザーでテストします。

最初のステップはとても簡単です。Invoke-Command から返される 1 ユーザーのデータに対して実行する PowerShell を記述するだけです。

 

$mbx = invoke-command -session (get-pssession) -scriptblock {get-mailbox $mailboxstring | select-object -property DisplayName,Identity,PrimarySMTPAddress}
Get-MobileDeviceStatistics -mailbox $mbx.primarysmtpaddress.tostring() | select-object @{Name="DisplayName";Expression={$mbx.Displayname}},Status,DeviceOS,DeviceModel,LastSuccessSync,FirstSyncTime

ここで、まず変数 $mbx に 1 つの受信トレイの情報を入力しますが、私は、完全なデータ セットを収集する場合とまったく同じ方法を使用します。つまり、Invoke-CommandSelect-Object を使用して、Get-MobileDeviceStatistics の実行に必要な最低限のプロパティのセットのみを取得します。

Invoke-Command を使用すると、結果として想定外のデータ型が取得される場合があります。つまり、返されるオブジェクトをコマンドに渡すと、渡されたデータ型を処理できないというエラーが発生する可能性があります。こうしたケースのほとんどは、.tostring() を使用して結果を文字列に変換して、コマンドレットが認識できるようにすることで解決できます。

次に、Get-MobileDeviceStatistics コマンドを実行して、再び Select-Object で必要な出力情報のみを取得します。また、Select-Object の機能 (こちらのページ (英語) の例 4) を使用してその場でプロパティを作成し、受信トレイの Display Name を出力に含めます。こうすれば、既定の Get-MobileDeviceStatistics の出力に含まれていない項目を出力できます。これで、スクリプトを実行して必要なデータを取得できるようになりました。後は、わずかな変更を加えて、それを Start-RobustCloudCommand.ps1 のスクリプト ブロックで機能させるだけです。スクリプト ブロックは、$mbx 変数を読み取ることができません。これは、スクリプト内ではローカルの Invoke-Command -input を使用して各オブジェクトを 1 つずつコマンドに渡すためです。そのため、オブジェクトの値を読み取るには、自動変数の $input を使用する必要があります。

よって、コマンドは以下のようになります。

Get-MobileDeviceStatistics -mailbox $input.primarysmtpaddress.tostring() | select-object @{Name="DisplayName";Expression={$input.Displayname}},Status,DeviceOS,DeviceModel,LastSuccessSync,FirstSyncTime

簡単な変更ですが、スクリプトを機能させるために非常に重要です。

実行対象のオブジェクトを収集する

実行するコマンドができたので、次に、実行対象のすべてのオブジェクトを収集します。

ここで、上記で学習したことを活かして、処理の実行対象のオブジェクトをすばやく効率的に収集したいと思います。

 

Invoke-Command -Session (Get-PSSession) -ScriptBlock {Get-Mailbox -resultsize unlimited | select-object -property DisplayName,Identity,PrimarySMTPAddress} | Export-Csv c:\temp\users.csv
$mbx = Import-Csv c:\temp\users.csv

出力を CSV ファイルにエクスポートしてから、ファイルを変数にインポートします。オブジェクトの大規模なコレクションを処理する場合、このような方法で、処理対象オブジェクトのコレクションを書き出したものを取得しておくことをお勧めします。後から、制御できない問題が生じたときにその CSV ファイルを使用して、処理済みのオブジェクトを CSV ファイルから除外した状態でスクリプトを再開できます。スクリプトの再開は手動で行います。

スクリプト ブロックをラップして実行する

最後に、すべてをまとめてラッパー スクリプトに含めます。

$cred = Get-Credential
.\Start-RobustCloudCommand.ps1 -Agree -LogFile c:\temp\10012015.log -Recipients $mbx -ScriptBlock { Get-MobileDeviceStatistics -Mailbox $input.primarysmtpaddress.tostring() | Select-Object @{Name="DisplayName";Expression={$input.Displayname}},Status,DeviceOS,DeviceModel,LastSuccessSync,FirstSyncTime } -Credential $cred

ここで、各テスト ユーザーに対してスクリプトが繰り返し実行され、コマンドが実行され、結果が画面に出力されます。また、スクリプトによってすべての処理のログが画面に表示され、指定したログ ファイルに記録されます。

画面への情報の表示はデモには最適ですが、通常この情報は、後からの確認用にファイルに保存します。スクリプト ブロックの最後に add | Export-Csv c:\temp\devices.csv -Append を追加するだけで、各コマンドの出力を CSV に保存できます。基本的に、これは非常に複雑な foreach ループなので、-append が必要です。-append を使用しないと、すべてのユーザーに対して上書きを行ってしまいます。

.\Start-RobustCloudCommand.ps1 -Agree -LogFile c:\temp\10012015.log -Recipients $mbx -ScriptBlock { Get-MobileDeviceStatistics -Mailbox $input.primarysmtpaddress.tostring() | Select-Object @{Name="DisplayName";Expression={$input.Displayname}},Status,DeviceOS,DeviceModel,LastSuccessSync,FirstSyncTime | Export-Csv c:\temp\devices.csv -Append } -Credential $cred

まとめ

このソリューションは、サービス内の膨大な数 (5,000 超) のユーザーに対してコマンドレットを実行する必要があり、なおかつ問題が発生しているお客様のために設計されました。望みどおりの処理を機能させるにしては少し複雑ですが、非常に汎用性の高い設計がなされているため、ほぼすべての処理に対応できます。

膨大な数のユーザーを処理するお客様にこのソリューションをご活用いただき、各テナントへの変更の適用やレポートの実行が容易に実現されることを願っております。

Matthew Byrd

 

Skip to main content